michael@0: #!/usr/bin/env python michael@0: michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: # You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: import os michael@0: import shutil michael@0: import tempfile michael@0: import unittest michael@0: import urllib2 michael@0: michael@0: from manifestparser import ManifestParser michael@0: import mozfile michael@0: import mozhttpd michael@0: import mozlog michael@0: import mozprofile michael@0: michael@0: from addon_stubs import generate_addon, generate_manifest michael@0: michael@0: michael@0: here = os.path.dirname(os.path.abspath(__file__)) michael@0: michael@0: michael@0: class TestAddonsManager(unittest.TestCase): michael@0: """ Class to test mozprofile.addons.AddonManager """ michael@0: michael@0: def setUp(self): michael@0: self.logger = mozlog.getLogger('mozprofile.addons') michael@0: self.logger.setLevel(mozlog.ERROR) michael@0: michael@0: self.profile = mozprofile.profile.Profile() michael@0: self.am = self.profile.addon_manager michael@0: michael@0: self.profile_path = self.profile.profile michael@0: self.tmpdir = tempfile.mkdtemp() michael@0: michael@0: def tearDown(self): michael@0: mozfile.rmtree(self.tmpdir) michael@0: michael@0: self.am = None michael@0: self.profile = None michael@0: michael@0: # Bug 934484 michael@0: # Sometimes the profile folder gets recreated at the end and will be left michael@0: # behind. So we should ensure that we clean it up correctly. michael@0: mozfile.rmtree(self.profile_path) michael@0: michael@0: def test_install_addons_multiple_same_source(self): michael@0: # Generate installer stubs for all possible types of addons michael@0: addon_xpi = generate_addon('test-addon-1@mozilla.org', michael@0: path=self.tmpdir) michael@0: addon_folder = generate_addon('test-addon-1@mozilla.org', michael@0: path=self.tmpdir, michael@0: xpi=False) michael@0: michael@0: # The same folder should not be installed twice michael@0: self.am.install_addons([addon_folder, addon_folder]) michael@0: self.assertEqual(self.am.installed_addons, [addon_folder]) michael@0: self.am.clean() michael@0: michael@0: # The same XPI file should not be installed twice michael@0: self.am.install_addons([addon_xpi, addon_xpi]) michael@0: self.assertEqual(self.am.installed_addons, [addon_xpi]) michael@0: self.am.clean() michael@0: michael@0: # Even if it is the same id the add-on should be installed twice, if michael@0: # specified via XPI and folder michael@0: self.am.install_addons([addon_folder, addon_xpi]) michael@0: self.assertEqual(len(self.am.installed_addons), 2) michael@0: self.assertIn(addon_folder, self.am.installed_addons) michael@0: self.assertIn(addon_xpi, self.am.installed_addons) michael@0: self.am.clean() michael@0: michael@0: def test_download(self): michael@0: server = mozhttpd.MozHttpd(docroot=os.path.join(here, 'addons')) michael@0: server.start() michael@0: michael@0: # Download a valid add-on without a class instance to the general michael@0: # tmp folder and clean-up michael@0: try: michael@0: addon = server.get_url() + 'empty.xpi' michael@0: xpi_file = mozprofile.addons.AddonManager.download(addon) michael@0: self.assertTrue(os.path.isfile(xpi_file)) michael@0: self.assertIn('test-empty@quality.mozilla.org.xpi', michael@0: os.path.basename(xpi_file)) michael@0: self.assertNotIn(self.tmpdir, os.path.dirname(xpi_file)) michael@0: finally: michael@0: # Given that the file is stored outside of the created tmp dir michael@0: # we have to ensure to explicitely remove it michael@0: if os.path.isfile(xpi_file): michael@0: os.remove(xpi_file) michael@0: michael@0: # Download an valid add-on to a special folder michael@0: addon = server.get_url() + 'empty.xpi' michael@0: xpi_file = self.am.download(addon, self.tmpdir) michael@0: self.assertTrue(os.path.isfile(xpi_file)) michael@0: self.assertIn('test-empty@quality.mozilla.org.xpi', michael@0: os.path.basename(xpi_file)) michael@0: self.assertIn(self.tmpdir, os.path.dirname(xpi_file)) michael@0: self.assertEqual(self.am.downloaded_addons, []) michael@0: os.remove(xpi_file) michael@0: michael@0: # Download an invalid add-on to a special folder michael@0: addon = server.get_url() + 'invalid.xpi' michael@0: self.assertRaises(mozprofile.addons.AddonFormatError, michael@0: self.am.download, addon, self.tmpdir) michael@0: self.assertEqual(os.listdir(self.tmpdir), []) michael@0: michael@0: # Download from an invalid URL michael@0: addon = server.get_url() + 'not_existent.xpi' michael@0: self.assertRaises(urllib2.HTTPError, michael@0: self.am.download, addon, self.tmpdir) michael@0: self.assertEqual(os.listdir(self.tmpdir), []) michael@0: michael@0: # Download from an invalid URL michael@0: addon = 'not_existent.xpi' michael@0: self.assertRaises(ValueError, michael@0: self.am.download, addon, self.tmpdir) michael@0: self.assertEqual(os.listdir(self.tmpdir), []) michael@0: michael@0: server.stop() michael@0: michael@0: def test_install_from_path_xpi(self): michael@0: addons_to_install = [] michael@0: addons_installed = [] michael@0: michael@0: # Generate installer stubs and install them michael@0: for ext in ['test-addon-1@mozilla.org', 'test-addon-2@mozilla.org']: michael@0: temp_addon = generate_addon(ext, path=self.tmpdir) michael@0: addons_to_install.append(self.am.addon_details(temp_addon)['id']) michael@0: self.am.install_from_path(temp_addon) michael@0: michael@0: # Generate a list of addons installed in the profile michael@0: addons_installed = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join( michael@0: self.profile.profile, 'extensions', 'staged'))] michael@0: self.assertEqual(addons_to_install.sort(), addons_installed.sort()) michael@0: michael@0: def test_install_from_path_folder(self): michael@0: # Generate installer stubs for all possible types of addons michael@0: addons = [] michael@0: addons.append(generate_addon('test-addon-1@mozilla.org', michael@0: path=self.tmpdir)) michael@0: addons.append(generate_addon('test-addon-2@mozilla.org', michael@0: path=self.tmpdir, michael@0: xpi=False)) michael@0: addons.append(generate_addon('test-addon-3@mozilla.org', michael@0: path=self.tmpdir, michael@0: name='addon-3')) michael@0: addons.append(generate_addon('test-addon-4@mozilla.org', michael@0: path=self.tmpdir, michael@0: name='addon-4', michael@0: xpi=False)) michael@0: addons.sort() michael@0: michael@0: self.am.install_from_path(self.tmpdir) michael@0: michael@0: self.assertEqual(self.am.installed_addons, addons) michael@0: michael@0: def test_install_from_path_unpack(self): michael@0: # Generate installer stubs for all possible types of addons michael@0: addon_xpi = generate_addon('test-addon-unpack@mozilla.org', michael@0: path=self.tmpdir) michael@0: addon_folder = generate_addon('test-addon-unpack@mozilla.org', michael@0: path=self.tmpdir, michael@0: xpi=False) michael@0: addon_no_unpack = generate_addon('test-addon-1@mozilla.org', michael@0: path=self.tmpdir) michael@0: michael@0: # Test unpack flag for add-on as XPI michael@0: self.am.install_from_path(addon_xpi) michael@0: self.assertEqual(self.am.installed_addons, [addon_xpi]) michael@0: self.am.clean() michael@0: michael@0: # Test unpack flag for add-on as folder michael@0: self.am.install_from_path(addon_folder) michael@0: self.assertEqual(self.am.installed_addons, [addon_folder]) michael@0: self.am.clean() michael@0: michael@0: # Test forcing unpack an add-on michael@0: self.am.install_from_path(addon_no_unpack, unpack=True) michael@0: self.assertEqual(self.am.installed_addons, [addon_no_unpack]) michael@0: self.am.clean() michael@0: michael@0: def test_install_from_path_url(self): michael@0: server = mozhttpd.MozHttpd(docroot=os.path.join(here, 'addons')) michael@0: server.start() michael@0: michael@0: addon = server.get_url() + 'empty.xpi' michael@0: self.am.install_from_path(addon) michael@0: michael@0: server.stop() michael@0: michael@0: self.assertEqual(len(self.am.downloaded_addons), 1) michael@0: self.assertTrue(os.path.isfile(self.am.downloaded_addons[0])) michael@0: self.assertIn('test-empty@quality.mozilla.org.xpi', michael@0: os.path.basename(self.am.downloaded_addons[0])) michael@0: michael@0: def test_install_from_path_after_reset(self): michael@0: # Installing the same add-on after a reset should not cause a failure michael@0: addon = generate_addon('test-addon-1@mozilla.org', michael@0: path=self.tmpdir, xpi=False) michael@0: michael@0: # We cannot use self.am because profile.reset() creates a new instance michael@0: self.profile.addon_manager.install_from_path(addon) michael@0: michael@0: self.profile.reset() michael@0: michael@0: self.profile.addon_manager.install_from_path(addon) michael@0: self.assertEqual(self.profile.addon_manager.installed_addons, [addon]) michael@0: michael@0: def test_install_from_path_backup(self): michael@0: staged_path = os.path.join(self.profile_path, 'extensions', 'staged') michael@0: michael@0: # Generate installer stubs for all possible types of addons michael@0: addon_xpi = generate_addon('test-addon-1@mozilla.org', michael@0: path=self.tmpdir) michael@0: addon_folder = generate_addon('test-addon-1@mozilla.org', michael@0: path=self.tmpdir, michael@0: xpi=False) michael@0: addon_name = generate_addon('test-addon-1@mozilla.org', michael@0: path=self.tmpdir, michael@0: name='test-addon-1-dupe@mozilla.org') michael@0: michael@0: # Test backup of xpi files michael@0: self.am.install_from_path(addon_xpi) michael@0: self.assertIsNone(self.am.backup_dir) michael@0: michael@0: self.am.install_from_path(addon_xpi) michael@0: self.assertIsNotNone(self.am.backup_dir) michael@0: self.assertEqual(os.listdir(self.am.backup_dir), michael@0: ['test-addon-1@mozilla.org.xpi']) michael@0: michael@0: self.am.clean() michael@0: self.assertEqual(os.listdir(staged_path), michael@0: ['test-addon-1@mozilla.org.xpi']) michael@0: self.am.clean() michael@0: michael@0: # Test backup of folders michael@0: self.am.install_from_path(addon_folder) michael@0: self.assertIsNone(self.am.backup_dir) michael@0: michael@0: self.am.install_from_path(addon_folder) michael@0: self.assertIsNotNone(self.am.backup_dir) michael@0: self.assertEqual(os.listdir(self.am.backup_dir), michael@0: ['test-addon-1@mozilla.org']) michael@0: michael@0: self.am.clean() michael@0: self.assertEqual(os.listdir(staged_path), michael@0: ['test-addon-1@mozilla.org']) michael@0: self.am.clean() michael@0: michael@0: # Test backup of xpi files with another file name michael@0: self.am.install_from_path(addon_name) michael@0: self.assertIsNone(self.am.backup_dir) michael@0: michael@0: self.am.install_from_path(addon_xpi) michael@0: self.assertIsNotNone(self.am.backup_dir) michael@0: self.assertEqual(os.listdir(self.am.backup_dir), michael@0: ['test-addon-1@mozilla.org.xpi']) michael@0: michael@0: self.am.clean() michael@0: self.assertEqual(os.listdir(staged_path), michael@0: ['test-addon-1@mozilla.org.xpi']) michael@0: self.am.clean() michael@0: michael@0: def test_install_from_path_invalid_addons(self): michael@0: # Generate installer stubs for all possible types of addons michael@0: addons = [] michael@0: addons.append(generate_addon('test-addon-invalid-no-manifest@mozilla.org', michael@0: path=self.tmpdir, michael@0: xpi=False)) michael@0: addons.append(generate_addon('test-addon-invalid-no-id@mozilla.org', michael@0: path=self.tmpdir)) michael@0: michael@0: self.am.install_from_path(self.tmpdir) michael@0: michael@0: self.assertEqual(self.am.installed_addons, []) michael@0: michael@0: @unittest.skip("Feature not implemented as part of AddonManger") michael@0: def test_install_from_path_error(self): michael@0: """ Check install_from_path raises an error with an invalid addon""" michael@0: michael@0: temp_addon = generate_addon('test-addon-invalid-version@mozilla.org') michael@0: # This should raise an error here michael@0: self.am.install_from_path(temp_addon) michael@0: michael@0: def test_install_from_manifest(self): michael@0: temp_manifest = generate_manifest(['test-addon-1@mozilla.org', michael@0: 'test-addon-2@mozilla.org']) michael@0: m = ManifestParser() michael@0: m.read(temp_manifest) michael@0: addons = m.get() michael@0: michael@0: # Obtain details of addons to install from the manifest michael@0: addons_to_install = [self.am.addon_details(x['path']).get('id') for x in addons] michael@0: michael@0: self.am.install_from_manifest(temp_manifest) michael@0: # Generate a list of addons installed in the profile michael@0: addons_installed = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join( michael@0: self.profile.profile, 'extensions', 'staged'))] michael@0: self.assertEqual(addons_installed.sort(), addons_to_install.sort()) michael@0: michael@0: # Cleanup the temporary addon and manifest directories michael@0: mozfile.rmtree(os.path.dirname(temp_manifest)) michael@0: michael@0: def test_addon_details(self): michael@0: # Generate installer stubs for a valid and invalid add-on manifest michael@0: valid_addon = generate_addon('test-addon-1@mozilla.org', michael@0: path=self.tmpdir) michael@0: invalid_addon = generate_addon('test-addon-invalid-not-wellformed@mozilla.org', michael@0: path=self.tmpdir) michael@0: michael@0: # Check valid add-on michael@0: details = self.am.addon_details(valid_addon) michael@0: self.assertEqual(details['id'], 'test-addon-1@mozilla.org') michael@0: self.assertEqual(details['name'], 'Test Add-on 1') michael@0: self.assertEqual(details['unpack'], False) michael@0: self.assertEqual(details['version'], '0.1') michael@0: michael@0: # Check invalid add-on michael@0: self.assertRaises(mozprofile.addons.AddonFormatError, michael@0: self.am.addon_details, invalid_addon) michael@0: michael@0: # Check invalid path michael@0: self.assertRaises(IOError, michael@0: self.am.addon_details, '') michael@0: michael@0: # Check invalid add-on format michael@0: addon_path = os.path.join(os.path.join(here, 'files'), 'not_an_addon.txt') michael@0: self.assertRaises(mozprofile.addons.AddonFormatError, michael@0: self.am.addon_details, addon_path) michael@0: michael@0: @unittest.skip("Bug 900154") michael@0: def test_clean_addons(self): michael@0: addon_one = generate_addon('test-addon-1@mozilla.org') michael@0: addon_two = generate_addon('test-addon-2@mozilla.org') michael@0: michael@0: self.am.install_addons(addon_one) michael@0: installed_addons = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join( michael@0: self.profile.profile, 'extensions', 'staged'))] michael@0: michael@0: # Create a new profile based on an existing profile michael@0: # Install an extra addon in the new profile michael@0: # Cleanup addons michael@0: duplicate_profile = mozprofile.profile.Profile(profile=self.profile.profile, michael@0: addons=addon_two) michael@0: duplicate_profile.addon_manager.clean() michael@0: michael@0: addons_after_cleanup = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join( michael@0: duplicate_profile.profile, 'extensions', 'staged'))] michael@0: # New addons installed should be removed by clean_addons() michael@0: self.assertEqual(installed_addons, addons_after_cleanup) michael@0: michael@0: def test_noclean(self): michael@0: """test `restore=True/False` functionality""" michael@0: michael@0: server = mozhttpd.MozHttpd(docroot=os.path.join(here, 'addons')) michael@0: server.start() michael@0: michael@0: profile = tempfile.mkdtemp() michael@0: tmpdir = tempfile.mkdtemp() michael@0: michael@0: try: michael@0: # empty initially michael@0: self.assertFalse(bool(os.listdir(profile))) michael@0: michael@0: # make an addon michael@0: addons = [] michael@0: addons.append(generate_addon('test-addon-1@mozilla.org', michael@0: path=tmpdir)) michael@0: addons.append(server.get_url() + 'empty.xpi') michael@0: michael@0: # install it with a restore=True AddonManager michael@0: am = mozprofile.addons.AddonManager(profile, restore=True) michael@0: michael@0: for addon in addons: michael@0: am.install_from_path(addon) michael@0: michael@0: # now its there michael@0: self.assertEqual(os.listdir(profile), ['extensions']) michael@0: staging_folder = os.path.join(profile, 'extensions', 'staged') michael@0: self.assertTrue(os.path.exists(staging_folder)) michael@0: self.assertEqual(len(os.listdir(staging_folder)), 2) michael@0: michael@0: # del addons; now its gone though the directory tree exists michael@0: downloaded_addons = am.downloaded_addons michael@0: del am michael@0: michael@0: self.assertEqual(os.listdir(profile), ['extensions']) michael@0: self.assertTrue(os.path.exists(staging_folder)) michael@0: self.assertEqual(os.listdir(staging_folder), []) michael@0: michael@0: for addon in downloaded_addons: michael@0: self.assertFalse(os.path.isfile(addon)) michael@0: michael@0: finally: michael@0: mozfile.rmtree(tmpdir) michael@0: mozfile.rmtree(profile) michael@0: michael@0: def test_remove_addon(self): michael@0: addons = [] michael@0: addons.append(generate_addon('test-addon-1@mozilla.org', michael@0: path=self.tmpdir)) michael@0: addons.append(generate_addon('test-addon-2@mozilla.org', michael@0: path=self.tmpdir)) michael@0: michael@0: self.am.install_from_path(self.tmpdir) michael@0: michael@0: extensions_path = os.path.join(self.profile_path, 'extensions') michael@0: staging_path = os.path.join(extensions_path, 'staged') michael@0: michael@0: # Fake a run by virtually installing one of the staged add-ons michael@0: shutil.move(os.path.join(staging_path, 'test-addon-1@mozilla.org.xpi'), michael@0: extensions_path) michael@0: michael@0: for addon in self.am._addons: michael@0: self.am.remove_addon(addon) michael@0: michael@0: self.assertEqual(os.listdir(staging_path), []) michael@0: self.assertEqual(os.listdir(extensions_path), ['staged']) michael@0: michael@0: michael@0: if __name__ == '__main__': michael@0: unittest.main()