| #!/usr/bin/env python3 |
| # Copyright 2015 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import os |
| |
| 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', |
| 'gen', |
| '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 |
| |
| def testFeature(self): |
| # Test some basic feature parsing for a sanity check. |
| f = self._parseFeature( |
| { |
| 'blocklist': [ |
| 'ABCDEF0123456789ABCDEF0123456789ABCDEF01', |
| '10FEDCBA9876543210FEDCBA9876543210FEDCBA', |
| ], |
| 'channel': 'stable', |
| 'command_line_switch': 'switch', |
| 'component_extensions_auto_granted': False, |
| 'contexts': [ |
| 'privileged_extension', |
| 'privileged_web_page', |
| ], |
| 'default_parent': True, |
| 'dependencies': ['dependency1', 'dependency2'], |
| 'developer_mode_only': True, |
| 'disallow_for_service_workers': True, |
| 'extension_types': ['extension'], |
| 'location': 'component', |
| 'internal': True, |
| 'matches': ['*://*/*'], |
| 'max_manifest_version': 1, |
| 'requires_delegated_availability_check': True, |
| 'noparent': True, |
| 'platforms': ['mac', 'win'], |
| 'session_types': ['kiosk', 'regular'], |
| 'allowlist': [ |
| '0123456789ABCDEF0123456789ABCDEF01234567', |
| '76543210FEDCBA9876543210FEDCBA9876543210', |
| ], |
| 'required_buildflags': ['use_cups'], |
| } |
| ) |
| self.assertFalse(f.GetErrors()) |
| |
| def testInvalidAll(self): |
| f = self._parseFeature( |
| { |
| 'channel': 'stable', |
| 'dependencies': 'all', |
| } |
| ) |
| self._hasError(f, 'Illegal value: "all"') |
| |
| def testInvalidChannel(self): |
| f = self._parseFeature( |
| {'contexts': ['privileged_extension'], 'channel': 'invalid_channel'} |
| ) |
| self._hasError(f, 'Illegal value: "invalid_channel"') |
| |
| def testUnknownKeyError(self): |
| f = self._parseFeature( |
| { |
| 'contexts': ['privileged_extension'], |
| 'channel': 'stable', |
| 'unknownkey': 'unknownvalue', |
| } |
| ) |
| self._hasError(f, 'Unrecognized key') |
| |
| def testUnknownEnumValue(self): |
| f = self._parseFeature( |
| { |
| 'contexts': ['privileged_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 testEmptyList(self): |
| f = self._parseFeature({'extension_types': []}) |
| self._hasError(f, 'List must specify at least one element.') |
| |
| def testEmptyListWithAllowEmpty(self): |
| # `dependencies` is the only key that allows an empty list. |
| f = self._parseFeature({'dependencies': []}) |
| self.assertFalse(f.GetErrors()) |
| |
| def testApiFeaturesNeedContexts(self): |
| f = self._parseFeature( |
| {'extension_types': ['extension'], 'channel': 'trunk'} |
| ) |
| f.Validate('APIFeature', {}) |
| self._hasError(f, 'APIFeatures must specify the contexts property') |
| |
| def testAPIFeaturesCanSpecifyEmptyContexts(self): |
| f = self._parseFeature( |
| {'extension_types': ['extension'], 'channel': 'trunk', 'contexts': []} |
| ) |
| f.Validate('APIFeature', {}) |
| self.assertFalse(f.GetErrors()) |
| |
| 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': ['privileged_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': ['privileged_extension'], |
| } |
| ) |
| f.Validate('PermissionFeature', {}) |
| self._hasError(f, 'PermissionFeatures do not support contexts') |
| |
| def testAllPermissionsNeedChannelOrDependencies(self): |
| api_feature = self._parseFeature({'contexts': ['privileged_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': ['privileged_extension'], 'channel': 'trunk'} |
| ) |
| channel_feature.Validate('APIFeature', {}) |
| self.assertFalse(channel_feature.GetErrors()) |
| dependency_feature = self._parseFeature( |
| {'contexts': ['privileged_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': ['privileged_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': ['privileged_extension'], |
| 'alias': 'feature_beta', |
| }, |
| 'feature_beta': [ |
| { |
| 'channel': 'beta', |
| 'contexts': ['privileged_extension'], |
| 'source': 'feature_alpha', |
| }, |
| {'channel': 'dev', 'context': ['privileged_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': ['privileged_extension'], |
| 'alias': 'feature_beta', |
| }, |
| 'feature_beta': { |
| 'channel': 'beta', |
| 'contexts': ['privileged_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': ['privileged_extension'], |
| 'alias': 'feature_beta', |
| }, |
| { |
| 'contexts': ['privileged_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': ['privileged_extension'], |
| 'alias': 'feature_beta', |
| }, |
| { |
| 'contexts': ['privileged_extension'], |
| 'channel': 'beta', |
| }, |
| ], |
| 'feature_beta': { |
| 'channel': 'beta', |
| 'contexts': ['privileged_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': ['privileged_extension'], |
| 'alias': 'feature_beta', |
| }, |
| 'feature_beta': { |
| 'contexts': ['privileged_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': ['privileged_extension'], |
| 'alias': 'feature_beta', |
| }, |
| {'contexts': ['privileged_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': ['privileged_extension'], |
| 'channel': 'beta', |
| }, |
| 'feature_beta': { |
| 'channel': 'beta', |
| 'contexts': ['privileged_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, None |
| ) |
| c._CompileFeature( |
| 'bookmarks', |
| [ |
| { |
| 'contexts': ['privileged_extension'], |
| }, |
| { |
| 'channel': 'stable', |
| 'contexts': ['webui'], |
| }, |
| ], |
| ) |
| |
| with self.assertRaisesRegex( |
| AssertionError, 'No default parent found for bookmarks' |
| ): |
| c._CompileFeature('bookmarks.export', {"allowlist": ["asdf"]}) |
| |
| def testComplexFeatureWithSinglePropertyBlock(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| |
| error = ( |
| 'Error parsing feature "feature_alpha": A complex feature ' |
| 'definition is only needed when there are multiple objects ' |
| 'specifying different groups of properties for feature ' |
| 'availability. You can reduce it down to a single object on the ' |
| 'feature key instead of a list.' |
| ) |
| with self.assertRaisesRegex(AssertionError, error): |
| compiler._CompileFeature( |
| 'feature_alpha', |
| [ |
| { |
| 'contexts': ['privileged_extension'], |
| 'channel': 'stable', |
| } |
| ], |
| ) |
| |
| def testRealIdsDisallowedInAllowlist(self): |
| fake_id = 'a' * 32 |
| f = self._parseFeature( |
| { |
| 'allowlist': [fake_id], |
| 'extension_types': ['extension'], |
| 'channel': 'beta', |
| } |
| ) |
| f.Validate('PermissionFeature', {}) |
| self._hasError( |
| f, 'list should only have hex-encoded SHA1 hashes of extension ids' |
| ) |
| |
| def testHostedAppsCantUseAllowlistedFeatures_SimpleFeature(self): |
| f = self._parseFeature( |
| { |
| 'extension_types': ['extension', 'hosted_app'], |
| 'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'], |
| 'channel': 'beta', |
| } |
| ) |
| f.Validate('PermissionFeature', {}) |
| self._hasError(f, 'Hosted apps are not allowed to use restricted features') |
| |
| def testHostedAppsCantUseAllowlistedFeatures_ComplexFeature(self): |
| c = feature_compiler.FeatureCompiler( |
| None, None, 'PermissionFeature', None, None, None, None |
| ) |
| c._CompileFeature( |
| 'invalid_feature', |
| [ |
| { |
| 'extension_types': ['extension'], |
| 'channel': 'beta', |
| }, |
| { |
| 'channel': 'beta', |
| 'extension_types': ['hosted_app'], |
| 'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'], |
| }, |
| ], |
| ) |
| c._CompileFeature( |
| 'valid_feature', |
| [ |
| { |
| 'extension_types': ['extension'], |
| 'channel': 'beta', |
| 'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'], |
| }, |
| { |
| 'channel': 'beta', |
| 'extension_types': ['hosted_app'], |
| }, |
| ], |
| ) |
| |
| valid_feature = c._features.get('valid_feature') |
| self.assertTrue(valid_feature) |
| self.assertFalse(valid_feature.GetErrors()) |
| |
| invalid_feature = c._features.get('invalid_feature') |
| self.assertTrue(invalid_feature) |
| self._hasError( |
| invalid_feature, 'Hosted apps are not allowed to use restricted features' |
| ) |
| |
| def testHostedAppsCantUseAllowlistedFeatures_ChildFeature(self): |
| c = feature_compiler.FeatureCompiler( |
| None, None, 'PermissionFeature', None, None, None, None |
| ) |
| c._CompileFeature( |
| 'parent', |
| { |
| 'extension_types': ['hosted_app'], |
| 'channel': 'beta', |
| }, |
| ) |
| |
| c._CompileFeature( |
| 'parent.child', |
| {'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567']}, |
| ) |
| feature = c._features.get('parent.child') |
| self.assertTrue(feature) |
| self._hasError( |
| feature, 'Hosted apps are not allowed to use restricted features' |
| ) |
| |
| def testEmptyContextsDisallowed(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| compiler._json = { |
| 'feature_alpha': { |
| 'channel': 'beta', |
| 'contexts': [], |
| 'extension_types': ['extension'], |
| } |
| } |
| compiler.Compile() |
| |
| feature = compiler._features.get('feature_alpha') |
| self.assertTrue(feature) |
| self._hasError( |
| feature, 'An empty contexts list is not allowed for this feature.' |
| ) |
| |
| def testEmptyContextsAllowed(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| compiler._json = { |
| 'empty_contexts': { |
| 'channel': 'beta', |
| 'contexts': [], |
| 'extension_types': ['extension'], |
| } |
| } |
| compiler.Compile() |
| |
| feature = compiler._features.get('empty_contexts') |
| self.assertTrue(feature) |
| self.assertFalse(feature.GetErrors()) |
| |
| def testFeatureHiddenBehindBuildflag(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| |
| compiler._json = { |
| 'feature_cups': { |
| 'channel': 'beta', |
| 'command_line_switch': 'enable-cups', |
| 'contexts': ['privileged_extension'], |
| 'extension_types': ['extension'], |
| 'feature_flag': 'ApiCups', |
| 'matches': ['https://example.com/*'], |
| 'required_buildflags': ['use_cups'], |
| } |
| } |
| compiler.Compile() |
| cc_code = compiler.Render() |
| |
| # The code below is formatted correctly! |
| self.assertEqual( |
| cc_code.Render(), |
| ''' { |
| #if BUILDFLAG(USE_CUPS) |
| SimpleFeature* feature = new SimpleFeature(); |
| feature->set_name(StaticStringView("feature_cups")); |
| feature->set_channel(version_info::Channel::BETA); |
| feature->set_command_line_switch(StaticCString("enable-cups")); |
| static constexpr auto kContexts = |
| std::to_array<mojom::ContextType>( |
| {mojom::ContextType::kPrivilegedExtension}); |
| feature->set_contexts(StaticSpan(kContexts)); |
| static constexpr auto kExtensionTypes = |
| std::to_array<Manifest::Type>( |
| {Manifest::Type::kExtension}); |
| feature->set_extension_types(StaticSpan(kExtensionTypes)); |
| feature->set_feature_flag(StaticCString("ApiCups")); |
| static constexpr auto kMatches = |
| std::to_array<std::string_view>( |
| {"https://example.com/*"}); |
| feature->set_matches(StaticSpan(kMatches)); |
| provider->AddFeature("feature_cups", feature); |
| #endif |
| }''', |
| ) |
| |
| def testFeatureIdentityStringsUseStaticStorage(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| compiler._json = { |
| 'feature_alpha': { |
| 'alias': 'feature_beta', |
| 'contexts': ['privileged_extension'] |
| }, |
| 'feature_beta': { |
| 'contexts': ['privileged_extension'], |
| 'source': 'feature_alpha' |
| } |
| } |
| compiler.Compile() |
| cc_code = compiler.Render().Render() |
| |
| # The code below is formatted correctly! |
| self.assertEqual( |
| cc_code, ''' { |
| SimpleFeature* feature = new SimpleFeature(); |
| feature->set_name(StaticStringView("feature_alpha")); |
| feature->set_alias(StaticCString("feature_beta")); |
| static constexpr auto kContexts = |
| std::to_array<mojom::ContextType>( |
| {mojom::ContextType::kPrivilegedExtension}); |
| feature->set_contexts(StaticSpan(kContexts)); |
| provider->AddFeature("feature_alpha", feature); |
| } |
| { |
| SimpleFeature* feature = new SimpleFeature(); |
| feature->set_name(StaticStringView("feature_beta")); |
| static constexpr auto kContexts = |
| std::to_array<mojom::ContextType>( |
| {mojom::ContextType::kPrivilegedExtension}); |
| feature->set_contexts(StaticSpan(kContexts)); |
| feature->set_source(StaticCString("feature_alpha")); |
| provider->AddFeature("feature_beta", feature); |
| }''') |
| |
| def testFeatureWithEmptyMatches(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| compiler._json = { |
| 'empty_matches': { |
| 'channel': 'beta', |
| 'contexts': ['privileged_extension'], |
| 'extension_types': ['extension'], |
| 'matches': [], |
| } |
| } |
| compiler.Compile() |
| cc_code = compiler.Render().Render() |
| |
| self.assertNotIn('set_matches', cc_code) |
| self.assertNotIn('kMatches', cc_code) |
| |
| def testEmptyDependenciesAreSkipped(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| |
| compiler._json = { |
| 'empty_dependencies': { |
| 'channel': 'beta', |
| 'contexts': ['privileged_extension'], |
| 'dependencies': [], |
| } |
| } |
| compiler.Compile() |
| cc_code = compiler.Render().Render() |
| |
| # An empty list needs no setter call; the member already defaults to an |
| # empty span, and a zero-length array cannot form a span. |
| self.assertNotIn('set_dependencies', cc_code) |
| self.assertNotIn('kDependencies', cc_code) |
| |
| def testEmptyContextsStillCallsSetter(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| |
| compiler._json = {'empty_contexts': {'channel': 'beta', 'contexts': []}} |
| compiler.Compile() |
| cc_code = compiler.Render().Render() |
| |
| # Empty contexts means unavailable in every context, not unrestricted. |
| self.assertIn( |
| 'feature->set_contexts(StaticSpan<mojom::ContextType>());', cc_code |
| ) |
| self.assertNotIn('kContexts', cc_code) |
| |
| def testEnumListEmitsStaticArray(self): |
| compiler = self._createTestFeatureCompiler('APIFeature') |
| |
| compiler._json = { |
| 'enum_lists': { |
| 'channel': 'beta', |
| 'contexts': ['privileged_extension'], |
| 'extension_types': ['extension'], |
| 'platforms': ['chromeos'], |
| 'session_types': ['regular'], |
| } |
| } |
| compiler.Compile() |
| |
| # The code below is formatted correctly! |
| self.assertEqual( |
| compiler.Render().Render(), |
| ''' { |
| SimpleFeature* feature = new SimpleFeature(); |
| feature->set_name(StaticStringView("enum_lists")); |
| feature->set_channel(version_info::Channel::BETA); |
| static constexpr auto kContexts = |
| std::to_array<mojom::ContextType>( |
| {mojom::ContextType::kPrivilegedExtension}); |
| feature->set_contexts(StaticSpan(kContexts)); |
| static constexpr auto kExtensionTypes = |
| std::to_array<Manifest::Type>( |
| {Manifest::Type::kExtension}); |
| feature->set_extension_types(StaticSpan(kExtensionTypes)); |
| static constexpr auto kPlatforms = |
| std::to_array<Feature::Platform>( |
| {Feature::CHROMEOS_PLATFORM}); |
| feature->set_platforms(StaticSpan(kPlatforms)); |
| static constexpr auto kSessionTypes = |
| std::to_array<mojom::FeatureSessionType>( |
| {mojom::FeatureSessionType::kRegular}); |
| feature->set_session_types(StaticSpan(kSessionTypes)); |
| provider->AddFeature("enum_lists", feature); |
| }''', |
| ) |
| |
| def testEmptyNonContextEnumListsSkipSetters(self): |
| # Call the emitter directly because the grammar rejects empty lists for |
| # these keys before code generation. |
| cc_code = feature_compiler.GetCodeForFeatureValues( |
| {'extension_types': '{}', 'platforms': '{}', 'session_types': '{}'} |
| ).Render() |
| |
| self.assertNotIn('set_extension_types', cc_code) |
| self.assertNotIn('set_platforms', cc_code) |
| self.assertNotIn('set_session_types', cc_code) |
| |
| def testOverrideFeature(self): |
| current_directory = os.path.dirname(os.path.abspath(__file__)) |
| source_files = ['test/_test_api_features.json'] |
| compiler = feature_compiler.FeatureCompiler( |
| current_directory, |
| source_files, |
| 'APIFeature', |
| 'provider_class', |
| 'out_root', |
| 'gen', |
| 'out_base_filename', |
| ) |
| |
| compiler.Load() |
| compiler.Compile() |
| |
| # The original _test_api_features.json file defines this feature as |
| # available on beta, but it's overridden by the |
| # _test_api_features.override.json file to be available only on trunk. The |
| # override takes precedence in the compiled feature. |
| feature = compiler._features.get('feature') |
| self.assertTrue(feature) |
| self.assertFalse(feature.GetErrors()) |
| self.assertEqual( |
| 'version_info::Channel::UNKNOWN', feature.GetValue('channel') |
| ) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |