~ubuntu-core-dev/update-notifier/ubuntu

858 by Steve Langasek
Migrate fully from python2 to python3 now that python3-debian is
1
#!/usr/bin/python3
668 by Michael Vogt
add empty test in preparation for more to come :)
2
848.1.1 by Michael Vogt
use apt-helper to download the data file to get the apt proxy settings
3
import hashlib
887 by Brian Murray
* data/package-data-downloader:
4
import http.server as SimpleHTTPServer
670 by Michael Vogt
add tests before refactoring
5
import os
848.1.1 by Michael Vogt
use apt-helper to download the data file to get the apt proxy settings
6
import random
670 by Michael Vogt
add tests before refactoring
7
import shutil
859 by Steve Langasek
More python3 compat fixes
8
import socketserver
668 by Michael Vogt
add empty test in preparation for more to come :)
9
import sys
887 by Brian Murray
* data/package-data-downloader:
10
import time
670 by Michael Vogt
add tests before refactoring
11
import tempfile
668 by Michael Vogt
add empty test in preparation for more to come :)
12
import unittest
13
887 by Brian Murray
* data/package-data-downloader:
14
from mock import patch
15
from multiprocessing import Process
848.1.1 by Michael Vogt
use apt-helper to download the data file to get the apt proxy settings
16
888 by Brian Murray
releasing package update-notifier version 3.173
17
sys.path.insert(0, "data")
18
sys.path.insert(1, "../data")
19
import package_data_downloader  # nopep8
668 by Michael Vogt
add empty test in preparation for more to come :)
20
848.1.1 by Michael Vogt
use apt-helper to download the data file to get the apt proxy settings
21
22
class TestHttpd(Process):
23
    def __init__(self, port, servdir):
24
        super(TestHttpd, self).__init__()
25
        self.port = port
26
        self.servdir = servdir
887 by Brian Murray
* data/package-data-downloader:
27
848.1.1 by Michael Vogt
use apt-helper to download the data file to get the apt proxy settings
28
    def run(self):
29
        os.chdir(self.servdir)
30
        Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
859 by Steve Langasek
More python3 compat fixes
31
        httpd = socketserver.TCPServer(("localhost", self.port), Handler)
848.1.1 by Michael Vogt
use apt-helper to download the data file to get the apt proxy settings
32
        httpd.serve_forever()
33
34
35
class DownloadTestCase(unittest.TestCase):
36
37
    def setUp(self):
38
        self.tempdir = tempfile.mkdtemp()
39
        self._setup_canary_file()
40
        self.PORT = random.randint(1025, 60000)
41
        self.p = TestHttpd(self.PORT, self.tempdir)
42
        self.p.start()
887 by Brian Murray
* data/package-data-downloader:
43
848.1.1 by Michael Vogt
use apt-helper to download the data file to get the apt proxy settings
44
    def tearDown(self):
45
        self.p.terminate()
46
        shutil.rmtree(self.tempdir)
887 by Brian Murray
* data/package-data-downloader:
47
848.1.1 by Michael Vogt
use apt-helper to download the data file to get the apt proxy settings
48
    def _setup_canary_file(self):
49
        self.canary_text = "meep"
50
        self.canary_file = os.path.join(self.tempdir, "canary-file.txt")
51
        with open(self.canary_file, "w") as f:
52
            f.write(self.canary_text)
887 by Brian Murray
* data/package-data-downloader:
53
        self.canary_hashsum = hashlib.sha256(
54
            self.canary_text.encode('utf-8')).hexdigest()
55
848.1.1 by Michael Vogt
use apt-helper to download the data file to get the apt proxy settings
56
    def test_download_file(self):
848.1.3 by Michael Vogt
fix hashsum calculation and download destination
57
        package_data_downloader.STAMPDIR = self.tempdir
848.1.1 by Michael Vogt
use apt-helper to download the data file to get the apt proxy settings
58
        os.makedirs(os.path.join(self.tempdir, "partial"))
59
        uri = "http://localhost:%s/%s" % (
60
            self.PORT, os.path.basename(self.canary_file))
61
        destfile = package_data_downloader.download_file(
848.1.3 by Michael Vogt
fix hashsum calculation and download destination
62
            uri, self.canary_hashsum)
848.1.1 by Michael Vogt
use apt-helper to download the data file to get the apt proxy settings
63
        self.assertNotEqual(destfile, None)
887 by Brian Murray
* data/package-data-downloader:
64
        self.assertEqual(hashlib.sha256(
65
            open(destfile).read().encode('utf-8')).hexdigest(),
66
            self.canary_hashsum)
67
68
69
class ProcessDownloadRequestsTestCase(unittest.TestCase):
70
71
    def setUp(self):
72
        self.tempdir = tempfile.mkdtemp()
73
        self._setup_canary_file()
74
        self.PORT = random.randint(1025, 60000)
75
        self.p = TestHttpd(self.PORT, self.tempdir)
76
        self.p.start()
77
        self.tempdir = tempfile.mkdtemp()
78
        # stampdir
79
        stampdir = os.path.join(self.tempdir, "stampdir")
80
        os.makedirs(stampdir)
81
        package_data_downloader.STAMPDIR = stampdir
82
        os.makedirs(os.path.join(stampdir, "partial"))
83
        # datadir
84
        datadir = os.path.join(self.tempdir, "datadir")
85
        os.makedirs(datadir)
86
        package_data_downloader.DATADIR = datadir
87
        # override the location of the notifier files
88
        notifierdir = os.path.join(self.tempdir, "notifierdir")
89
        os.makedirs(notifierdir)
90
        package_data_downloader.NOTIFIER_FILE = \
91
            os.path.join(notifierdir, "data-downloads-failed")
92
        package_data_downloader.NOTIFIER_PERMANENT_FILE = \
93
            package_data_downloader.NOTIFIER_FILE + "-permanently"
94
95
    def tearDown(self):
96
        self.p.terminate()
97
        shutil.rmtree(self.tempdir)
98
99
    def _setup_canary_file(self):
100
        self.canary_text = "meep"
101
        self.canary_file = os.path.join(self.tempdir, "canary-file.txt")
102
        with open(self.canary_file, "w") as f:
103
            f.write(self.canary_text)
104
        self.canary_hashsum = hashlib.sha256(
105
            self.canary_text.encode('utf-8')).hexdigest()
106
107
    def _setup_hook_file(self, filename, script="/bin/true"):
108
        with open(os.path.join(package_data_downloader.DATADIR,
109
                  filename), "w") as foo:
110
            foo.write("Url: http://localhost:%s/%s\n" %
111
                      (self.PORT, os.path.basename(self.canary_file)))
112
            foo.write("Sha256: %s\n" % self.canary_hashsum)
113
            foo.write("\n")
114
            foo.write("Script: %s" % script)
115
116
    def test_hookfile_older_than_stampfile(self):
117
        # hook file older than stamp file nothing happens
118
        # create the hook file
119
        hookfile = "older-hookfile"
120
        self._setup_hook_file(hookfile)
121
        time.sleep(0.01)
122
        # create the stamp file
123
        stampfile = os.path.join(package_data_downloader.STAMPDIR,
124
                                 hookfile)
888 by Brian Murray
releasing package update-notifier version 3.173
125
        with open(stampfile, "w"):
887 by Brian Murray
* data/package-data-downloader:
126
                pass
127
        orig_stamp_time = os.stat(stampfile).st_mtime
128
        package_data_downloader.process_download_requests()
129
        new_stamp_time = os.stat(stampfile).st_mtime
130
        self.assertEqual(orig_stamp_time, new_stamp_time)
131
        # confirm failure files not created
132
        self.assertFalse(os.path.exists(
133
            os.path.join(package_data_downloader.STAMPDIR,
134
                         "%s.permanent-failure" % hookfile)))
135
        self.assertFalse(os.path.exists(package_data_downloader.NOTIFIER_FILE))
136
137
    def test_stampfile_older_than_hookfile(self):
138
        # hook file newer than stampfile, download, run script, update
139
        # stampfile
140
        hookfile = "older-stampfile"
141
        stampfile = os.path.join(package_data_downloader.STAMPDIR,
142
                                 hookfile)
888 by Brian Murray
releasing package update-notifier version 3.173
143
        with open(stampfile, "w"):
887 by Brian Murray
* data/package-data-downloader:
144
                pass
145
        orig_stamp_date = os.stat(stampfile).st_mtime
146
        time.sleep(0.01)
147
        # create the hook file
148
        self._setup_hook_file(hookfile)
149
        package_data_downloader.process_download_requests()
150
        # the download succeeded so the stamp file is touched
151
        new_stamp_date = os.stat(stampfile).st_mtime
152
        self.assertGreater(new_stamp_date, orig_stamp_date)
153
        # confirm failure files not created
154
        self.assertFalse(os.path.exists(
155
            os.path.join(package_data_downloader.STAMPDIR,
156
                         "%s.permanent-failure" % hookfile)))
157
        self.assertFalse(os.path.exists(package_data_downloader.NOTIFIER_FILE))
158
891 by Brian Murray
* data/package-data-downloader:
159
    def test_only_retry_failure(self):
160
        # two hook files but only has failed
161
        hookfile = "success"
162
        stampfile = os.path.join(package_data_downloader.STAMPDIR,
163
                                 hookfile)
164
        with open(stampfile, "w"):
165
            pass
166
        orig_stamp_date = os.stat(stampfile).st_mtime
167
        time.sleep(0.01)
168
        # create the hook file
169
        self._setup_hook_file(hookfile)
170
        fail_hookfile = "failure"
171
        # the stampfile indicates a failure
172
        fail_stampfile = os.path.join(package_data_downloader.STAMPDIR,
173
                                      "%s.failed" % fail_hookfile)
174
        with open(fail_stampfile, "w"):
175
            pass
176
        time.sleep(0.01)
177
        # create the hook file
178
        self._setup_hook_file(fail_hookfile)
179
        # create an empty notifier file
180
        with open(package_data_downloader.NOTIFIER_FILE, "w"):
181
            pass
182
        package_data_downloader.process_download_requests()
183
        new_stamp_date = os.stat(stampfile).st_mtime
184
        # if the downloaded succeeded it shouldn't be done again
185
        self.assertEqual(new_stamp_date, orig_stamp_date)
186
887 by Brian Murray
* data/package-data-downloader:
187
    def test_hookfile_script_fails(self):
188
        # script in the hook file fails, failure recorded as permanent
189
        hookfile = "script-failure"
190
        stampfile = os.path.join(package_data_downloader.STAMPDIR,
191
                                 hookfile)
888 by Brian Murray
releasing package update-notifier version 3.173
192
        with open(stampfile, "w"):
887 by Brian Murray
* data/package-data-downloader:
193
                pass
194
        time.sleep(0.01)
195
        # create the hook file
196
        self._setup_hook_file(hookfile, "/bin/false")
197
        package_data_downloader.process_download_requests()
198
        # script failures are considered permanent
199
        self.assertTrue(os.path.exists(
200
            os.path.join(package_data_downloader.STAMPDIR,
201
                         "%s.permanent-failure" % hookfile)))
202
203
    def test_stampfile_and_notifierfile(self):
204
        # notifier file removed on successful download
205
        hookfile = "stampfile_and_notifierfile"
206
        stampfile = os.path.join(package_data_downloader.STAMPDIR,
207
                                 hookfile)
888 by Brian Murray
releasing package update-notifier version 3.173
208
        with open(stampfile, "w"):
887 by Brian Murray
* data/package-data-downloader:
209
                pass
210
        time.sleep(0.01)
211
        self._setup_hook_file(hookfile)
212
        # create an empty notifier file
888 by Brian Murray
releasing package update-notifier version 3.173
213
        with open(package_data_downloader.NOTIFIER_FILE, "w"):
887 by Brian Murray
* data/package-data-downloader:
214
            pass
215
        package_data_downloader.process_download_requests()
216
        self.assertFalse(os.path.exists(package_data_downloader.NOTIFIER_FILE))
217
218
    def test_stampfile_notifierfile_and_notifierpermanent(self):
219
        # both notifier files removed on successful download
220
        hookfile = "stampfile_notifierfile_and_notifierpermanentfile"
221
        stampfile = os.path.join(package_data_downloader.STAMPDIR,
222
                                 hookfile)
888 by Brian Murray
releasing package update-notifier version 3.173
223
        with open(stampfile, "w"):
887 by Brian Murray
* data/package-data-downloader:
224
                pass
225
        time.sleep(0.01)
226
        self._setup_hook_file(hookfile)
227
        # create empty notifier files
888 by Brian Murray
releasing package update-notifier version 3.173
228
        with open(package_data_downloader.NOTIFIER_FILE, "w"):
887 by Brian Murray
* data/package-data-downloader:
229
            pass
888 by Brian Murray
releasing package update-notifier version 3.173
230
        with open(package_data_downloader.NOTIFIER_PERMANENT_FILE, "w"):
887 by Brian Murray
* data/package-data-downloader:
231
            pass
232
        package_data_downloader.process_download_requests()
233
        self.assertFalse(os.path.exists(package_data_downloader.NOTIFIER_FILE))
234
        self.assertFalse(os.path.exists(
235
            package_data_downloader.NOTIFIER_PERMANENT_FILE))
236
237
    def test_stampfile_and_notifierpermanent(self):
238
        # permanent notifier file removed on successful download
239
        hookfile = "stampfile_and_permanentnotifierfile"
240
        stampfile = os.path.join(package_data_downloader.STAMPDIR,
241
                                 hookfile)
888 by Brian Murray
releasing package update-notifier version 3.173
242
        with open(stampfile, "w"):
887 by Brian Murray
* data/package-data-downloader:
243
                pass
244
        time.sleep(0.01)
245
        self._setup_hook_file(hookfile)
246
        # create an empty permanent notifier file
888 by Brian Murray
releasing package update-notifier version 3.173
247
        with open(package_data_downloader.NOTIFIER_PERMANENT_FILE, "w"):
887 by Brian Murray
* data/package-data-downloader:
248
            pass
249
        package_data_downloader.process_download_requests()
250
        self.assertFalse(os.path.exists(
251
            package_data_downloader.NOTIFIER_PERMANENT_FILE))
252
253
    def test_hookfile_download_failure(self):
254
        # can't download the file, non-permanent failure created
255
        hookfile = "download-failure"
256
        stampfile = os.path.join(package_data_downloader.STAMPDIR,
257
                                 hookfile)
888 by Brian Murray
releasing package update-notifier version 3.173
258
        with open(stampfile, "w"):
887 by Brian Murray
* data/package-data-downloader:
259
                pass
260
        time.sleep(0.01)
261
        # overwrite canary file to create a failure
262
        self.canary_file = "not-there.txt"
263
        # create the hook file
264
        self._setup_hook_file(hookfile)
265
        package_data_downloader.process_download_requests()
266
        self.assertTrue(os.path.exists(
267
            os.path.join(package_data_downloader.STAMPDIR,
268
                         "%s.failed" % hookfile)))
269
        self.assertFalse(os.path.exists(
270
            os.path.join(package_data_downloader.STAMPDIR,
271
                         "%s.permanent-failure" % hookfile)))
272
891 by Brian Murray
* data/package-data-downloader:
273
    def test_hookfile_notifierfile_download_failure(self):
887 by Brian Murray
* data/package-data-downloader:
274
        # can't download the file, notifier file stays around
275
        hookfile = "download-failure-with-notifierfile"
276
        # overwrite canary file to create a failure
277
        self.canary_file = "not-there.txt"
278
        # create the hook file
279
        self._setup_hook_file(hookfile)
280
        # create an empty notifier file
888 by Brian Murray
releasing package update-notifier version 3.173
281
        with open(package_data_downloader.NOTIFIER_FILE, "w"):
887 by Brian Murray
* data/package-data-downloader:
282
            pass
283
        package_data_downloader.process_download_requests()
284
        # because there was a failure it shouldn't be removed
285
        self.assertTrue(os.path.exists(
286
            package_data_downloader.NOTIFIER_FILE))
287
848.1.1 by Michael Vogt
use apt-helper to download the data file to get the apt proxy settings
288
668 by Michael Vogt
add empty test in preparation for more to come :)
289
class PackageDataDownloaderTestCase(unittest.TestCase):
290
670 by Michael Vogt
add tests before refactoring
291
    def setUp(self):
292
        self.tmpdir = tempfile.mkdtemp()
293
        # stampdir
294
        stampdir = os.path.join(self.tmpdir, "stampdir")
295
        os.makedirs(stampdir)
296
        package_data_downloader.STAMPDIR = stampdir
673 by Michael Vogt
ignore .dpkg-* files in the datadir and add a test for it
297
        # datadir
298
        datadir = os.path.join(self.tmpdir, "datadir")
299
        os.makedirs(datadir)
300
        package_data_downloader.DATADIR = datadir
670 by Michael Vogt
add tests before refactoring
301
302
    def tearDown(self):
303
        shutil.rmtree(self.tmpdir)
304
737 by Steve Langasek
Add a new test for broken translations, and fix the test for the notifier
305
    def test_wrong_template_translations(self):
886 by Brian Murray
resolve failure with test_wrong_template_translations
306
        package_data_downloader.NOTIFIER_SOURCE_FILE = \
888 by Brian Murray
releasing package update-notifier version 3.173
307
            'data/package-data-downloads-failed.in'
886 by Brian Murray
resolve failure with test_wrong_template_translations
308
        package_data_downloader.NOTIFIER_FILE = \
309
            self.tmpdir + "/data-downloads-failed"
310
        package_data_downloader.NOTIFIER_PERMANENT_SOURCE_FILE = \
888 by Brian Murray
releasing package update-notifier version 3.173
311
            'data/package-data-downloads-failed-permanently.in'
886 by Brian Murray
resolve failure with test_wrong_template_translations
312
        package_data_downloader.NOTIFIER_PERMANENT_FILE = \
313
            package_data_downloader.NOTIFIER_FILE + '-permanently'
859 by Steve Langasek
More python3 compat fixes
314
        package_data_downloader.trigger_update_notifier([], False)
315
        package_data_downloader.trigger_update_notifier([], True)
737 by Steve Langasek
Add a new test for broken translations, and fix the test for the notifier
316
670 by Michael Vogt
add tests before refactoring
317
    def test_permanently_failed(self):
672 by Michael Vogt
add tests for create_or_update_stampfile, hook_is_permanently_failed, existing_permanent_failures, create_or_update_stampfile, mark_hook_failed
318
        # create a bunch of files using the provided mechanism
319
        test_files = ["foo.permanent-failure", "bar.failure", "baz"]
320
        for f in test_files:
321
            package_data_downloader.create_or_update_stampfile(
322
                os.path.join(package_data_downloader.STAMPDIR, f))
323
        self.assertEqual(
324
            sorted(os.listdir(package_data_downloader.STAMPDIR)),
325
            sorted(test_files))
670 by Michael Vogt
add tests before refactoring
326
        # test hook_is_permanently_failed()
327
        self.assertTrue(
328
            package_data_downloader.hook_is_permanently_failed("foo"))
329
        self.assertFalse(
330
            package_data_downloader.hook_is_permanently_failed("bar"))
331
        self.assertFalse(
332
            package_data_downloader.hook_is_permanently_failed("baz"))
333
        # existing_permanent_failures()
334
        self.assertEqual(
335
            package_data_downloader.existing_permanent_failures(),
336
            ["foo"])
668 by Michael Vogt
add empty test in preparation for more to come :)
337
887 by Brian Murray
* data/package-data-downloader:
338
    def test_hook_aged_out(self):
339
        # test to see if a hook has failed for more than 3 days
340
        # create a failure file
341
        with open(os.path.join(package_data_downloader.STAMPDIR,
342
                               "aged.failed"), "w"):
343
            pass
344
        from datetime import datetime, timedelta
345
        # patch datetime so we think its the future
346
        with patch('package_data_downloader.datetime') as mock_datetime:
347
            thefuture = (datetime.now() + timedelta(days=3))
348
            mock_datetime.now.return_value = thefuture
349
            mock_datetime.fromtimestamp.side_effect = \
350
                lambda *args, **kw: datetime.fromtimestamp(*args, **kw)
351
            self.assertEqual(package_data_downloader.datetime.now(),
352
                             thefuture)
353
            self.assertTrue(package_data_downloader.hook_aged_out("aged"))
354
672 by Michael Vogt
add tests for create_or_update_stampfile, hook_is_permanently_failed, existing_permanent_failures, create_or_update_stampfile, mark_hook_failed
355
    def test_mark_hook_failed(self):
356
        # prepare
357
        package_data_downloader.create_or_update_stampfile(
358
            os.path.join(package_data_downloader.STAMPDIR, "foo"))
359
        # temp failure
360
        package_data_downloader.mark_hook_failed("foo")
361
        self.assertEqual(os.listdir(package_data_downloader.STAMPDIR),
362
                         ["foo.failed"])
363
        self.assertFalse(
364
            package_data_downloader.hook_is_permanently_failed("foo"))
365
        # permanent
366
        package_data_downloader.mark_hook_failed("foo", permanent=True)
367
        self.assertEqual(os.listdir(package_data_downloader.STAMPDIR),
368
                         ["foo.permanent-failure"])
369
        self.assertTrue(
370
            package_data_downloader.hook_is_permanently_failed("foo"))
887 by Brian Murray
* data/package-data-downloader:
371
674 by Michael Vogt
add basic test for trigger_update_notifier()
372
    def test_get_hook_file_names(self):
673 by Michael Vogt
ignore .dpkg-* files in the datadir and add a test for it
373
        # test that .dpkg-* is ignored
374
        test_datadir_files = ["foo.dpkg-new", "bar"]
375
        for name in test_datadir_files:
887 by Brian Murray
* data/package-data-downloader:
376
            with open(os.path.join(package_data_downloader.DATADIR, name),
377
                      "w"):
673 by Michael Vogt
ignore .dpkg-* files in the datadir and add a test for it
378
                pass
379
        self.assertEqual(package_data_downloader.get_hook_file_names(),
380
                         ["bar"])
674 by Michael Vogt
add basic test for trigger_update_notifier()
381
382
    def test_trigger_update_notifier(self):
383
        # write to tmpdir
384
        package_data_downloader.NOTIFIER_FILE = os.path.join(
385
            self.tmpdir, "data-downloads-failed")
386
        # point to local repo file
387
        package_data_downloader.NOTIFIER_SOURCE_FILE = \
888 by Brian Murray
releasing package update-notifier version 3.173
388
            "data/package-data-downloads-failed.in"
674 by Michael Vogt
add basic test for trigger_update_notifier()
389
        package_data_downloader.trigger_update_notifier(
390
            failures=["foo", "bar"], permanent=False)
391
        data = open(package_data_downloader.NOTIFIER_FILE).read()
737 by Steve Langasek
Add a new test for broken translations, and fix the test for the notifier
392
        self.assertEqual(data, """Priority: High
393
_Name: Failure to download extra data files
675 by Michael Vogt
tiny refactor of trigger_update_notifier() but not at all important (plus test update to cover the full note
394
Terminal: True
737 by Steve Langasek
Add a new test for broken translations, and fix the test for the notifier
395
Command: pkexec /usr/lib/update-notifier/package-data-downloader
887 by Brian Murray
* data/package-data-downloader:
396
_Description:
675 by Michael Vogt
tiny refactor of trigger_update_notifier() but not at all important (plus test update to cover the full note
397
 The following packages requested additional data downloads after package
398
 installation, but the data could not be downloaded or could not be
399
 processed.
400
 .
401
 .
402
   foo, bar
403
 .
404
 .
405
 The download will be attempted again later, or you can try the download
406
 again now.  Running this command requires an active Internet connection.
407
""")
674 by Michael Vogt
add basic test for trigger_update_notifier()
408
672 by Michael Vogt
add tests for create_or_update_stampfile, hook_is_permanently_failed, existing_permanent_failures, create_or_update_stampfile, mark_hook_failed
409
668 by Michael Vogt
add empty test in preparation for more to come :)
410
if __name__ == "__main__":
411
    unittest.main()