| #!/usr/bin/env python |
| # Copyright 2015 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import copy |
| import feature_compiler |
| import unittest |
| |
| class FeatureCompilerTest(unittest.TestCase): |
| """Test the FeatureCompiler. Note that we test that the expected features are |
| generated more thoroughly in features_generation_unittest.cc. And, of course, |
| this is most exhaustively tested through Chrome's compilation process (if a |
| feature fails to parse, the compile fails). |
| These tests primarily focus on catching errors during parsing. |
| """ |
| def _parseFeature(self, value): |
| """Parses a feature from the given value and returns the result.""" |
| f = feature_compiler.Feature('alpha') |
| f.Parse(value, {}) |
| return f |
| |
| def _createTestFeatureCompiler(self, feature_class): |
| return feature_compiler.FeatureCompiler('chrome_root', [], feature_class, |
| 'provider_class', 'out_root', 'out_base_filename') |
| |
| def _hasError(self, f, error): |
| """Asserts that |error| is present somewhere in the given feature's |
| errors.""" |
| errors = f.GetErrors() |
| self.assertTrue(errors) |
| self.assertNotEqual(-1, str(errors).find(error), str(errors)) |
| |
| def setUp(self): |
| feature_compiler.ENABLE_ASSERTIONS = False |
| feature_compiler.STRINGS_TO_UNICODE = True |
| |
| def testFeature(self): |
| # Test some basic feature parsing for a sanity check. |
| f = self._parseFeature({ |
| 'blacklist': ['aaa', 'bbb'], |
| 'channel': 'stable', |
| 'command_line_switch': 'switch', |
| 'component_extensions_auto_granted': False, |
| 'contexts': [ |
| 'blessed_extension', |
| 'blessed_web_page', |
| 'lock_screen_extension' |
| ], |
| 'default_parent': True, |
| 'dependencies': ['dependency1', 'dependency2'], |
| 'extension_types': ['extension'], |
| 'location': 'component', |
| 'internal': True, |
| 'matches': ['*://*/*'], |
| 'max_manifest_version': 1, |
| 'noparent': True, |
| 'platforms': ['mac', 'win'], |
| 'session_types': ['kiosk', 'regular'], |
| 'whitelist': ['zzz', 'yyy'] |
| }) |
| self.assertFalse(f.GetErrors()) |
| |
| def testInvalidAll(self): |
| f = self._parseFeature({ |
| 'channel': 'stable', |
| 'dependencies': 'all', |
| }) |
| self._hasError(f, 'Illegal value: "all"') |
| |
| def testUnknownKeyError(self): |
| f = self._parseFeature({ |
| 'contexts': ['blessed_extension'], |
| 'channel': 'stable', |
| 'unknownkey': 'unknownvalue' |
| }) |
| self._hasError(f, 'Unrecognized key') |
| |
| def testUnknownEnumValue(self): |
| f = self._parseFeature({ |
| 'contexts': ['blessed_extension', 'unknown_context'], |
| 'channel': 'stable' |
| }) |
| self._hasError(f, 'Illegal value: "unknown_context"') |
| |
| def testImproperType(self): |
| f = self._parseFeature({'min_manifest_version': '1'}) |
| self._hasError(f, 'Illegal value: "1"') |
| |
| def testImproperSubType(self): |
| f = self._parseFeature({'dependencies': [1, 2, 3]}) |
| self._hasError(f, 'Illegal value: "1"') |
| |
| def testImproperValue(self): |
| f = self._parseFeature({'noparent': False}) |
| self._hasError(f, 'Illegal value: "False"') |
| |
| def testApiFeaturesNeedContexts(self): |
| f = self._parseFeature({'dependencies': 'alpha', |
| 'extension_types': ['extension'], |
| 'channel': 'trunk'}) |
| f.Validate('APIFeature', {}) |
| self._hasError(f, 'APIFeatures must specify at least one context') |
| |
| def testManifestFeaturesNeedExtensionTypes(self): |
| f = self._parseFeature({'dependencies': 'alpha', 'channel': 'beta'}) |
| f.Validate('ManifestFeature', {}) |
| self._hasError(f, |
| 'ManifestFeatures must specify at least one extension type') |
| |
| def testManifestFeaturesCantHaveContexts(self): |
| f = self._parseFeature({'dependencies': 'alpha', |
| 'channel': 'beta', |
| 'extension_types': ['extension'], |
| 'contexts': ['blessed_extension']}) |
| f.Validate('ManifestFeature', {}) |
| self._hasError(f, 'ManifestFeatures do not support contexts') |
| |
| def testPermissionFeaturesNeedExtensionTypes(self): |
| f = self._parseFeature({'dependencies': 'alpha', 'channel': 'beta'}) |
| f.Validate('PermissionFeature', {}) |
| self._hasError( |
| f, 'PermissionFeatures must specify at least one extension type') |
| |
| def testPermissionFeaturesCantHaveContexts(self): |
| f = self._parseFeature({'dependencies': 'alpha', |
| 'channel': 'beta', |
| 'extension_types': ['extension'], |
| 'contexts': ['blessed_extension']}) |
| f.Validate('PermissionFeature', {}) |
| self._hasError(f, 'PermissionFeatures do not support contexts') |
| |
| def testAllPermissionsNeedChannelOrDependencies(self): |
| api_feature = self._parseFeature({'contexts': ['blessed_extension']}) |
| api_feature.Validate('APIFeature', {}) |
| self._hasError( |
| api_feature, 'Features must specify either a channel or dependencies') |
| permission_feature = self._parseFeature({'extension_types': ['extension']}) |
| permission_feature.Validate('PermissionFeature', {}) |
| self._hasError(permission_feature, |
| 'Features must specify either a channel or dependencies') |
| manifest_feature = self._parseFeature({'extension_types': ['extension']}) |
| manifest_feature.Validate('ManifestFeature', {}) |
| self._hasError(manifest_feature, |
| 'Features must specify either a channel or dependencies') |
| channel_feature = self._parseFeature({'contexts': ['blessed_extension'], |
| 'channel': 'trunk'}) |
| channel_feature.Validate('APIFeature', {}) |
| self.assertFalse(channel_feature.GetErrors()) |
| dependency_feature = self._parseFeature( |
| {'contexts': ['blessed_extension'], |
| 'dependencies': ['alpha']}) |
| dependency_feature.Validate('APIFeature', {}) |
| self.assertFalse(dependency_feature.GetErrors()) |
| |
| def testBothAliasAndSource(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| compiler._json = { |
| 'feature_alpha': { |
| 'channel': 'beta', |
| 'contexts': ['blessed_extension'], |
| 'alias': 'feature_alpha', |
| 'source': 'feature_alpha' |
| } |
| } |
| compiler.Compile() |
| |
| feature = compiler._features.get('feature_alpha') |
| self.assertTrue(feature) |
| self._hasError(feature, 'Features cannot specify both alias and source.') |
| |
| def testAliasOnNonApiFeature(self): |
| compiler = self._createTestFeatureCompiler('PermissionFeature') |
| compiler._json = { |
| 'feature_alpha': { |
| 'channel': 'beta', |
| 'contexts': ['blessed_extension'], |
| 'alias': 'feature_beta' |
| }, |
| 'feature_beta': [{ |
| 'channel': 'beta', |
| 'contexts': ['blessed_extension'], |
| 'source': 'feature_alpha' |
| },{ |
| 'channel': 'dev', |
| 'context': ['blessed_extension'] |
| }] |
| }; |
| compiler.Compile() |
| |
| feature = compiler._features.get('feature_alpha') |
| self.assertTrue(feature) |
| self._hasError(feature, 'PermissionFeatures do not support alias.') |
| |
| feature = compiler._features.get('feature_beta') |
| self.assertTrue(feature) |
| self._hasError(feature, 'PermissionFeatures do not support source.') |
| |
| def testAliasFeature(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| compiler._json = { |
| 'feature_alpha': { |
| 'channel': 'beta', |
| 'contexts': ['blessed_extension'], |
| 'alias': 'feature_beta' |
| }, |
| 'feature_beta': { |
| 'channel': 'beta', |
| 'contexts': ['blessed_extension'], |
| 'source': 'feature_alpha' |
| } |
| }; |
| compiler.Compile() |
| |
| feature = compiler._features.get('feature_alpha') |
| self.assertTrue(feature) |
| self.assertFalse(feature.GetErrors()) |
| |
| feature = compiler._features.get('feature_beta') |
| self.assertTrue(feature) |
| self.assertFalse(feature.GetErrors()) |
| |
| def testMultipleAliasesInComplexFeature(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| compiler._json = { |
| 'feature_alpha': [{ |
| 'channel': 'beta', |
| 'contexts': ['blessed_extension'], |
| 'alias': 'feature_beta' |
| }, { |
| 'contexts': ['blessed_extension'], |
| 'channel': 'beta', |
| 'alias': 'feature_beta' |
| }] |
| }; |
| compiler.Compile() |
| |
| feature = compiler._features.get('feature_alpha') |
| self.assertTrue(feature) |
| self._hasError(feature, 'Error parsing feature "feature_alpha" at key ' + |
| '"alias": Key can be set at most once per feature.') |
| |
| def testAliasReferenceInComplexFeature(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| compiler._json = { |
| 'feature_alpha': [{ |
| 'channel': 'beta', |
| 'contexts': ['blessed_extension'], |
| 'alias': 'feature_beta' |
| }, { |
| 'contexts': ['blessed_extension'], |
| 'channel': 'beta', |
| }], |
| 'feature_beta': { |
| 'channel': 'beta', |
| 'contexts': ['blessed_extension'], |
| 'source': 'feature_alpha' |
| } |
| }; |
| compiler.Compile() |
| |
| feature = compiler._features.get('feature_alpha') |
| self.assertTrue(feature) |
| self.assertFalse(feature.GetErrors()) |
| |
| feature = compiler._features.get('feature_beta') |
| self.assertTrue(feature) |
| self.assertFalse(feature.GetErrors()) |
| |
| def testSourceMissingReference(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| compiler._json = { |
| 'feature_alpha': { |
| 'channel': 'beta', |
| 'contexts': ['blessed_extension'], |
| 'alias': 'feature_beta' |
| }, |
| 'feature_beta': { |
| 'contexts': ['blessed_extension'], |
| 'channel': 'beta', |
| 'source': 'does_not_exist' |
| } |
| }; |
| compiler.Compile() |
| |
| feature = compiler._features.get('feature_beta') |
| self.assertTrue(feature) |
| self._hasError(feature, 'A feature source property should reference a ' + |
| 'feature whose alias property references it back.') |
| |
| |
| def testAliasMissingReferenceInComplexFeature(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| compiler._json = { |
| 'feature_alpha': [{ |
| 'channel': 'beta', |
| 'contexts': ['blessed_extension'], |
| 'alias': 'feature_beta' |
| }, { |
| 'contexts': ['blessed_extension'], |
| 'channel': 'beta' |
| }] |
| }; |
| compiler.Compile() |
| |
| feature = compiler._features.get('feature_alpha') |
| self.assertTrue(feature) |
| self._hasError(feature, 'A feature alias property should reference a ' + |
| 'feature whose source property references it back.') |
| |
| def testAliasReferenceMissingSourceInComplexFeature(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| compiler._json = { |
| 'feature_alpha': { |
| 'contexts': ['blessed_extension'], |
| 'channel': 'beta', |
| }, |
| 'feature_beta': { |
| 'channel': 'beta', |
| 'contexts': ['blessed_extension'], |
| 'alias': 'feature_alpha' |
| } |
| }; |
| compiler.Compile() |
| |
| feature = compiler._features.get('feature_alpha') |
| self.assertTrue(feature) |
| self.assertFalse(feature.GetErrors()) |
| |
| feature = compiler._features.get('feature_beta') |
| self.assertTrue(feature) |
| self._hasError(feature, 'A feature alias property should reference a ' + |
| 'feature whose source property references it back.') |
| |
| def testComplexParentWithoutDefaultParent(self): |
| c = feature_compiler.FeatureCompiler( |
| None, None, 'APIFeature', None, None, None) |
| c._CompileFeature('bookmarks', |
| [{ |
| 'contexts': ['blessed_extension'], |
| }, { |
| 'channel': 'stable', |
| 'contexts': ['webui'], |
| }]) |
| |
| with self.assertRaisesRegexp(AssertionError, |
| 'No default parent found for bookmarks'): |
| c._CompileFeature('bookmarks.export', { "whitelist": ["asdf"] }) |
| |
| def testRealIdsDisallowedInWhitelist(self): |
| fake_id = 'a' * 32; |
| f = self._parseFeature({'whitelist': [fake_id], |
| 'extension_types': ['extension'], |
| 'channel': 'beta'}) |
| f.Validate('PermissionFeature', {}) |
| self._hasError( |
| f, 'list should only have hex-encoded SHA1 hashes of extension ids') |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |