Rewrite security_BundledExtensions

Reports all discrepancies now, not just the first one found.
Tracks extension by ID rather than name, won't fail on name changes now.
Much faster, consolidates 3 login/logouts into 1.
Handles board-specific extensions now.

BUG=chromium-os:36788
TEST=run_remote_tests ... on link, butterfly, zgb ToT

Reviewed-on: https://gerrit.chromium.org/gerrit/42602
Reviewed-by: Kees Cook <keescook@chromium.org>
Commit-Queue: Jim Hebert <jimhebert@chromium.org>
Tested-by: Jim Hebert <jimhebert@chromium.org>
(cherry picked from commit deb5557f3e6172a7594e316c06bba843927601ee)

Change-Id: I142a7f88d4ff2feb2d02d5e8ed2daf855dd3ba37
Reviewed-on: https://gerrit.chromium.org/gerrit/42961
Commit-Queue: Jim Hebert <jimhebert@chromium.org>
Reviewed-by: Jim Hebert <jimhebert@chromium.org>
Tested-by: Jim Hebert <jimhebert@chromium.org>
diff --git a/client/site_tests/security_BundledExtensions/baseline b/client/site_tests/security_BundledExtensions/baseline
index 297a4f3..8781ee0 100644
--- a/client/site_tests/security_BundledExtensions/baseline
+++ b/client/site_tests/security_BundledExtensions/baseline
@@ -2,16 +2,16 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 {
-  "ignored_extension_names": [
-    "Bookmark Manager",
-    "Chrome Web Store",
-    "GaiaTestAuthExtension"
+# Chrome Web Store ("ahfgeienlihckogmohjhadlkjgocpleb") and
+# Bookmark Manager ("eemcgdkfndhakfknompkggombfjjjeno") are
+# already tracked and reviewed on the Chrome side. Ignore them
+# in our test, otherwise it's just a baseline maintenance treadmill.
+  "ignored_extension_ids": [
+    "ahfgeienlihckogmohjhadlkjgocpleb",
+    "eemcgdkfndhakfknompkggombfjjjeno"
   ],
-  "ignored_crx_files": [
-  ],
-  "bundled_crx_directory": "/opt/google/chrome/extensions",
   "bundled_crx_baseline": [
-      { "crx_file": "apdfllckaahabafndbhieahigkjlhalf.crx",
+      { "id": "apdfllckaahabafndbhieahigkjlhalf",
         "name": "Google Drive",
         "effective_host_permissions": [],
         "api_permissions": ["background",
@@ -20,37 +20,37 @@
                             "notifications",
                             "unlimitedStorage"]
       },
-      { "crx_file": "blpcfgokakmgnkcojhhkbfbldkacnbeo.crx",
+      { "id": "blpcfgokakmgnkcojhhkbfbldkacnbeo",
         "name": "YouTube",
         "effective_host_permissions": [],
         "api_permissions": ["appNotifications"]
       },
-      { "crx_file": "ejjicmeblgpmajnghnpcppodonldlgfn.crx",
+      { "id": "ejjicmeblgpmajnghnpcppodonldlgfn",
         "name": "Google Calendar",
         "effective_host_permissions": [],
         "api_permissions": ["notifications",
                             "unlimitedStorage"]
       },
-      { "crx_file": "fobcpibfeplaikcclojfdhfdmbbeofai.crx",
+      { "id": "fobcpibfeplaikcclojfdhfdmbbeofai",
         "name": "Games",
         "effective_host_permissions": [],
         "api_permissions": []
       },
-      { "crx_file": "gbchcmhmhahfdphkhkmpfmihenigjmpp.crx",
+      { "id": "gbchcmhmhahfdphkhkmpfmihenigjmpp",
         "name": "Chrome Remote Desktop",
         "effective_host_permissions": ["https://accounts.google.com/*",
                                        "https://relay.google.com/*",
                                        "https://*.talkgadget.google.com/*",
-"https://chromoting-oauth.talkgadget.google.com/talkgadget/oauth/chrome-remote-desktop/rel/*",
+"https://*.talkgadget.google.com/talkgadget/oauth/chrome-remote-desktop/rel/*",
                                        "https://www.googleapis.com/*"],
         "api_permissions": ["clipboardRead", "clipboardWrite"]
       },
-      { "crx_file": "icppfcnhkcmnfdhfhphakoifcfokfdhg.crx",
+      { "id": "icppfcnhkcmnfdhfhphakoifcfokfdhg",
         "name": "Google Play Music",
         "effective_host_permissions": [],
         "api_permissions": []
       },
-      { "crx_file": "kjebfhglflhjjjiceimfkgicifkhjlnm.crx",
+      { "id": "kjebfhglflhjjjiceimfkgicifkhjlnm",
         "name": "Scratchpad",
         "effective_host_permissions": ["https://docs.google.com/*",
                                        "https://www.google.com/*"],
@@ -59,78 +59,79 @@
                             "notifications",
                             "tabs"]
       },
-      { "crx_file": "pjkljhegncpnkpknbcohdijeoejaedia.crx",
+      { "id": "pjkljhegncpnkpknbcohdijeoejaedia",
         "name": "Gmail",
         "effective_host_permissions": [],
         "api_permissions": ["notifications"]
       },
-      { "crx_file": "lneaknkopdijkpnocmklfnjbeapigfbh.crx",
+      { "id": "lneaknkopdijkpnocmklfnjbeapigfbh",
         "name": "Google Maps",
         "effective_host_permissions": [],
         "api_permissions": []
       },
-      { "crx_file": "dlppkpafhbajpcmmoheippocdidnckmm.crx",
+      { "id": "dlppkpafhbajpcmmoheippocdidnckmm",
         "name": "Google+",
         "effective_host_permissions": [],
         "api_permissions": []
       },
-      { "crx_file": "gbkeegbaiigmenfmjfclcdgdpimamgkj.crx",
+      { "id": "gbkeegbaiigmenfmjfclcdgdpimamgkj",
         "name": "Chrome Office Viewer",
         "effective_host_permissions": [],
         "api_permissions": ["fileBrowserHandler",
                             "fileBrowserHandlerInternal",
+                            "metricsPrivate",
                             "unlimitedStorage"
                            ]
       },
-      { "crx_file": "coobgpohoikkiipiblmjeljniedjpjpf.crx",
+      { "id": "coobgpohoikkiipiblmjeljniedjpjpf",
         "name": "Google Search",
         "effective_host_permissions": [],
         "api_permissions": []
       },
-      { "crx_file": "kbpgddbgniojgndnhlkjbkpknjhppkbk.crx",
+      { "id": "kbpgddbgniojgndnhlkjbkpknjhppkbk",
         "name": "Google+ Hangouts",
         "effective_host_permissions": [],
         "api_permissions": ["background", "tabs"]
       },
-      { "crx_file": "hkhhlkdconhgemhegnplaldnmnmkaemd.crx",
+      { "id": "hkhhlkdconhgemhegnplaldnmnmkaemd",
         "name": "Get Started",
         "effective_host_permissions": [],
         "api_permissions": ["unlimitedStorage"]
       },
-      { "crx_file": "mmimngoggfoobjdlefbcabngfnmieonb.crx",
+      { "id": "mmimngoggfoobjdlefbcabngfnmieonb",
         "name": "Google Play Books",
         "effective_host_permissions": [],
         "api_permissions": ["clipboardWrite",
                             "unlimitedStorage"]
       },
-      { "crx_file": "aohghmighlieiainnegkcijnfilokake.crx",
+      { "id": "aohghmighlieiainnegkcijnfilokake",
         "name": "Google Docs",
         "effective_host_permissions": [],
         "api_permissions": []
       },
-      { "crx_file": "joodangkbfjnajiiifokapkpmhfnpleo.crx",
+      { "id": "joodangkbfjnajiiifokapkpmhfnpleo",
         "name": "Calculator",
         "effective_host_permissions": [],
         "api_permissions": ["app.currentWindowInternal",
                             "app.runtime",
                             "app.window"]
       },
-      { "crx_file": "felcaaldnbdncclmgdcncolpebgiejap.crx",
+      { "id": "felcaaldnbdncclmgdcncolpebgiejap",
         "name": "Google Sheets",
         "effective_host_permissions": [],
         "api_permissions": []
       },
-      { "crx_file": "aapocclcgogkmnckokdopfmhonfmgoek.crx",
+      { "id": "aapocclcgogkmnckokdopfmhonfmgoek",
         "name": "Google Slides",
         "effective_host_permissions": [],
         "api_permissions": []
       },
-      { "crx_file": "fppdphmgcddhjeddoeghpjefkdlccljb.crx",
+      { "id": "fppdphmgcddhjeddoeghpjefkdlccljb",
         "name": "Google Play Movies",
         "effective_host_permissions": [],
         "api_permissions": []
       },
-      { "crx_file": "hfhhnacclhffhdffklopdkcgdhifgngh.crx",
+      { "id": "hfhhnacclhffhdffklopdkcgdhifgngh",
         "name": "Camera",
         "effective_host_permissions": [],
         "api_permissions": ["app.currentWindowInternal",
@@ -145,7 +146,8 @@
       }
   ],
   "component_extension_baseline": [
-      { "name": "Files",
+      { "id": "hhaomjibdihmijegdhdafkllkbggdgoj",
+        "name": "File Manager",
         "effective_host_permissions": ["chrome://extension-icon/*",
                                        "chrome://resources/*",
                                        "chrome://theme/*",
@@ -161,7 +163,8 @@
                             "clipboardRead",
                             "unlimitedStorage"]
       },
-      { "name": "Wallpaper Picker",
+      { "id": "obklkkbkpaoaejdabbfldmcfplpdgolj",
+        "name": "Wallpaper Picker",
         "effective_host_permissions": ["https://storage.googleapis.com/*",
                                        "https://commondatastorage.googleapis.com/*"],
         "api_permissions": ["app.currentWindowInternal",
@@ -170,28 +173,45 @@
                             "storage",
                             "wallpaperPrivate"]
       },
-      { "name": "Mobile Activation",
+      { "id": "iadeocfgjdjdmpenejdbfeaocpbikmab",
+        "name": "Mobile Activation",
         "effective_host_permissions": [],
         "api_permissions": []
       },
-      { "name": "crosh_builtin",
+      { "id": "nkoccljplnhpfnfiajclkommnmllphnl",
+        "name": "crosh_builtin",
         "effective_host_permissions": [],
         "api_permissions": ["terminalPrivate",
                             "unlimitedStorage"]
       },
-      { "name": "Chrome OS ECHO Extension",
+      { "id": "kddnkjkcjddckihglkfcickdhbmaodcn",
+        "name": "Chrome OS ECHO Extension",
         "effective_host_permissions": ["https://*/*"],
         "api_permissions": ["cookies",
                             "echoPrivate",
                             "experimental"]
       },
-      { "name": "Chrome",
+      { "id": "mgndgikekgjfcpckkfioiadnlibdjbkf",
+        "name": "Chrome",
+        "effective_host_permissions": [],
+        "api_permissions": []
+      },
+      { "id": "mlbmkoenclnokonejhlfakkeabdlmpek",
+        "name": "TimeScapes",
+        "boards": ["link"],
+        "effective_host_permissions": [],
+        "api_permissions": []
+      },
+      { "id": "plamffmopddapimaopfdbibfkjpbojbf",
+        "name": "Registration",
+        "boards": ["butterfly"],
         "effective_host_permissions": [],
         "api_permissions": []
       }
   ],
   "official_components": [
       { "name": "Help",
+        "id": "honijodknafkokifofgiaalefdiedpko",
         "effective_host_permissions": [
             "https://gweb-gettingstartedguide.appspot.com/*",
             "http://support.google.com/chromeos/*",
diff --git a/client/site_tests/security_BundledExtensions/control b/client/site_tests/security_BundledExtensions/control
index 656a19c..0d1dcf5 100644
--- a/client/site_tests/security_BundledExtensions/control
+++ b/client/site_tests/security_BundledExtensions/control
@@ -22,12 +22,4 @@
 list and fails (with various helpful details) if there is not an exact match.
 """
 
-job.run_test('security_BundledExtensions', creds='$default',
-             tag='ComponentExtensionPermissions',
-             mode='ComponentExtensionPermissions')
-job.run_test('security_BundledExtensions', creds='$default',
-             tag='BundledCrxPermissions',
-             mode='BundledCrxPermissions')
-job.run_test('security_BundledExtensions', creds='$default',
-             tag='NoUnexpectedExtensions',
-             mode='NoUnexpectedExtensions')
+job.run_test('security_BundledExtensions', creds='$default')
diff --git a/client/site_tests/security_BundledExtensions/security_BundledExtensions.py b/client/site_tests/security_BundledExtensions/security_BundledExtensions.py
index ae39aad..a8978fc 100644
--- a/client/site_tests/security_BundledExtensions/security_BundledExtensions.py
+++ b/client/site_tests/security_BundledExtensions/security_BundledExtensions.py
@@ -7,12 +7,11 @@
 import os
 
 from autotest_lib.client.bin import test
-from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib import error, site_utils
 from autotest_lib.client.cros import cros_ui_test
 
 class security_BundledExtensions(cros_ui_test.UITest):
-    version = 1
-
+    version = 2
 
     def load_baseline(self):
         bfile = open(os.path.join(self.bindir, 'baseline'))
@@ -22,9 +21,7 @@
                 if not line.startswith('#'):
                     baseline.append(line)
             baseline = json.loads(''.join(baseline))
-        self._ignored_extension_names = baseline['ignored_extension_names']
-        self._ignored_crx_files = baseline['ignored_crx_files']
-        self._bundled_crx_directory = baseline['bundled_crx_directory']
+        self._ignored_extension_ids = baseline['ignored_extension_ids']
         self._bundled_crx_baseline = baseline['bundled_crx_baseline']
         self._component_extension_baseline = baseline[
             'component_extension_baseline']
@@ -45,78 +42,11 @@
                       self.pyauto.pformat(complete_info))
         filtered_info = []
         for rec in complete_info:
-            if not rec['name'] in self._ignored_extension_names:
+            if not rec['id'] in self._ignored_extension_ids:
                 filtered_info.append(rec)
         return filtered_info
 
 
-    def assert_perms_match(self, expected_set, actual_set, perm_type,
-                           full_expected_info, full_actual_info):
-        """Asserts that the set of permissions for an extension is expected.
-
-        Args:
-          expected_set: A set of permissions that are expected to be present.
-          actual_set: A set of permissions that are actually present.
-          perm_type: A string describing the type of permission involved.
-          full_expected_info: A dictionary fully describing the expected
-                              information associated with the given extension.
-          full_actual_info: A dictionary fully describing the actual
-                            information associated with the given extension.
-        """
-        def _diff_set_msg(expected_set, actual_set):
-            strings = []
-            for missing_item in expected_set.difference(actual_set):
-                strings.append('Missing item: "%s"' % missing_item)
-            for extra_item in actual_set.difference(expected_set):
-                strings.append('Unexpected (extra) item: "%s"' % extra_item)
-            return '\n'.join(strings)
-
-        self.pyauto.assertEqual(
-            expected_set, actual_set,
-            msg=('%s do not match for "%s".\n'
-                 '%s\n'
-                 'Expected extension info:\n%s'
-                 '\nActual extension info:\n%s' %
-                 (perm_type, full_expected_info['name'],
-                  _diff_set_msg(expected_set, actual_set),
-                  self.pyauto.pformat(full_expected_info),
-                  self.pyauto.pformat(full_actual_info))))
-
-
-    def assert_names_match(self, expected_set, actual_set, ext_type,
-                           full_expected_info, full_actual_info):
-        """Asserts that a set of extensions is expected.
-
-        Args:
-          expected_set: A set of extension names that are expected to be
-                        present.
-          actual_set: A set of extension names that are actually present.
-          ext_type: A string describing the type of extensions involved.
-          full_expected_info: A list of dictionaries describing the expected
-                              information for all extensions.
-          full_actual_info: A list of dictionaries describing the actual
-                            information for all extensions.
-        """
-        def _diff_set_msg(expected_set, actual_set):
-            strings = []
-            for missing_item in expected_set.difference(actual_set):
-                strings.append('Missing item: "%s"' % missing_item)
-                located_ext_info = [info for info in full_expected_info if
-                                    info['name'] == missing_item][0]
-                strings.append(self.pyauto.pformat(located_ext_info))
-            for extra_item in actual_set.difference(expected_set):
-                strings.append('Unexpected (extra) item: "%s"' % extra_item)
-                located_ext_info = [info for info in full_actual_info if
-                                    info['name'] == extra_item][0]
-                strings.append(self.pyauto.pformat(located_ext_info))
-            return '\n'.join(strings)
-
-        self.pyauto.assertEqual(
-            expected_set, actual_set,
-            msg='%s names do not match the baseline.\n'
-                '%s\n' % (ext_type, _diff_set_msg(expected_set, actual_set)))
-
-
     def attempt_install(self, crx_file):
         """Try to install a crx, and log an error if it fails.
 
@@ -135,99 +65,101 @@
             logging.error('Installation failed for %s' % crx_file)
 
 
-    def verify_extension_perms(self, baseline):
-        """Ensures extension permissions in the baseline match actual info.
-
-        This function will fail the current test if either (1) an
-        extension named in the baseline is not currently installed in
-        Chrome; or (2) the api permissions or effective host
-        permissions of an extension in the baseline do not match the
-        actual permissions associated with the extension in Chrome.
-
-        Args:
-          baseline: A dictionary of expected extension information, containing
-                    extension names and api/effective host permission info.
-        """
-        full_ext_actual_info = self.get_extensions_info()
-        for ext_expected_info in baseline:
-            located_ext_info = [info for info in full_ext_actual_info if
-                                info['name'] == ext_expected_info['name']]
-            self.pyauto.assertTrue(
-                located_ext_info,
-                msg=('Cannot locate extension info for "%s".\n'
-                     'Expected extension info:\n%s' %
-                     (ext_expected_info['name'],
-                      self.pyauto.pformat(ext_expected_info))))
-            ext_actual_info = located_ext_info[0]
-            self.assert_perms_match(
-                set(ext_expected_info['effective_host_permissions']),
-                set(ext_actual_info['effective_host_permissions']),
-                'Effective host permissions', ext_expected_info,
-                ext_actual_info)
-            self.assert_perms_match(
-                set(ext_expected_info['api_permissions']),
-                set(ext_actual_info['api_permissions']),
-                'API permissions', ext_expected_info, ext_actual_info)
-
-
-    def test_component_extension_permissions(self):
-        """Ensures component extension permissions are as expected."""
-        expected_names = [ext['name'] for ext in
-                          self._component_extension_baseline]
-        ext_actual_info = self.get_extensions_info()
-        actual_names = [ext['name'] for ext in ext_actual_info if
-                        ext['is_component']]
-        self.assert_names_match(
-            set(expected_names), set(actual_names), 'Component extension',
-            self._component_extension_baseline, ext_actual_info)
-        self.verify_extension_perms(self._component_extension_baseline)
-
-
-    def test_bundled_crx_permissions(self):
-        """Ensures bundled CRX permissions are as expected."""
-        # Verify that each bundled CRX on the device is expected, then
-        # install it.
-        for file_name in os.listdir(self._bundled_crx_directory):
-            if file_name in self._ignored_crx_files:
-                logging.debug('Ignoring %s' % file_name)
+    def install_all(self, crx_dirs):
+        for crx_dir in crx_dirs:
+            if not os.path.exists(crx_dir):
                 continue
-            if file_name.endswith('.crx'):
-                self.pyauto.assertTrue(
-                    file_name in [x['crx_file'] for x in
-                                  self._bundled_crx_baseline],
-                    msg='Unexpected CRX file: ' + file_name)
-                crx_file = os.path.join(self._bundled_crx_directory, file_name)
-                self.attempt_install(crx_file)
-
-        # Verify that the permissions information in the baseline matches the
-        # permissions associated with the installed bundled CRX extensions.
-        self.verify_extension_perms(self._bundled_crx_baseline)
-
-
-    def test_no_unexpected_extensions(self):
-        """Ensures there are no unexpected bundled or component extensions."""
-        # Install all bundled extensions on the device.
-        for file_name in os.listdir(self._bundled_crx_directory):
-            if file_name in self._ignored_crx_files:
-                logging.debug('Ignoring %s' % file_name)
-                continue
-            if file_name.endswith('.crx'):
-                crx_file = os.path.join(self._bundled_crx_directory, file_name)
-                self.attempt_install(crx_file)
+            for file_name in os.listdir(crx_dir):
+                if not file_name.endswith('.crx'):
+                    continue
+                crx_id = self.crx_id_from_filename(file_name)
+                if crx_id in self._ignored_extension_ids:
+                    logging.debug('Ignoring %s' % file_name)
+                    continue
+                self.attempt_install(os.path.join(crx_dir, file_name))
         logging.debug('Done installing extensions')
 
-        # Ensure that the set of installed extension names precisely
-        # matches the baseline.
-        expected_names = [ext['name'] for ext in
-                          self._component_extension_baseline]
-        expected_names.extend([ext['name'] for ext in
-                               self._bundled_crx_baseline])
-        ext_actual_info = self.get_extensions_info()
-        installed_names = [ext['name'] for ext in ext_actual_info]
-        self.assert_names_match(
-            set(expected_names), set(installed_names), 'Installed extension',
-            self._component_extension_baseline + self._bundled_crx_baseline,
-            ext_actual_info)
+
+    def crx_id_from_filename(self, filename):
+        return filename.split('.crx')[0]
+
+
+    def install_and_compare(self):
+        test_fail = False
+        # Install all bundled extensions on the device.
+        main_crx_dir = '/opt/google/chrome/extensions'
+        board_specific_crx_dir = '/usr/share/google-chrome/extensions'
+        self.install_all([main_crx_dir, board_specific_crx_dir])
+
+        # * Find the set of expected IDs.
+        # * Find the set of observed IDs.
+        # * Do set comparison to find the unexpected, and the expected/missing.
+        combined_baseline = (self._bundled_crx_baseline +
+                             self._component_extension_baseline)
+        # Filter out any baseline entries that don't apply to this board.
+        # If there is no 'boards' limiter on a given record, the record applies.
+        # If there IS a 'boards' limiter, check that it applies.
+        board = site_utils.get_current_board()
+        combined_baseline = [x for x in combined_baseline
+                             if ((not 'boards' in x) or
+                                 ('boards' in x and board in x['boards']))]
+
+        observed_extensions = self.get_extensions_info()
+        observed_ids = set([x['id'] for x in observed_extensions])
+        expected_ids = set([x['id'] for x in combined_baseline])
+
+        missing_ids = expected_ids - observed_ids
+        missing_names = ['%s (%s)' % (x['name'], x['id'])
+                         for x in combined_baseline if x['id'] in missing_ids]
+
+        unexpected_ids = observed_ids - expected_ids
+        unexpected_names = ['%s (%s)' % (x['name'], x['id'])
+                            for x in observed_extensions if
+                            x['id'] in unexpected_ids]
+
+        good_ids = expected_ids.intersection(observed_ids)
+
+        if missing_names:
+            logging.error('Missing: %s' % '; '.join(missing_names))
+            test_fail = True
+        if unexpected_names:
+            logging.error('Unexpected: %s' % '; '.join(unexpected_names))
+            test_fail = True
+
+        # For those IDs in both the expected-and-observed, ie, "good":
+        #   Compare sets of expected-vs-actual API permissions, report diffs.
+        #   Do same for host permissions.
+        for good_id in good_ids:
+            baseline = [x for x in combined_baseline if x['id'] == good_id][0]
+            actual = [x for x in observed_extensions if x['id'] == good_id][0]
+            # Check the API permissions.
+            baseline_apis = set(baseline['api_permissions'])
+            actual_apis = set(actual['api_permissions'])
+            missing_apis = baseline_apis - actual_apis
+            unexpected_apis = actual_apis - baseline_apis
+            if missing_apis or unexpected_apis:
+                test_fail = True
+                self._report_attribute_diffs(missing_apis, unexpected_apis,
+                                             actual)
+            # Check the host permissions.
+            baseline_hosts = set(baseline['effective_host_permissions'])
+            actual_hosts = set(actual['effective_host_permissions'])
+            missing_hosts = baseline_hosts - actual_hosts
+            unexpected_hosts = actual_hosts - baseline_hosts
+            if missing_hosts or unexpected_hosts:
+                test_fail = True
+                self._report_attribute_diffs(missing_hosts, unexpected_hosts,
+                                             actual)
+        if test_fail:
+            raise error.TestFail('Bundled extensions mismatch, see error log.')
+
+
+    def _report_attribute_diffs(self, missing, unexpected, rec):
+        logging.error('Problem with %s (%s):' % (rec['name'], rec['id']))
+        if missing:
+            logging.error('It no longer uses: %s' % '; '.join(missing))
+        if unexpected:
+            logging.error('It unexpectedly uses: %s' % '; '.join(unexpected))
 
 
     def run_once(self, mode=None):
@@ -235,16 +167,4 @@
         if self.pyauto.GetBrowserInfo()['properties']['is_official']:
             self._component_extension_baseline.extend(self._official_components)
 
-        # In pyauto this was implemented as 3 different tests, each of
-        # which can raise without stopping execution of the other two,
-        # and each of which was run in its own clean session.
-        # The autotest analogue is to label these in the control file as
-        # 3 distinct runs of the test.
-        if mode == 'ComponentExtensionPermissions':
-            self.test_component_extension_permissions()
-        elif mode == 'BundledCrxPermissions':
-            self.test_bundled_crx_permissions()
-        elif mode == 'NoUnexpectedExtensions':
-            self.test_no_unexpected_extensions()
-        else:
-            error.TestFail('Unimplemented: %s' % mode)
+        self.install_and_compare()