|
1 #!/usr/bin/env python |
|
2 |
|
3 # This Source Code Form is subject to the terms of the Mozilla Public |
|
4 # License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
5 # You can obtain one at http://mozilla.org/MPL/2.0/. |
|
6 |
|
7 import os |
|
8 import shutil |
|
9 import tempfile |
|
10 import unittest |
|
11 import urllib2 |
|
12 |
|
13 from manifestparser import ManifestParser |
|
14 import mozfile |
|
15 import mozhttpd |
|
16 import mozlog |
|
17 import mozprofile |
|
18 |
|
19 from addon_stubs import generate_addon, generate_manifest |
|
20 |
|
21 |
|
22 here = os.path.dirname(os.path.abspath(__file__)) |
|
23 |
|
24 |
|
25 class TestAddonsManager(unittest.TestCase): |
|
26 """ Class to test mozprofile.addons.AddonManager """ |
|
27 |
|
28 def setUp(self): |
|
29 self.logger = mozlog.getLogger('mozprofile.addons') |
|
30 self.logger.setLevel(mozlog.ERROR) |
|
31 |
|
32 self.profile = mozprofile.profile.Profile() |
|
33 self.am = self.profile.addon_manager |
|
34 |
|
35 self.profile_path = self.profile.profile |
|
36 self.tmpdir = tempfile.mkdtemp() |
|
37 |
|
38 def tearDown(self): |
|
39 mozfile.rmtree(self.tmpdir) |
|
40 |
|
41 self.am = None |
|
42 self.profile = None |
|
43 |
|
44 # Bug 934484 |
|
45 # Sometimes the profile folder gets recreated at the end and will be left |
|
46 # behind. So we should ensure that we clean it up correctly. |
|
47 mozfile.rmtree(self.profile_path) |
|
48 |
|
49 def test_install_addons_multiple_same_source(self): |
|
50 # Generate installer stubs for all possible types of addons |
|
51 addon_xpi = generate_addon('test-addon-1@mozilla.org', |
|
52 path=self.tmpdir) |
|
53 addon_folder = generate_addon('test-addon-1@mozilla.org', |
|
54 path=self.tmpdir, |
|
55 xpi=False) |
|
56 |
|
57 # The same folder should not be installed twice |
|
58 self.am.install_addons([addon_folder, addon_folder]) |
|
59 self.assertEqual(self.am.installed_addons, [addon_folder]) |
|
60 self.am.clean() |
|
61 |
|
62 # The same XPI file should not be installed twice |
|
63 self.am.install_addons([addon_xpi, addon_xpi]) |
|
64 self.assertEqual(self.am.installed_addons, [addon_xpi]) |
|
65 self.am.clean() |
|
66 |
|
67 # Even if it is the same id the add-on should be installed twice, if |
|
68 # specified via XPI and folder |
|
69 self.am.install_addons([addon_folder, addon_xpi]) |
|
70 self.assertEqual(len(self.am.installed_addons), 2) |
|
71 self.assertIn(addon_folder, self.am.installed_addons) |
|
72 self.assertIn(addon_xpi, self.am.installed_addons) |
|
73 self.am.clean() |
|
74 |
|
75 def test_download(self): |
|
76 server = mozhttpd.MozHttpd(docroot=os.path.join(here, 'addons')) |
|
77 server.start() |
|
78 |
|
79 # Download a valid add-on without a class instance to the general |
|
80 # tmp folder and clean-up |
|
81 try: |
|
82 addon = server.get_url() + 'empty.xpi' |
|
83 xpi_file = mozprofile.addons.AddonManager.download(addon) |
|
84 self.assertTrue(os.path.isfile(xpi_file)) |
|
85 self.assertIn('test-empty@quality.mozilla.org.xpi', |
|
86 os.path.basename(xpi_file)) |
|
87 self.assertNotIn(self.tmpdir, os.path.dirname(xpi_file)) |
|
88 finally: |
|
89 # Given that the file is stored outside of the created tmp dir |
|
90 # we have to ensure to explicitely remove it |
|
91 if os.path.isfile(xpi_file): |
|
92 os.remove(xpi_file) |
|
93 |
|
94 # Download an valid add-on to a special folder |
|
95 addon = server.get_url() + 'empty.xpi' |
|
96 xpi_file = self.am.download(addon, self.tmpdir) |
|
97 self.assertTrue(os.path.isfile(xpi_file)) |
|
98 self.assertIn('test-empty@quality.mozilla.org.xpi', |
|
99 os.path.basename(xpi_file)) |
|
100 self.assertIn(self.tmpdir, os.path.dirname(xpi_file)) |
|
101 self.assertEqual(self.am.downloaded_addons, []) |
|
102 os.remove(xpi_file) |
|
103 |
|
104 # Download an invalid add-on to a special folder |
|
105 addon = server.get_url() + 'invalid.xpi' |
|
106 self.assertRaises(mozprofile.addons.AddonFormatError, |
|
107 self.am.download, addon, self.tmpdir) |
|
108 self.assertEqual(os.listdir(self.tmpdir), []) |
|
109 |
|
110 # Download from an invalid URL |
|
111 addon = server.get_url() + 'not_existent.xpi' |
|
112 self.assertRaises(urllib2.HTTPError, |
|
113 self.am.download, addon, self.tmpdir) |
|
114 self.assertEqual(os.listdir(self.tmpdir), []) |
|
115 |
|
116 # Download from an invalid URL |
|
117 addon = 'not_existent.xpi' |
|
118 self.assertRaises(ValueError, |
|
119 self.am.download, addon, self.tmpdir) |
|
120 self.assertEqual(os.listdir(self.tmpdir), []) |
|
121 |
|
122 server.stop() |
|
123 |
|
124 def test_install_from_path_xpi(self): |
|
125 addons_to_install = [] |
|
126 addons_installed = [] |
|
127 |
|
128 # Generate installer stubs and install them |
|
129 for ext in ['test-addon-1@mozilla.org', 'test-addon-2@mozilla.org']: |
|
130 temp_addon = generate_addon(ext, path=self.tmpdir) |
|
131 addons_to_install.append(self.am.addon_details(temp_addon)['id']) |
|
132 self.am.install_from_path(temp_addon) |
|
133 |
|
134 # Generate a list of addons installed in the profile |
|
135 addons_installed = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join( |
|
136 self.profile.profile, 'extensions', 'staged'))] |
|
137 self.assertEqual(addons_to_install.sort(), addons_installed.sort()) |
|
138 |
|
139 def test_install_from_path_folder(self): |
|
140 # Generate installer stubs for all possible types of addons |
|
141 addons = [] |
|
142 addons.append(generate_addon('test-addon-1@mozilla.org', |
|
143 path=self.tmpdir)) |
|
144 addons.append(generate_addon('test-addon-2@mozilla.org', |
|
145 path=self.tmpdir, |
|
146 xpi=False)) |
|
147 addons.append(generate_addon('test-addon-3@mozilla.org', |
|
148 path=self.tmpdir, |
|
149 name='addon-3')) |
|
150 addons.append(generate_addon('test-addon-4@mozilla.org', |
|
151 path=self.tmpdir, |
|
152 name='addon-4', |
|
153 xpi=False)) |
|
154 addons.sort() |
|
155 |
|
156 self.am.install_from_path(self.tmpdir) |
|
157 |
|
158 self.assertEqual(self.am.installed_addons, addons) |
|
159 |
|
160 def test_install_from_path_unpack(self): |
|
161 # Generate installer stubs for all possible types of addons |
|
162 addon_xpi = generate_addon('test-addon-unpack@mozilla.org', |
|
163 path=self.tmpdir) |
|
164 addon_folder = generate_addon('test-addon-unpack@mozilla.org', |
|
165 path=self.tmpdir, |
|
166 xpi=False) |
|
167 addon_no_unpack = generate_addon('test-addon-1@mozilla.org', |
|
168 path=self.tmpdir) |
|
169 |
|
170 # Test unpack flag for add-on as XPI |
|
171 self.am.install_from_path(addon_xpi) |
|
172 self.assertEqual(self.am.installed_addons, [addon_xpi]) |
|
173 self.am.clean() |
|
174 |
|
175 # Test unpack flag for add-on as folder |
|
176 self.am.install_from_path(addon_folder) |
|
177 self.assertEqual(self.am.installed_addons, [addon_folder]) |
|
178 self.am.clean() |
|
179 |
|
180 # Test forcing unpack an add-on |
|
181 self.am.install_from_path(addon_no_unpack, unpack=True) |
|
182 self.assertEqual(self.am.installed_addons, [addon_no_unpack]) |
|
183 self.am.clean() |
|
184 |
|
185 def test_install_from_path_url(self): |
|
186 server = mozhttpd.MozHttpd(docroot=os.path.join(here, 'addons')) |
|
187 server.start() |
|
188 |
|
189 addon = server.get_url() + 'empty.xpi' |
|
190 self.am.install_from_path(addon) |
|
191 |
|
192 server.stop() |
|
193 |
|
194 self.assertEqual(len(self.am.downloaded_addons), 1) |
|
195 self.assertTrue(os.path.isfile(self.am.downloaded_addons[0])) |
|
196 self.assertIn('test-empty@quality.mozilla.org.xpi', |
|
197 os.path.basename(self.am.downloaded_addons[0])) |
|
198 |
|
199 def test_install_from_path_after_reset(self): |
|
200 # Installing the same add-on after a reset should not cause a failure |
|
201 addon = generate_addon('test-addon-1@mozilla.org', |
|
202 path=self.tmpdir, xpi=False) |
|
203 |
|
204 # We cannot use self.am because profile.reset() creates a new instance |
|
205 self.profile.addon_manager.install_from_path(addon) |
|
206 |
|
207 self.profile.reset() |
|
208 |
|
209 self.profile.addon_manager.install_from_path(addon) |
|
210 self.assertEqual(self.profile.addon_manager.installed_addons, [addon]) |
|
211 |
|
212 def test_install_from_path_backup(self): |
|
213 staged_path = os.path.join(self.profile_path, 'extensions', 'staged') |
|
214 |
|
215 # Generate installer stubs for all possible types of addons |
|
216 addon_xpi = generate_addon('test-addon-1@mozilla.org', |
|
217 path=self.tmpdir) |
|
218 addon_folder = generate_addon('test-addon-1@mozilla.org', |
|
219 path=self.tmpdir, |
|
220 xpi=False) |
|
221 addon_name = generate_addon('test-addon-1@mozilla.org', |
|
222 path=self.tmpdir, |
|
223 name='test-addon-1-dupe@mozilla.org') |
|
224 |
|
225 # Test backup of xpi files |
|
226 self.am.install_from_path(addon_xpi) |
|
227 self.assertIsNone(self.am.backup_dir) |
|
228 |
|
229 self.am.install_from_path(addon_xpi) |
|
230 self.assertIsNotNone(self.am.backup_dir) |
|
231 self.assertEqual(os.listdir(self.am.backup_dir), |
|
232 ['test-addon-1@mozilla.org.xpi']) |
|
233 |
|
234 self.am.clean() |
|
235 self.assertEqual(os.listdir(staged_path), |
|
236 ['test-addon-1@mozilla.org.xpi']) |
|
237 self.am.clean() |
|
238 |
|
239 # Test backup of folders |
|
240 self.am.install_from_path(addon_folder) |
|
241 self.assertIsNone(self.am.backup_dir) |
|
242 |
|
243 self.am.install_from_path(addon_folder) |
|
244 self.assertIsNotNone(self.am.backup_dir) |
|
245 self.assertEqual(os.listdir(self.am.backup_dir), |
|
246 ['test-addon-1@mozilla.org']) |
|
247 |
|
248 self.am.clean() |
|
249 self.assertEqual(os.listdir(staged_path), |
|
250 ['test-addon-1@mozilla.org']) |
|
251 self.am.clean() |
|
252 |
|
253 # Test backup of xpi files with another file name |
|
254 self.am.install_from_path(addon_name) |
|
255 self.assertIsNone(self.am.backup_dir) |
|
256 |
|
257 self.am.install_from_path(addon_xpi) |
|
258 self.assertIsNotNone(self.am.backup_dir) |
|
259 self.assertEqual(os.listdir(self.am.backup_dir), |
|
260 ['test-addon-1@mozilla.org.xpi']) |
|
261 |
|
262 self.am.clean() |
|
263 self.assertEqual(os.listdir(staged_path), |
|
264 ['test-addon-1@mozilla.org.xpi']) |
|
265 self.am.clean() |
|
266 |
|
267 def test_install_from_path_invalid_addons(self): |
|
268 # Generate installer stubs for all possible types of addons |
|
269 addons = [] |
|
270 addons.append(generate_addon('test-addon-invalid-no-manifest@mozilla.org', |
|
271 path=self.tmpdir, |
|
272 xpi=False)) |
|
273 addons.append(generate_addon('test-addon-invalid-no-id@mozilla.org', |
|
274 path=self.tmpdir)) |
|
275 |
|
276 self.am.install_from_path(self.tmpdir) |
|
277 |
|
278 self.assertEqual(self.am.installed_addons, []) |
|
279 |
|
280 @unittest.skip("Feature not implemented as part of AddonManger") |
|
281 def test_install_from_path_error(self): |
|
282 """ Check install_from_path raises an error with an invalid addon""" |
|
283 |
|
284 temp_addon = generate_addon('test-addon-invalid-version@mozilla.org') |
|
285 # This should raise an error here |
|
286 self.am.install_from_path(temp_addon) |
|
287 |
|
288 def test_install_from_manifest(self): |
|
289 temp_manifest = generate_manifest(['test-addon-1@mozilla.org', |
|
290 'test-addon-2@mozilla.org']) |
|
291 m = ManifestParser() |
|
292 m.read(temp_manifest) |
|
293 addons = m.get() |
|
294 |
|
295 # Obtain details of addons to install from the manifest |
|
296 addons_to_install = [self.am.addon_details(x['path']).get('id') for x in addons] |
|
297 |
|
298 self.am.install_from_manifest(temp_manifest) |
|
299 # Generate a list of addons installed in the profile |
|
300 addons_installed = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join( |
|
301 self.profile.profile, 'extensions', 'staged'))] |
|
302 self.assertEqual(addons_installed.sort(), addons_to_install.sort()) |
|
303 |
|
304 # Cleanup the temporary addon and manifest directories |
|
305 mozfile.rmtree(os.path.dirname(temp_manifest)) |
|
306 |
|
307 def test_addon_details(self): |
|
308 # Generate installer stubs for a valid and invalid add-on manifest |
|
309 valid_addon = generate_addon('test-addon-1@mozilla.org', |
|
310 path=self.tmpdir) |
|
311 invalid_addon = generate_addon('test-addon-invalid-not-wellformed@mozilla.org', |
|
312 path=self.tmpdir) |
|
313 |
|
314 # Check valid add-on |
|
315 details = self.am.addon_details(valid_addon) |
|
316 self.assertEqual(details['id'], 'test-addon-1@mozilla.org') |
|
317 self.assertEqual(details['name'], 'Test Add-on 1') |
|
318 self.assertEqual(details['unpack'], False) |
|
319 self.assertEqual(details['version'], '0.1') |
|
320 |
|
321 # Check invalid add-on |
|
322 self.assertRaises(mozprofile.addons.AddonFormatError, |
|
323 self.am.addon_details, invalid_addon) |
|
324 |
|
325 # Check invalid path |
|
326 self.assertRaises(IOError, |
|
327 self.am.addon_details, '') |
|
328 |
|
329 # Check invalid add-on format |
|
330 addon_path = os.path.join(os.path.join(here, 'files'), 'not_an_addon.txt') |
|
331 self.assertRaises(mozprofile.addons.AddonFormatError, |
|
332 self.am.addon_details, addon_path) |
|
333 |
|
334 @unittest.skip("Bug 900154") |
|
335 def test_clean_addons(self): |
|
336 addon_one = generate_addon('test-addon-1@mozilla.org') |
|
337 addon_two = generate_addon('test-addon-2@mozilla.org') |
|
338 |
|
339 self.am.install_addons(addon_one) |
|
340 installed_addons = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join( |
|
341 self.profile.profile, 'extensions', 'staged'))] |
|
342 |
|
343 # Create a new profile based on an existing profile |
|
344 # Install an extra addon in the new profile |
|
345 # Cleanup addons |
|
346 duplicate_profile = mozprofile.profile.Profile(profile=self.profile.profile, |
|
347 addons=addon_two) |
|
348 duplicate_profile.addon_manager.clean() |
|
349 |
|
350 addons_after_cleanup = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join( |
|
351 duplicate_profile.profile, 'extensions', 'staged'))] |
|
352 # New addons installed should be removed by clean_addons() |
|
353 self.assertEqual(installed_addons, addons_after_cleanup) |
|
354 |
|
355 def test_noclean(self): |
|
356 """test `restore=True/False` functionality""" |
|
357 |
|
358 server = mozhttpd.MozHttpd(docroot=os.path.join(here, 'addons')) |
|
359 server.start() |
|
360 |
|
361 profile = tempfile.mkdtemp() |
|
362 tmpdir = tempfile.mkdtemp() |
|
363 |
|
364 try: |
|
365 # empty initially |
|
366 self.assertFalse(bool(os.listdir(profile))) |
|
367 |
|
368 # make an addon |
|
369 addons = [] |
|
370 addons.append(generate_addon('test-addon-1@mozilla.org', |
|
371 path=tmpdir)) |
|
372 addons.append(server.get_url() + 'empty.xpi') |
|
373 |
|
374 # install it with a restore=True AddonManager |
|
375 am = mozprofile.addons.AddonManager(profile, restore=True) |
|
376 |
|
377 for addon in addons: |
|
378 am.install_from_path(addon) |
|
379 |
|
380 # now its there |
|
381 self.assertEqual(os.listdir(profile), ['extensions']) |
|
382 staging_folder = os.path.join(profile, 'extensions', 'staged') |
|
383 self.assertTrue(os.path.exists(staging_folder)) |
|
384 self.assertEqual(len(os.listdir(staging_folder)), 2) |
|
385 |
|
386 # del addons; now its gone though the directory tree exists |
|
387 downloaded_addons = am.downloaded_addons |
|
388 del am |
|
389 |
|
390 self.assertEqual(os.listdir(profile), ['extensions']) |
|
391 self.assertTrue(os.path.exists(staging_folder)) |
|
392 self.assertEqual(os.listdir(staging_folder), []) |
|
393 |
|
394 for addon in downloaded_addons: |
|
395 self.assertFalse(os.path.isfile(addon)) |
|
396 |
|
397 finally: |
|
398 mozfile.rmtree(tmpdir) |
|
399 mozfile.rmtree(profile) |
|
400 |
|
401 def test_remove_addon(self): |
|
402 addons = [] |
|
403 addons.append(generate_addon('test-addon-1@mozilla.org', |
|
404 path=self.tmpdir)) |
|
405 addons.append(generate_addon('test-addon-2@mozilla.org', |
|
406 path=self.tmpdir)) |
|
407 |
|
408 self.am.install_from_path(self.tmpdir) |
|
409 |
|
410 extensions_path = os.path.join(self.profile_path, 'extensions') |
|
411 staging_path = os.path.join(extensions_path, 'staged') |
|
412 |
|
413 # Fake a run by virtually installing one of the staged add-ons |
|
414 shutil.move(os.path.join(staging_path, 'test-addon-1@mozilla.org.xpi'), |
|
415 extensions_path) |
|
416 |
|
417 for addon in self.am._addons: |
|
418 self.am.remove_addon(addon) |
|
419 |
|
420 self.assertEqual(os.listdir(staging_path), []) |
|
421 self.assertEqual(os.listdir(extensions_path), ['staged']) |
|
422 |
|
423 |
|
424 if __name__ == '__main__': |
|
425 unittest.main() |