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

« back to all changes in this revision

Viewing changes to tests/test_package-data-downloader.py

  • Committer: Balint Reczey
  • Date: 2020-06-11 18:46:02 UTC
  • Revision ID: balint.reczey@canonical.com-20200611184602-2rv1zan3xu723x2u
Moved to git at https://git.launchpad.net/update-notifier

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python3
2
 
 
3
 
import hashlib
4
 
import http.server as SimpleHTTPServer
5
 
import os
6
 
import random
7
 
import shutil
8
 
import socketserver
9
 
import sys
10
 
import time
11
 
import tempfile
12
 
import unittest
13
 
 
14
 
from mock import patch
15
 
from multiprocessing import Process
16
 
 
17
 
sys.path.insert(0, "data")
18
 
sys.path.insert(1, "../data")
19
 
import package_data_downloader  # nopep8
20
 
 
21
 
 
22
 
class TestHttpd(Process):
23
 
    def __init__(self, port, servdir):
24
 
        super(TestHttpd, self).__init__()
25
 
        self.port = port
26
 
        self.servdir = servdir
27
 
 
28
 
    def run(self):
29
 
        os.chdir(self.servdir)
30
 
        Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
31
 
        httpd = socketserver.TCPServer(("localhost", self.port), Handler)
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()
43
 
 
44
 
    def tearDown(self):
45
 
        self.p.terminate()
46
 
        shutil.rmtree(self.tempdir)
47
 
 
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)
53
 
        self.canary_hashsum = hashlib.sha256(
54
 
            self.canary_text.encode('utf-8')).hexdigest()
55
 
 
56
 
    def test_download_file(self):
57
 
        package_data_downloader.STAMPDIR = self.tempdir
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(
62
 
            uri, self.canary_hashsum)
63
 
        self.assertNotEqual(destfile, None)
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)
125
 
        with open(stampfile, "w"):
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)
143
 
        with open(stampfile, "w"):
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
 
 
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
 
 
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)
192
 
        with open(stampfile, "w"):
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)
208
 
        with open(stampfile, "w"):
209
 
            pass
210
 
        time.sleep(0.01)
211
 
        self._setup_hook_file(hookfile)
212
 
        # create an empty notifier file
213
 
        with open(package_data_downloader.NOTIFIER_FILE, "w"):
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)
223
 
        with open(stampfile, "w"):
224
 
            pass
225
 
        time.sleep(0.01)
226
 
        self._setup_hook_file(hookfile)
227
 
        # create empty notifier files
228
 
        with open(package_data_downloader.NOTIFIER_FILE, "w"):
229
 
            pass
230
 
        with open(package_data_downloader.NOTIFIER_PERMANENT_FILE, "w"):
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)
242
 
        with open(stampfile, "w"):
243
 
            pass
244
 
        time.sleep(0.01)
245
 
        self._setup_hook_file(hookfile)
246
 
        # create an empty permanent notifier file
247
 
        with open(package_data_downloader.NOTIFIER_PERMANENT_FILE, "w"):
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)
258
 
        with open(stampfile, "w"):
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
 
 
273
 
    def test_hookfile_notifierfile_download_failure(self):
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
281
 
        with open(package_data_downloader.NOTIFIER_FILE, "w"):
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
 
 
288
 
 
289
 
class PackageDataDownloaderTestCase(unittest.TestCase):
290
 
 
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
297
 
        # datadir
298
 
        datadir = os.path.join(self.tmpdir, "datadir")
299
 
        os.makedirs(datadir)
300
 
        package_data_downloader.DATADIR = datadir
301
 
 
302
 
    def tearDown(self):
303
 
        shutil.rmtree(self.tmpdir)
304
 
 
305
 
    def test_wrong_template_translations(self):
306
 
        package_data_downloader.NOTIFIER_SOURCE_FILE = \
307
 
            'data/package-data-downloads-failed.in'
308
 
        package_data_downloader.NOTIFIER_FILE = \
309
 
            self.tmpdir + "/data-downloads-failed"
310
 
        package_data_downloader.NOTIFIER_PERMANENT_SOURCE_FILE = \
311
 
            'data/package-data-downloads-failed-permanently.in'
312
 
        package_data_downloader.NOTIFIER_PERMANENT_FILE = \
313
 
            package_data_downloader.NOTIFIER_FILE + '-permanently'
314
 
        package_data_downloader.trigger_update_notifier([], False)
315
 
        package_data_downloader.trigger_update_notifier([], True)
316
 
 
317
 
    def test_permanently_failed(self):
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))
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"])
337
 
 
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
 
 
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"))
371
 
 
372
 
    def test_get_hook_file_names(self):
373
 
        # test that .dpkg-* is ignored
374
 
        test_datadir_files = ["foo.dpkg-new", "bar"]
375
 
        for name in test_datadir_files:
376
 
            with open(os.path.join(package_data_downloader.DATADIR, name),
377
 
                      "w"):
378
 
                pass
379
 
        self.assertEqual(package_data_downloader.get_hook_file_names(),
380
 
                         ["bar"])
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 = \
388
 
            "data/package-data-downloads-failed.in"
389
 
        package_data_downloader.trigger_update_notifier(
390
 
            failures=["foo", "bar"], permanent=False)
391
 
        data = open(package_data_downloader.NOTIFIER_FILE).read()
392
 
        self.assertEqual(data, """Priority: High
393
 
_Name: Failure to download extra data files
394
 
Terminal: True
395
 
Command: pkexec /usr/lib/update-notifier/package-data-downloader
396
 
_Description:
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
 
""")
408
 
 
409
 
 
410
 
if __name__ == "__main__":
411
 
    unittest.main()