Merge lp:~michael.nelson/click-reviewers-tools/add-test-without-mocks into lp:click-reviewers-tools

Proposed by Michael Nelson on 2015-10-15
Status: Merged
Merged at revision: 534
Proposed branch: lp:~michael.nelson/click-reviewers-tools/add-test-without-mocks
Merge into: lp:click-reviewers-tools
Diff against target: 776 lines (+356/-212)
17 files modified
clickreviews/cr_common.py (+2/-0)
clickreviews/cr_tests.py (+128/-147)
clickreviews/tests/test_aaa_example_cr_skeleton.py (+0/-5)
clickreviews/tests/test_cr_bin_path.py (+0/-5)
clickreviews/tests/test_cr_common.py (+1/-4)
clickreviews/tests/test_cr_content_hub.py (+0/-5)
clickreviews/tests/test_cr_desktop.py (+0/-5)
clickreviews/tests/test_cr_framework.py (+0/-5)
clickreviews/tests/test_cr_lint.py (+34/-5)
clickreviews/tests/test_cr_online_accounts.py (+0/-5)
clickreviews/tests/test_cr_push_helper.py (+0/-5)
clickreviews/tests/test_cr_scope.py (+0/-5)
clickreviews/tests/test_cr_security.py (+1/-4)
clickreviews/tests/test_cr_systemd.py (+0/-5)
clickreviews/tests/test_cr_url_dispatcher.py (+0/-5)
clickreviews/tests/test_modules.py (+1/-2)
clickreviews/tests/utils.py (+189/-0)
To merge this branch: bzr merge lp:~michael.nelson/click-reviewers-tools/add-test-without-mocks
Reviewer Review Type Date Requested Status
Jamie Strandboge 2015-10-15 Approve on 2015-10-21
Michael Vogt (community) Approve on 2015-10-20
Review via email: mp+274509@code.launchpad.net

Commit Message

Add non-mocked test example.

Description of the Change

The purpose of this branch is to provide an example test for a click check which doesn't rely on any mocks. As someone unfamiliar with the code, it's quite difficult to follow everything that is currently mocked for each test, and I think it's much clearer and safer to test without mocks wherever possible.

After adding the initial test, I found it passed when run on it's own, but that there were test isolation issues causing failures when running all tests, related to the (currently) global vars UNPACK_DIR and RAW_UNPACK_DIR.

Note: I'm not using 'click build' to build the package because it will in fact fix issues that we're setting up for tests. Here's an example of the new test *failing* when 'click build' is used, because click build fixes the malicious setup, filtering out the .click directory:

http://paste.ubuntu.com/12796183/

Perhaps there's a way to get click build to not run any checks? Not sure.

Details of changes (in order of diff):
 1) Ensure UNPACK_DIR and RAW_UNPACK_DIR are reset to None when cleanup_unpack() is called (related to test-isolation, see comments)
 2) Moved the creation of all current mock patches from the module level to a function, create_patches, which can be called during setUp (avoids the cyclic import issue mentioned in the deleted comments)
 3) Refactored so that the code for starting and stopping the patches is in the one place - the setUp of the base test case.
 4) Added a new test case without any mocks with a single test.
 5) Added a tests/utils.py with a make_package function for creating potentially invalid packages. The make_package utility is taken from a script we use in myapps. It will need more work.

To post a comment you must log in.
Colin Watson (cjwatson) wrote :

"click build" should not gain options to remove this filtering, no; they have no legitimate use outside of exercising pathological cases. This is a case where it's perfectly reasonable to use dpkg-deb in your test data construction.

Michael Vogt (mvo) wrote :

I really like the new tests/utils.py

review: Approve
Jamie Strandboge (jdstrand) wrote :

Thank you for this MP. I never really liked how the mocking got so complicated and this should provide a way to move away from that as we move forward. Code looks good. Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'clickreviews/cr_common.py'
2--- clickreviews/cr_common.py 2015-09-28 21:03:58 +0000
3+++ clickreviews/cr_common.py 2015-10-16 05:56:07 +0000
4@@ -42,9 +42,11 @@
5 global UNPACK_DIR
6 if UNPACK_DIR is not None and os.path.isdir(UNPACK_DIR):
7 recursive_rm(UNPACK_DIR)
8+ UNPACK_DIR = None
9 global RAW_UNPACK_DIR
10 if RAW_UNPACK_DIR is not None and os.path.isdir(RAW_UNPACK_DIR):
11 recursive_rm(RAW_UNPACK_DIR)
12+ RAW_UNPACK_DIR = None
13 atexit.register(cleanup_unpack)
14
15
16
17=== modified file 'clickreviews/cr_tests.py'
18--- clickreviews/cr_tests.py 2015-09-28 21:03:58 +0000
19+++ clickreviews/cr_tests.py 2015-10-16 05:56:07 +0000
20@@ -244,151 +244,131 @@
21 return True
22
23
24-# http://docs.python.org/3.4/library/unittest.mock-examples.html
25-# Mock patching. Don't use decorators but instead patch in setUp() of the
26-# child. Set up a list of patches, but don't start them. Create the helper
27-# method mock_patch() to start all the patches. The child can do this in a
28-# setUp() like so:
29-# import clickreviews.cr_tests as cr_tests
30-# class TestClickReviewFoo(cr_tests.TestClickReview):
31-# def setUp(self):
32-# # Monkey patch various file access classes. stop() is handled with
33-# # addCleanup in super()
34-# cr_tests.mock_patch()
35-# super()
36-patches = []
37-patches.append(patch('clickreviews.cr_common.ClickReview._check_path_exists',
38- _mock_func))
39-patches.append(patch(
40- 'clickreviews.cr_common.ClickReview._extract_control_file',
41- _extract_control_file))
42-patches.append(patch(
43- 'clickreviews.cr_common.ClickReview._extract_manifest_file',
44- _extract_manifest_file))
45-patches.append(patch(
46- 'clickreviews.cr_common.ClickReview._extract_package_yaml',
47- _extract_package_yaml))
48-patches.append(patch(
49- 'clickreviews.cr_common.ClickReview._extract_hashes_yaml',
50- _extract_hashes_yaml))
51-patches.append(patch(
52- 'clickreviews.cr_common.ClickReview._path_join',
53- _path_join))
54-patches.append(patch(
55- 'clickreviews.cr_common.ClickReview._get_sha512sum',
56- _get_sha512sum))
57-patches.append(patch(
58- 'clickreviews.cr_common.ClickReview._extract_statinfo',
59- _extract_statinfo))
60-patches.append(patch(
61- 'clickreviews.cr_common.ClickReview._extract_click_frameworks',
62- _extract_click_frameworks))
63-patches.append(patch('clickreviews.cr_common.unpack_click', _mock_func))
64-patches.append(patch('clickreviews.cr_common.raw_unpack_pkg', _mock_func))
65-patches.append(patch('clickreviews.cr_common.ClickReview._list_all_files',
66- _mock_func))
67-patches.append(patch(
68- 'clickreviews.cr_common.ClickReview._list_all_compiled_binaries',
69- _mock_func))
70-
71-# lint overrides
72-patches.append(patch(
73- 'clickreviews.cr_lint.ClickReviewLint._list_control_files',
74- _mock_func))
75-patches.append(patch('clickreviews.cr_lint.ClickReviewLint._list_all_files',
76- _mock_func))
77-patches.append(patch(
78- 'clickreviews.cr_lint.ClickReview._list_all_compiled_binaries',
79- _mock_func))
80-patches.append(patch(
81- 'clickreviews.cr_lint.ClickReviewLint._extract_readme_md',
82- _extract_readme_md))
83-patches.append(patch(
84- 'clickreviews.cr_lint.ClickReviewLint._check_innerpath_executable',
85- _check_innerpath_executable))
86-
87-# security overrides
88-patches.append(patch(
89- 'clickreviews.cr_security.ClickReviewSecurity._extract_security_manifest',
90- _extract_security_manifest))
91-patches.append(patch(
92- 'clickreviews.cr_security.ClickReviewSecurity._get_security_manifest',
93- _get_security_manifest))
94-patches.append(patch(
95- 'clickreviews.cr_security.ClickReviewSecurity._extract_security_profile',
96- _extract_security_profile))
97-patches.append(patch(
98- 'clickreviews.cr_security.ClickReviewSecurity._get_security_profile',
99- _get_security_profile))
100-
101-# desktop overrides
102-patches.append(patch(
103- 'clickreviews.cr_desktop.ClickReviewDesktop._extract_desktop_entry',
104- _extract_desktop_entry))
105-patches.append(patch(
106- 'clickreviews.cr_desktop.ClickReviewDesktop._get_desktop_entry',
107- _get_desktop_entry))
108-patches.append(patch(
109- 'clickreviews.cr_desktop.ClickReviewDesktop._extract_webapp_manifests',
110- _extract_webapp_manifests))
111-
112-# url-dispatcher overrides
113-patches.append(patch(
114- 'clickreviews.cr_url_dispatcher.ClickReviewUrlDispatcher._extract_url_dispatcher',
115- _extract_url_dispatcher))
116-
117-# scope overrides
118-patches.append(patch(
119- 'clickreviews.cr_scope.ClickReviewScope._extract_scopes',
120- _extract_scopes))
121-
122-# content-hub overrides
123-patches.append(patch(
124- 'clickreviews.cr_content_hub.ClickReviewContentHub._extract_content_hub',
125- _extract_content_hub))
126-
127-# online accounts overrides
128-patches.append(patch(
129- 'clickreviews.cr_online_accounts.ClickReviewAccounts._extract_account',
130- _extract_account))
131-
132-# push-helper overrides
133-patches.append(patch(
134- 'clickreviews.cr_push_helper.ClickReviewPushHelper._extract_push_helper',
135- _extract_push_helper))
136-
137-# bin-path overrides
138-patches.append(patch(
139- 'clickreviews.cr_bin_path.ClickReviewBinPath._extract_bin_path',
140- _extract_bin_path))
141-patches.append(patch(
142- 'clickreviews.cr_bin_path.ClickReviewBinPath._check_bin_path_executable',
143- _check_bin_path_executable))
144-
145-# framework overrides
146-patches.append(patch(
147- 'clickreviews.cr_framework.ClickReviewFramework._extract_framework',
148- _extract_framework))
149-patches.append(patch(
150- 'clickreviews.cr_framework.ClickReviewFramework._extract_framework_policy',
151- _extract_framework_policy))
152-patches.append(patch(
153- 'clickreviews.cr_framework.ClickReviewFramework._has_framework_in_metadir',
154- _has_framework_in_metadir))
155-
156-
157-def mock_patch():
158- '''Call in setup of child'''
159- global patches
160- for p in patches:
161- try:
162- p.start()
163- except ImportError:
164- # This is only needed because we are importing ClickReviewLint
165- # in the security tests and ClickReviewSecurity in the lint tests.
166- # If we move those patches outside of this file, then we can
167- # remove this.
168- pass
169+def create_patches():
170+ # http://docs.python.org/3.4/library/unittest.mock-examples.html
171+ # Mock patching. Don't use decorators but instead patch in setUp() of the
172+ # child.
173+ patches = []
174+ patches.append(patch('clickreviews.cr_common.ClickReview._check_path_exists',
175+ _mock_func))
176+ patches.append(patch(
177+ 'clickreviews.cr_common.ClickReview._extract_control_file',
178+ _extract_control_file))
179+ patches.append(patch(
180+ 'clickreviews.cr_common.ClickReview._extract_manifest_file',
181+ _extract_manifest_file))
182+ patches.append(patch(
183+ 'clickreviews.cr_common.ClickReview._extract_package_yaml',
184+ _extract_package_yaml))
185+ patches.append(patch(
186+ 'clickreviews.cr_common.ClickReview._extract_hashes_yaml',
187+ _extract_hashes_yaml))
188+ patches.append(patch(
189+ 'clickreviews.cr_common.ClickReview._path_join',
190+ _path_join))
191+ patches.append(patch(
192+ 'clickreviews.cr_common.ClickReview._get_sha512sum',
193+ _get_sha512sum))
194+ patches.append(patch(
195+ 'clickreviews.cr_common.ClickReview._extract_statinfo',
196+ _extract_statinfo))
197+ patches.append(patch(
198+ 'clickreviews.cr_common.ClickReview._extract_click_frameworks',
199+ _extract_click_frameworks))
200+ patches.append(patch('clickreviews.cr_common.unpack_click', _mock_func))
201+ patches.append(patch('clickreviews.cr_common.raw_unpack_pkg', _mock_func))
202+ patches.append(patch('clickreviews.cr_common.ClickReview._list_all_files',
203+ _mock_func))
204+ patches.append(patch(
205+ 'clickreviews.cr_common.ClickReview._list_all_compiled_binaries',
206+ _mock_func))
207+
208+ # lint overrides
209+ patches.append(patch(
210+ 'clickreviews.cr_lint.ClickReviewLint._list_control_files',
211+ _mock_func))
212+ patches.append(patch('clickreviews.cr_lint.ClickReviewLint._list_all_files',
213+ _mock_func))
214+ patches.append(patch(
215+ 'clickreviews.cr_lint.ClickReview._list_all_compiled_binaries',
216+ _mock_func))
217+ patches.append(patch(
218+ 'clickreviews.cr_lint.ClickReviewLint._extract_readme_md',
219+ _extract_readme_md))
220+ patches.append(patch(
221+ 'clickreviews.cr_lint.ClickReviewLint._check_innerpath_executable',
222+ _check_innerpath_executable))
223+
224+ # security overrides
225+ patches.append(patch(
226+ 'clickreviews.cr_security.ClickReviewSecurity._extract_security_manifest',
227+ _extract_security_manifest))
228+ patches.append(patch(
229+ 'clickreviews.cr_security.ClickReviewSecurity._get_security_manifest',
230+ _get_security_manifest))
231+ patches.append(patch(
232+ 'clickreviews.cr_security.ClickReviewSecurity._extract_security_profile',
233+ _extract_security_profile))
234+ patches.append(patch(
235+ 'clickreviews.cr_security.ClickReviewSecurity._get_security_profile',
236+ _get_security_profile))
237+
238+ # desktop overrides
239+ patches.append(patch(
240+ 'clickreviews.cr_desktop.ClickReviewDesktop._extract_desktop_entry',
241+ _extract_desktop_entry))
242+ patches.append(patch(
243+ 'clickreviews.cr_desktop.ClickReviewDesktop._get_desktop_entry',
244+ _get_desktop_entry))
245+ patches.append(patch(
246+ 'clickreviews.cr_desktop.ClickReviewDesktop._extract_webapp_manifests',
247+ _extract_webapp_manifests))
248+
249+ # url-dispatcher overrides
250+ patches.append(patch(
251+ 'clickreviews.cr_url_dispatcher.ClickReviewUrlDispatcher._extract_url_dispatcher',
252+ _extract_url_dispatcher))
253+
254+ # scope overrides
255+ patches.append(patch(
256+ 'clickreviews.cr_scope.ClickReviewScope._extract_scopes',
257+ _extract_scopes))
258+
259+ # content-hub overrides
260+ patches.append(patch(
261+ 'clickreviews.cr_content_hub.ClickReviewContentHub._extract_content_hub',
262+ _extract_content_hub))
263+
264+ # online accounts overrides
265+ patches.append(patch(
266+ 'clickreviews.cr_online_accounts.ClickReviewAccounts._extract_account',
267+ _extract_account))
268+
269+ # push-helper overrides
270+ patches.append(patch(
271+ 'clickreviews.cr_push_helper.ClickReviewPushHelper._extract_push_helper',
272+ _extract_push_helper))
273+
274+ # bin-path overrides
275+ patches.append(patch(
276+ 'clickreviews.cr_bin_path.ClickReviewBinPath._extract_bin_path',
277+ _extract_bin_path))
278+ patches.append(patch(
279+ 'clickreviews.cr_bin_path.ClickReviewBinPath._check_bin_path_executable',
280+ _check_bin_path_executable))
281+
282+ # framework overrides
283+ patches.append(patch(
284+ 'clickreviews.cr_framework.ClickReviewFramework._extract_framework',
285+ _extract_framework))
286+ patches.append(patch(
287+ 'clickreviews.cr_framework.ClickReviewFramework._extract_framework_policy',
288+ _extract_framework_policy))
289+ patches.append(patch(
290+ 'clickreviews.cr_framework.ClickReviewFramework._has_framework_in_metadir',
291+ _has_framework_in_metadir))
292+
293+ return patches
294
295
296 class TestClickReview(TestCase):
297@@ -1107,9 +1087,10 @@
298
299 def setUp(self):
300 '''Make sure our patches are applied everywhere'''
301- global patches
302+ patches = create_patches()
303 for p in patches:
304- self.addCleanup(p.stop())
305+ p.start()
306+ self.addCleanup(p.stop)
307
308 def tearDown(self):
309 '''Make sure we reset everything to known good values'''
310
311=== modified file 'clickreviews/tests/test_aaa_example_cr_skeleton.py'
312--- clickreviews/tests/test_aaa_example_cr_skeleton.py 2015-08-18 16:05:06 +0000
313+++ clickreviews/tests/test_aaa_example_cr_skeleton.py 2015-10-16 05:56:07 +0000
314@@ -20,11 +20,6 @@
315
316 class TestClickReviewSkeleton(cr_tests.TestClickReview):
317 """Tests for the lint review tool."""
318- def setUp(self):
319- # Monkey patch various file access classes. stop() is handled with
320- # addCleanup in super()
321- cr_tests.mock_patch()
322- super()
323
324 def test_check_foo(self):
325 '''Test check_foo()'''
326
327=== modified file 'clickreviews/tests/test_cr_bin_path.py'
328--- clickreviews/tests/test_cr_bin_path.py 2015-07-07 20:02:36 +0000
329+++ clickreviews/tests/test_cr_bin_path.py 2015-10-16 05:56:07 +0000
330@@ -20,11 +20,6 @@
331
332 class TestClickReviewBinPath(cr_tests.TestClickReview):
333 """Tests for the lint review tool."""
334- def setUp(self):
335- # Monkey patch various file access classes. stop() is handled with
336- # addCleanup in super()
337- cr_tests.mock_patch()
338- super()
339
340 def _set_binary(self, entries, name=None):
341 d = dict()
342
343=== modified file 'clickreviews/tests/test_cr_common.py'
344--- clickreviews/tests/test_cr_common.py 2015-08-18 16:05:06 +0000
345+++ clickreviews/tests/test_cr_common.py 2015-10-16 05:56:07 +0000
346@@ -5,10 +5,7 @@
347 class ClickReviewTestCase(cr_tests.TestClickReview):
348
349 def setUp(self):
350- # Monkey patch various file access classes. stop() is handled with
351- # addCleanup in super()
352- cr_tests.mock_patch()
353- super()
354+ super().setUp()
355 self.review = ClickReview('app.click', 'review_type')
356
357 def test_add_result_default_manual_review(self):
358
359=== modified file 'clickreviews/tests/test_cr_content_hub.py'
360--- clickreviews/tests/test_cr_content_hub.py 2014-11-21 18:23:51 +0000
361+++ clickreviews/tests/test_cr_content_hub.py 2015-10-16 05:56:07 +0000
362@@ -20,11 +20,6 @@
363
364 class TestClickReviewContentHub(cr_tests.TestClickReview):
365 """Tests for the lint review tool."""
366- def setUp(self):
367- # Monkey patch various file access classes. stop() is handled with
368- # addCleanup in super()
369- cr_tests.mock_patch()
370- super()
371
372 def test_check_unknown_keys_none(self):
373 '''Test check_unknown() - no unknown'''
374
375=== modified file 'clickreviews/tests/test_cr_desktop.py'
376--- clickreviews/tests/test_cr_desktop.py 2015-08-18 16:05:06 +0000
377+++ clickreviews/tests/test_cr_desktop.py 2015-10-16 05:56:07 +0000
378@@ -20,11 +20,6 @@
379
380 class TestClickReviewDesktop(cr_tests.TestClickReview):
381 """Tests for the desktop review tool."""
382- def setUp(self):
383- # Monkey patch various file access classes. stop() is handled with
384- # addCleanup in super()
385- cr_tests.mock_patch()
386- super()
387
388 def test_check_desktop_file(self):
389 '''Test check_desktop_file()'''
390
391=== modified file 'clickreviews/tests/test_cr_framework.py'
392--- clickreviews/tests/test_cr_framework.py 2015-04-10 12:59:40 +0000
393+++ clickreviews/tests/test_cr_framework.py 2015-10-16 05:56:07 +0000
394@@ -20,11 +20,6 @@
395
396 class TestClickReviewFramework(cr_tests.TestClickReview):
397 """Tests for the lint review tool."""
398- def setUp(self):
399- # Monkey patch various file access classes. stop() is handled with
400- # addCleanup in super()
401- cr_tests.mock_patch()
402- super()
403
404 def test_framework_hook_obsolete(self):
405 '''Test check_framework_hook_obsolete()'''
406
407=== modified file 'clickreviews/tests/test_cr_lint.py'
408--- clickreviews/tests/test_cr_lint.py 2015-10-01 13:05:13 +0000
409+++ clickreviews/tests/test_cr_lint.py 2015-10-16 05:56:07 +0000
410@@ -14,24 +14,24 @@
411 # You should have received a copy of the GNU General Public License
412 # along with this program. If not, see <http://www.gnu.org/licenses/>.
413
414+from unittest import TestCase
415 from unittest.mock import patch
416
417+from clickreviews.cr_common import cleanup_unpack
418 from clickreviews.cr_lint import ClickReviewLint
419 from clickreviews.cr_lint import MINIMUM_CLICK_FRAMEWORK_VERSION
420 from clickreviews.frameworks import FRAMEWORKS_DATA_URL, USER_DATA_FILE
421+from clickreviews.tests import utils
422 import clickreviews.cr_tests as cr_tests
423
424 import os
425+import shutil
426 import stat
427+import tempfile
428
429
430 class TestClickReviewLint(cr_tests.TestClickReview):
431 """Tests for the lint review tool."""
432- def setUp(self):
433- # Monkey patch various file access classes. stop() is handled with
434- # addCleanup in super()
435- cr_tests.mock_patch()
436- super()
437
438 def _create_hashes_yaml(self):
439 # find cr_tests.py since that is what _get_statinfo() is mocked to
440@@ -1863,3 +1863,32 @@
441 r = c.click_report
442 expected_counts = {'info': None, 'warn': 0, 'error': 1}
443 self.check_results(r, expected_counts)
444+
445+
446+class ClickReviewLintTestCase(TestCase):
447+ """Tests without mocks where they are not needed."""
448+ def setUp(self):
449+ # XXX cleanup_unpack() is required because global variables
450+ # UNPACK_DIR, RAW_UNPACK_DIR are initialised to None at module
451+ # load time, but updated when a real (non-Mock) test runs, such as
452+ # here. While, at the same time, two of the existing tests using
453+ # mocks depend on both global vars being None. Ideally, those
454+ # global vars should be refactored away.
455+ self.addCleanup(cleanup_unpack)
456+ super().setUp()
457+
458+ def mkdtemp(self):
459+ """Create a temp dir which is cleaned up after test."""
460+ tmp_dir = tempfile.mkdtemp()
461+ self.addCleanup(shutil.rmtree, tmp_dir)
462+ return tmp_dir
463+
464+ def test_check_dot_click_root(self):
465+ package = utils.make_package(extra_files=['.click/'],
466+ output_dir=self.mkdtemp())
467+ c = ClickReviewLint(package)
468+
469+ c.check_dot_click()
470+
471+ errors = list(c.click_report['error'].keys())
472+ self.assertEqual(errors, ['lint:dot_click'])
473
474=== modified file 'clickreviews/tests/test_cr_online_accounts.py'
475--- clickreviews/tests/test_cr_online_accounts.py 2015-09-10 08:32:34 +0000
476+++ clickreviews/tests/test_cr_online_accounts.py 2015-10-16 05:56:07 +0000
477@@ -22,11 +22,6 @@
478
479 class TestClickReviewAccounts(cr_tests.TestClickReview):
480 """Tests for the lint review tool."""
481- def setUp(self):
482- # Monkey patch various file access classes. stop() is handled with
483- # addCleanup in super()
484- cr_tests.mock_patch()
485- super()
486
487 def _stub_application(self, root=None, id=None, do_subtree=True):
488 '''Stub application xml'''
489
490=== modified file 'clickreviews/tests/test_cr_push_helper.py'
491--- clickreviews/tests/test_cr_push_helper.py 2014-11-21 18:47:07 +0000
492+++ clickreviews/tests/test_cr_push_helper.py 2015-10-16 05:56:07 +0000
493@@ -20,11 +20,6 @@
494
495 class TestClickReviewPushHelper(cr_tests.TestClickReview):
496 """Tests for the lint review tool."""
497- def setUp(self):
498- # Monkey patch various file access classes. stop() is handled with
499- # addCleanup in super()
500- cr_tests.mock_patch()
501- super()
502
503 def test_check_unknown_keys_none(self):
504 '''Test check_unknown() - no unknown'''
505
506=== modified file 'clickreviews/tests/test_cr_scope.py'
507--- clickreviews/tests/test_cr_scope.py 2015-01-20 13:04:26 +0000
508+++ clickreviews/tests/test_cr_scope.py 2015-10-16 05:56:07 +0000
509@@ -21,11 +21,6 @@
510
511 class TestClickReviewScope(cr_tests.TestClickReview):
512 """Tests for the lint review tool."""
513- def setUp(self):
514- # Monkey patch various file access classes. stop() is handled with
515- # addCleanup in super()
516- cr_tests.mock_patch()
517- super()
518
519 def _create_scope(self, config_dict=None):
520 '''Create a scope to pass to tests'''
521
522=== modified file 'clickreviews/tests/test_cr_security.py'
523--- clickreviews/tests/test_cr_security.py 2015-10-13 21:35:38 +0000
524+++ clickreviews/tests/test_cr_security.py 2015-10-16 05:56:07 +0000
525@@ -24,10 +24,7 @@
526 class TestClickReviewSecurity(cr_tests.TestClickReview):
527 """Tests for the security lint review tool."""
528 def setUp(self):
529- # Monkey patch various file access classes. stop() is handled with
530- # addCleanup in super()
531- cr_tests.mock_patch()
532- super()
533+ super().setUp()
534
535 self.default_security_json = "%s.apparmor" % \
536 self.default_appname
537
538=== modified file 'clickreviews/tests/test_cr_systemd.py'
539--- clickreviews/tests/test_cr_systemd.py 2015-10-13 18:58:02 +0000
540+++ clickreviews/tests/test_cr_systemd.py 2015-10-16 05:56:07 +0000
541@@ -20,11 +20,6 @@
542
543 class TestClickReviewSystemd(cr_tests.TestClickReview):
544 """Tests for the lint review tool."""
545- def setUp(self):
546- # Monkey patch various file access classes. stop() is handled with
547- # addCleanup in super()
548- cr_tests.mock_patch()
549- super()
550
551 def _create_ports(self):
552 ports = {'internal': {'int1': {"port": '8081/tcp', "negotiable": True}},
553
554=== modified file 'clickreviews/tests/test_cr_url_dispatcher.py'
555--- clickreviews/tests/test_cr_url_dispatcher.py 2015-04-10 10:36:49 +0000
556+++ clickreviews/tests/test_cr_url_dispatcher.py 2015-10-16 05:56:07 +0000
557@@ -20,11 +20,6 @@
558
559 class TestClickReviewUrlDispatcher(cr_tests.TestClickReview):
560 """Tests for the lint review tool."""
561- def setUp(self):
562- # Monkey patch various file access classes. stop() is handled with
563- # addCleanup in super()
564- cr_tests.mock_patch()
565- super()
566
567 def test_check_required(self):
568 '''Test check_required() - has protocol'''
569
570=== modified file 'clickreviews/tests/test_modules.py'
571--- clickreviews/tests/test_modules.py 2014-09-02 16:32:51 +0000
572+++ clickreviews/tests/test_modules.py 2015-10-16 05:56:07 +0000
573@@ -7,8 +7,7 @@
574 '''Tests for the modules module.'''
575 def setUp(self):
576 self.cr_modules = modules.get_modules()
577- cr_tests.mock_patch()
578- super()
579+ super().setUp()
580
581 def test_number_of_suitable_modules(self):
582 path = clickreviews.__path__[0]
583
584=== added file 'clickreviews/tests/utils.py'
585--- clickreviews/tests/utils.py 1970-01-01 00:00:00 +0000
586+++ clickreviews/tests/utils.py 2015-10-16 05:56:07 +0000
587@@ -0,0 +1,189 @@
588+'''utils.py: test utils for click reviewer tools'''
589+#
590+# Copyright (C) 2013-2015 Canonical Ltd.
591+#
592+# This program is free software: you can redistribute it and/or modify
593+# it under the terms of the GNU General Public License as published by
594+# the Free Software Foundation; version 3 of the License.
595+#
596+# This program is distributed in the hope that it will be useful,
597+# but WITHOUT ANY WARRANTY; without even the implied warranty of
598+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
599+# GNU General Public License for more details.
600+#
601+# You should have received a copy of the GNU General Public License
602+# along with this program. If not, see <http://www.gnu.org/licenses/>.
603+
604+import glob
605+import json
606+import os
607+import shutil
608+import subprocess
609+import tempfile
610+
611+
612+def make_package(name='test', package_format='click', package_types=None,
613+ version='1.0', title="An application",
614+ framework='ubuntu-sdk-15.04', extra_files=None, output_dir=None):
615+ """Return the path to a click/snap package with the given data.
616+
617+ Caller is responsible for deleting the output_dir afterwards.
618+ """
619+ is_snap = (package_format == "snap")
620+ build_dir = tempfile.mkdtemp()
621+ package_types = package_types or []
622+
623+ try:
624+ make_dir_structure(build_dir, extra_files=extra_files)
625+ write_icon(build_dir)
626+ write_manifest(build_dir, name, version,
627+ title, framework, package_types,
628+ is_snap)
629+ if is_snap:
630+ write_meta_data(build_dir, name, version,
631+ title, framework)
632+ write_control(build_dir, name, version, title)
633+ write_preinst(build_dir)
634+ write_apparmor_profile(build_dir, name)
635+ write_other_files(build_dir)
636+ pkg_path = build_package(build_dir, name, version, package_format,
637+ output_dir=output_dir)
638+ finally:
639+ shutil.rmtree(build_dir)
640+
641+ return pkg_path
642+
643+
644+def make_dir_structure(path, extra_files=None):
645+ extra_files = extra_files or []
646+ directories = ['DEBIAN', 'meta']
647+ directories.extend(
648+ [os.path.dirname(extra_file) for extra_file in extra_files])
649+
650+ for directory in directories:
651+ directory = os.path.join(path, directory)
652+ if not os.path.exists(directory):
653+ os.makedirs(directory)
654+
655+ for extra_file in extra_files:
656+ dirname, basename = os.path.split(extra_file)
657+ if basename != '':
658+ with open(os.path.join(path, extra_file), 'wb'):
659+ pass
660+
661+
662+def write_icon(path):
663+ # XXX: Update to use a test icon in the branch to guarantee an icon.
664+ icons = glob.glob('/usr/share/icons/hicolor/256x256/apps/*.png')
665+ if len(icons) > 0:
666+ source_path = icons[0]
667+ target_path = os.path.join(path, 'meta', 'icon.png')
668+ shutil.copyfile(source_path, target_path)
669+
670+
671+def write_manifest(path, name, version, title, framework, types, is_snap):
672+ manifest_content = {
673+ 'framework': framework,
674+ 'maintainer': 'Someone <someone@example.com>',
675+ 'name': name,
676+ 'title': title,
677+ 'version': version,
678+ 'icon': 'meta/icon.png',
679+ 'hooks': {
680+ 'app': {
681+ 'apparmor': 'meta/{}.apparmor'.format(name),
682+ },
683+ },
684+ 'description': 'This is a test app.',
685+ }
686+ if types:
687+ if is_snap:
688+ manifest_content.update({'type': types[0]})
689+ else:
690+ if "scope" in types:
691+ manifest_content['hooks']['app'].update({'scope': ""})
692+ if "application" in types:
693+ manifest_content['hooks']['app'].update({'desktop': ""})
694+
695+ manifest_paths = [
696+ os.path.join(path, 'DEBIAN', 'manifest'),
697+ os.path.join(path, 'manifest.json'),
698+ ]
699+ for manifest_path in manifest_paths:
700+ with open(manifest_path, 'w') as f:
701+ json.dump(manifest_content, f)
702+
703+
704+def write_meta_data(path, name, version, title, framework):
705+ yaml_path = os.path.join(path, 'meta', 'package.yaml')
706+ content = """architectures:
707+icon: meta/icon.png
708+name: {}
709+version: "{}",
710+framework: {},
711+vendor: 'Someone <someone@example.com>',
712+""".format(name, version, framework)
713+
714+ with open(yaml_path, 'w') as f:
715+ f.write(content)
716+ with open(os.path.join(path, 'meta', 'readme.md'), 'w') as f:
717+ f.write(title)
718+
719+
720+def write_control(path, name, version, title):
721+ control_path = os.path.join(path, 'DEBIAN', 'control')
722+ control_content = {
723+ 'Package': name,
724+ 'Version': version,
725+ 'Click-Version': '0.4',
726+ 'Architecture': 'all',
727+ 'Maintainer': 'Someone <someone@example.com>',
728+ 'Installed-Size': '123',
729+ 'Description': title,
730+ }
731+ with open(control_path, 'w') as f:
732+ for key, value in control_content.items():
733+ f.write(key + ": " + value + "\n")
734+
735+
736+def write_preinst(path):
737+ preinst_path = os.path.join(path, 'DEBIAN', 'preinst')
738+ with open(preinst_path, 'w') as f:
739+ f.write("""#! /bin/sh
740+echo "Click packages may not be installed directly using dpkg."
741+echo "Use 'click install' instead."
742+exit 1
743+""")
744+ os.chmod(preinst_path, 0o775)
745+
746+
747+def write_apparmor_profile(path, name):
748+ profile_path = os.path.join(path, 'meta', '{}.apparmor'.format(name))
749+ profile = {
750+ 'policy_version': 1.3,
751+ 'policy_groups': [],
752+ }
753+ with open(profile_path, 'w') as f:
754+ json.dump(profile, f)
755+
756+
757+def write_other_files(path):
758+ def write_empty_file(path, perms=0o664):
759+ with open(path, 'wb'):
760+ pass
761+ os.chmod(path, perms)
762+ write_empty_file(os.path.join(path, 'DEBIAN', 'md5sums'))
763+
764+
765+def build_package(path, name, version, format, output_dir=None):
766+ filename = "{}_{}_all.{}".format(name, version, format)
767+ output_dir = output_dir or tempfile.mkdtemp()
768+ output_path = os.path.join(output_dir, filename)
769+
770+ # Note: We're not using 'click build' here as it corrects errors (such
771+ # as filtering out a .click directory present in the build). We want
772+ # to test with manually constructed, potentially tampered-with
773+ # clicks/snaps. Ideally, we'd be using click rather than dpkg to
774+ # construct the click without filtering any files in the build dir.
775+ subprocess.check_call(['dpkg-deb', '-b', path, output_path])
776+ return output_path

Subscribers

People subscribed via source and target branches