def test_env_type_int(self): options = Options.create(env={'PANTS_FOO_BAR': "['123','456']"}, config=self._create_config({}), known_scope_infos=OptionsTest._known_scope_infos, args=shlex.split('./pants'), option_tracker=OptionTracker()) options.register(GLOBAL_SCOPE, '--foo-bar', type=list, member_type=int) self.assertEqual([123, 456], options.for_global_scope().foo_bar) options = Options.create(env={'PANTS_FOO_BAR': '123'}, config=self._create_config({}), known_scope_infos=OptionsTest._known_scope_infos, args=shlex.split('./pants'), option_tracker=OptionTracker()) options.register(GLOBAL_SCOPE, '--foo-bar', type=int) self.assertEqual(123, options.for_global_scope().foo_bar)
def bootstrap_options_from_config(config): bootstrap_options = Options.create(env=self._env, config=config, known_scope_infos=[GlobalOptionsRegistrar.get_scope_info()], args=bargs) def register_global(*args, **kwargs): bootstrap_options.register(GLOBAL_SCOPE, *args, **kwargs) GlobalOptionsRegistrar.register_bootstrap_options(register_global) return bootstrap_options
def test_double_registration(self): options = Options.create(env={}, config=self._create_config({}), known_scope_infos=OptionsTest._known_scope_infos, args=shlex.split('./pants'), option_tracker=OptionTracker()) options.register(GLOBAL_SCOPE, '--foo-bar') self.assertRaises(OptionAlreadyRegistered, lambda: options.register(GLOBAL_SCOPE, '--foo-bar'))
def _full_options(self, known_scope_infos): bootstrap_option_values = self.get_bootstrap_options( ).for_global_scope() return Options.create(self.env, self.config, known_scope_infos, args=self.args, bootstrap_option_values=bootstrap_option_values)
def test_deprecated_option_past_removal(self): with self.assertRaises(PastRemovalVersionError): options = Options.create(env={}, config=self._create_config({}), known_scope_infos=OptionsTest._known_scope_infos, args="./pants") options.register(GLOBAL_SCOPE, '--too-old-option', deprecated_version='0.0.24', deprecated_hint='The semver for this option has already passed.')
def test_frozen_registration(self): options = Options.create(args=[], env={}, config=self._create_config({}), known_scope_infos=[task('foo')], option_tracker=OptionTracker()) options.register('foo', '--arg1') with self.assertRaises(FrozenRegistration): options.register(GLOBAL_SCOPE, '--arg2')
def _parse(self, args_str, env=None, config=None, bootstrap_option_values=None): args = shlex.split(str(args_str)) options = Options.create(env=env or {}, config=self._create_config(config or {}), known_scope_infos=OptionsTest._known_scope_infos, args=args, bootstrap_option_values=bootstrap_option_values) self._register(options) return options
def assertError(expected_error, *args, **kwargs): with self.assertRaises(expected_error): options = Options.create(args=[], env={}, config=self._create_config({}), known_scope_infos=[], option_tracker=OptionTracker()) options.register(GLOBAL_SCOPE, *args, **kwargs) options.for_global_scope()
def _parse(self, args_str, env=None, config=None, bootstrap_option_values=None): args = shlex.split(str(args_str)) options = Options.create(env=env or {}, config=self._create_config(config or {}), known_scope_infos=OptionsTest._known_scope_infos, args=args, bootstrap_option_values=bootstrap_option_values, option_tracker=OptionTracker()) self._register(options) return options
def _parse_type_int(self, args_str, env=None, config=None, bootstrap_option_values=None, action=None): args = shlex.split(str(args_str)) options = Options.create(env=env or {}, config=self._create_config(config or {}), known_scope_infos=OptionsTest._known_scope_infos, args=args, bootstrap_option_values=bootstrap_option_values) options.register(GLOBAL_SCOPE, '--config-override', action=action, type=int) return options
def test_deprecated_option_past_removal(self): with self.assertRaises(PastRemovalVersionError): options = Options.create(env={}, config=self._create_config({}), known_scope_infos=OptionsTest._known_scope_infos, args='./pants', option_tracker=OptionTracker()) options.register(GLOBAL_SCOPE, '--too-old-option', deprecated_version='0.0.24', deprecated_hint='The semver for this option has already passed.') options.for_global_scope()
def test_deprecated_option_past_removal(self): with self.assertRaises(PastRemovalVersionError): options = Options.create({}, FakeConfig({}), OptionsTest._known_scope_infos, "./pants") options.register( GLOBAL_SCOPE, '--too-old-option', deprecated_version='0.0.24', deprecated_hint='The semver for this option has already passed.' )
def test_shadowing(self): options = Options.create( env={}, config=self._create_config({}), known_scope_infos=[task("foo")], args="./pants", option_tracker=OptionTracker(), ) options.register("", "--bar") with self.assertRaises(RegistrationError): options.register("foo", "--bar")
def _parse_type_int(self, args_str, env=None, config=None, bootstrap_option_values=None, action=None): args = shlex.split(str(args_str)) options = Options.create(env=env or {}, config=self._create_config(config or {}), known_scope_infos=OptionsTest._known_scope_infos, args=args, bootstrap_option_values=bootstrap_option_values, option_tracker=OptionTracker()) options.register(GLOBAL_SCOPE, '--config-override', action=action, type=int) return options
def parse_bootstrap_options( env: Mapping[str, str], args: Sequence[str], config: Config ) -> Options: bootstrap_options = Options.create( env=env, config=config, known_scope_infos=[GlobalOptions.get_scope_info()], args=args, ) def register_global(*args, **kwargs): # Only use of Options.register? bootstrap_options.register(GLOBAL_SCOPE, *args, **kwargs) GlobalOptions.register_bootstrap_options(register_global) return bootstrap_options
def test_register_options_blessed(caplog) -> None: class GoodToGo(Subsystem): options_scope = "good-to-go" options = Options.create( env={}, config=Config.load([]), known_scope_infos=[GoodToGo.get_scope_info()], args=["./pants"], bootstrap_option_values=None, ) GoodToGo.register_options_on_scope(options) assert not caplog.records, "The current blessed means of registering options should never warn."
def parse_bootstrap_options(env, args, config): bootstrap_options = Options.create( env=env, config=config, known_scope_infos=[GlobalOptionsRegistrar.get_scope_info()], args=args, ) def register_global(*args, **kwargs): ## Only use of Options.register? bootstrap_options.register(GLOBAL_SCOPE, *args, **kwargs) GlobalOptionsRegistrar.register_bootstrap_options(register_global) return bootstrap_options
def _parse(self, args_str, env=None, config=None, bootstrap_option_values=None): args = shlex.split(str(args_str)) options = Options.create( env or {}, FakeConfig(config or {}), OptionsTest._known_scope_infos, args, bootstrap_option_values=bootstrap_option_values) self._register(options) return options
def parse_bootstrap_options(env: Mapping[str, str], args: Sequence[str], config: Config) -> Options: bootstrap_options = Options.create( env=env, config=config, known_scope_infos=[GlobalOptions.get_scope_info()], args=args, ) for options_info in collect_options_info(BootstrapOptions): # Only use of Options.register? bootstrap_options.register(GLOBAL_SCOPE, *options_info.flag_names, **options_info.flag_options) return bootstrap_options
def test_no_recursive_subsystem_options(self): options = Options.create(env={}, config=self._create_config({}), known_scope_infos=[subsystem('foo')], args='./pants', option_tracker=OptionTracker()) # All subsystem options are implicitly recursive (a subscope of subsystem scope represents # a separate instance of the subsystem, so it needs all the options). # We disallow explicit specification of recursive (even if set to True), to avoid confusion. with self.assertRaises(RecursiveSubsystemOption): options.register('foo', '--bar', recursive=False) options.for_scope('foo') with self.assertRaises(RecursiveSubsystemOption): options.register('foo', '--baz', recursive=True) options.for_scope('foo')
def _full_options(self, known_scope_infos): bootstrap_option_values = self.get_bootstrap_options().for_global_scope() options = Options.create(self.env, self.config, known_scope_infos, args=self.args, bootstrap_option_values=bootstrap_option_values) distinct_optionable_classes = set() for ksi in sorted(known_scope_infos, key=lambda si: si.scope): if not ksi.optionable_cls or ksi.optionable_cls in distinct_optionable_classes: continue distinct_optionable_classes.add(ksi.optionable_cls) ksi.optionable_cls.register_options_on_scope(options) return options
def parse_bootstrap_options(env: Mapping[str, str], args: Sequence[str], config: Config) -> Options: bootstrap_options = Options.create( env=env, config=config, known_scope_infos=[GlobalOptions.get_scope_info()], args=args, ) def register_global(*args, **kwargs): ## Only use of Options.register? bootstrap_options.register(GLOBAL_SCOPE, *args, **kwargs) GlobalOptions.register_bootstrap_options(register_global) opts = bootstrap_options.for_global_scope() if opts.v2 and not opts.v1 and opts.backend_packages == []: is_v2_exclusive.set() return bootstrap_options
def _full_options(self, known_scope_infos: FrozenOrderedSet[ScopeInfo]) -> Options: bootstrap_option_values = self.get_bootstrap_options().for_global_scope() options = Options.create( self.env, self.config, known_scope_infos, args=self.args, bootstrap_option_values=bootstrap_option_values, ) distinct_optionable_classes: Set[Type[Optionable]] = set() for ksi in known_scope_infos: if not ksi.optionable_cls or ksi.optionable_cls in distinct_optionable_classes: continue distinct_optionable_classes.add(ksi.optionable_cls) ksi.optionable_cls.register_options_on_scope(options) return options
def test_shadowing(self): options = Options.create(env={}, config=self._create_config({}), known_scope_infos=[task('bar'), intermediate('foo'), task('foo.bar')], args='./pants', option_tracker=OptionTracker()) options.register('', '--opt1') options.register('foo', '-o', '--opt2') with self.assertRaises(Shadowing): options.register('bar', '--opt1') with self.assertRaises(Shadowing): options.register('foo.bar', '--opt1') with self.assertRaises(Shadowing): options.register('foo.bar', '--opt2') with self.assertRaises(Shadowing): options.register('foo.bar', '--opt1', '--opt3') with self.assertRaises(Shadowing): options.register('foo.bar', '--opt3', '--opt2')
def get_full_options(self, known_scope_infos): """Get the full Options instance bootstrapped by this object for the given known scopes. :param known_scope_infos: ScopeInfos for all scopes that may be encountered. :returns: A bootrapped Options instance that also carries options for all the supplied known scopes. :rtype: :class:`Options` """ key = frozenset(sorted(known_scope_infos)) if key not in self._full_options: # Note: Don't inline this into the Options() call, as this populates # self._post_bootstrap_config, which is another argument to that call. bootstrap_option_values = self.get_bootstrap_options().for_global_scope() self._full_options[key] = Options.create(self._env, self._post_bootstrap_config, known_scope_infos, args=self._args, bootstrap_option_values=bootstrap_option_values) return self._full_options[key]
def _full_options( self, known_scope_infos: FrozenOrderedSet[ScopeInfo], allow_unknown_options: bool = False ) -> Options: bootstrap_option_values = self.get_bootstrap_options().for_global_scope() options = Options.create( self.env, self.config, known_scope_infos, args=self.args, bootstrap_option_values=bootstrap_option_values, allow_unknown_options=allow_unknown_options, ) distinct_subsystem_classes: set[type[Subsystem]] = set() for ksi in known_scope_infos: if not ksi.subsystem_cls or ksi.subsystem_cls in distinct_subsystem_classes: continue distinct_subsystem_classes.add(ksi.subsystem_cls) ksi.subsystem_cls.register_options_on_scope(options) return options
def test_option_tracker_required(self): with self.assertRaises(Options.OptionTrackerRequiredError): Options.create(None, None, [])
def test_get_all_help_info(): class Global(Subsystem): options_scope = GLOBAL_SCOPE help = "Global options." opt1 = IntOption("--opt1", default=42, help="Option 1") # This is special in having a short option `-l`. Make sure it works. level = LogLevelOption() class Foo(Subsystem): options_scope = "foo" help = "A foo." opt2 = BoolOption("--opt2", default=True, advanced=True, help="Option 2") class Bar(GoalSubsystem): name = "bar" help = "The bar goal." deprecated_options_scope = "bar-old" deprecated_options_scope_removal_version = "9.9.999" class QuxField(StringField): alias = "qux" default = "blahblah" help = "A qux string." class QuuxField(IntField): alias = "quux" required = True help = "A quux int.\n\nMust be non-zero. Or zero. Whatever you like really." class BazLibrary(Target): alias = "baz_library" help = "A library of baz-es.\n\nUse it however you like." core_fields = [QuxField, QuuxField] options = Options.create( env={}, config=Config.load([]), known_scope_infos=[ Global.get_scope_info(), Foo.get_scope_info(), Bar.get_scope_info() ], args=["./pants"], bootstrap_option_values=None, ) Global.register_options_on_scope(options) Foo.register_options_on_scope(options) Bar.register_options_on_scope(options) @rule def rule_info_test(foo: Foo) -> Target: """This rule is for testing info extraction only.""" def fake_consumed_scopes_mapper(scope: str) -> Tuple[str, ...]: return ("somescope", f"used_by_{scope or 'GLOBAL_SCOPE'}") bc_builder = BuildConfiguration.Builder() bc_builder.register_subsystems("help_info_extracter_test", (Foo, Bar)) bc_builder.register_target_types("help_info_extracter_test", (BazLibrary, )) bc_builder.register_rules("help_info_extracter_test", collect_rules(locals())) all_help_info = HelpInfoExtracter.get_all_help_info( options, UnionMembership({}), fake_consumed_scopes_mapper, RegisteredTargetTypes({BazLibrary.alias: BazLibrary}), bc_builder.create(), ) all_help_info_dict = all_help_info.asdict() expected_all_help_info_dict = { "scope_to_help_info": { GLOBAL_SCOPE: { "scope": GLOBAL_SCOPE, "description": "Global options.", "provider": "", "is_goal": False, "deprecated_scope": None, "basic": ( { "display_args": ("--opt1=<int>", ), "comma_separated_display_args": "--opt1=<int>", "scoped_cmd_line_args": ("--opt1", ), "unscoped_cmd_line_args": ("--opt1", ), "config_key": "opt1", "env_var": "PANTS_OPT1", "value_history": { "ranked_values": ( { "rank": Rank.NONE, "value": None, "details": None }, { "rank": Rank.HARDCODED, "value": 42, "details": None }, ), }, "typ": int, "default": 42, "help": "Option 1", "deprecation_active": False, "deprecated_message": None, "removal_version": None, "removal_hint": None, "choices": None, "comma_separated_choices": None, }, { "display_args": ("-l=<LogLevel>", "--level=<LogLevel>"), "comma_separated_display_args": "-l=<LogLevel>, --level=<LogLevel>", "scoped_cmd_line_args": ("-l", "--level"), "unscoped_cmd_line_args": ("-l", "--level"), "config_key": "level", "env_var": "PANTS_LEVEL", "value_history": { "ranked_values": ( { "rank": Rank.NONE, "value": None, "details": None }, { "rank": Rank.HARDCODED, "value": LogLevel.INFO, "details": None }, ), }, "typ": LogLevel, "default": LogLevel.INFO, "help": "Set the logging level.", "deprecation_active": False, "deprecated_message": None, "removal_version": None, "removal_hint": None, "choices": ("trace", "debug", "info", "warn", "error"), "comma_separated_choices": "trace, debug, info, warn, error", }, ), "advanced": tuple(), "deprecated": tuple(), }, "foo": { "scope": "foo", "provider": "help_info_extracter_test", "description": "A foo.", "is_goal": False, "deprecated_scope": None, "basic": (), "advanced": ({ "display_args": ("--[no-]foo-opt2", ), "comma_separated_display_args": "--[no-]foo-opt2", "scoped_cmd_line_args": ("--foo-opt2", "--no-foo-opt2"), "unscoped_cmd_line_args": ("--opt2", "--no-opt2"), "config_key": "opt2", "env_var": "PANTS_FOO_OPT2", "value_history": { "ranked_values": ( { "rank": Rank.NONE, "value": None, "details": None }, { "rank": Rank.HARDCODED, "value": True, "details": None }, ), }, "typ": bool, "default": True, "help": "Option 2", "deprecation_active": False, "deprecated_message": None, "removal_version": None, "removal_hint": None, "choices": None, "comma_separated_choices": None, }, ), "deprecated": tuple(), }, "bar": { "scope": "bar", "provider": "help_info_extracter_test", "description": "The bar goal.", "is_goal": True, "deprecated_scope": "bar-old", "basic": tuple(), "advanced": tuple(), "deprecated": tuple(), }, "bar-old": { "scope": "bar-old", "provider": "help_info_extracter_test", "description": "The bar goal.", "is_goal": True, "deprecated_scope": "bar-old", "basic": tuple(), "advanced": tuple(), "deprecated": tuple(), }, }, "name_to_goal_info": { "bar": { "name": "bar", "provider": "help_info_extracter_test", "description": "The bar goal.", "consumed_scopes": ("somescope", "used_by_bar"), "is_implemented": True, }, "bar-old": { "name": "bar", "provider": "help_info_extracter_test", "description": "The bar goal.", "consumed_scopes": ("somescope", "used_by_bar-old"), "is_implemented": True, }, }, "name_to_target_type_info": { "baz_library": { "alias": "baz_library", "provider": "help_info_extracter_test", "summary": "A library of baz-es.", "description": "A library of baz-es.\n\nUse it however you like.", "fields": ( { "alias": "qux", "provider": "", "default": "'blahblah'", "description": "A qux string.", "required": False, "type_hint": "str | None", }, { "alias": "quux", "provider": "", "default": None, "description": "A quux int.\n\nMust be non-zero. Or zero. " "Whatever you like really.", "required": True, "type_hint": "int", }, ), } }, "name_to_rule_info": { "construct_scope_foo": { "description": None, "documentation": "A foo.", "input_gets": ("Get(ScopedOptions, Scope, ..)", ), "input_types": (), "name": "construct_scope_foo", "output_type": "Foo", "provider": "help_info_extracter_test", }, "pants.help.help_info_extracter_test.test_get_all_help_info.rule_info_test": { "description": None, "documentation": "This rule is for testing info extraction only.", "input_gets": (), "input_types": ("Foo", ), "name": "pants.help.help_info_extracter_test.test_get_all_help_info.rule_info_test", "output_type": "Target", "provider": "help_info_extracter_test", }, }, "name_to_api_type_info": { "pants.help.help_info_extracter_test.Foo": { "consumed_by_rules": ("pants.help.help_info_extracter_test.test_get_all_help_info.rule_info_test", ), "dependees": ("help_info_extracter_test", ), "dependencies": ("pants.option.scope", ), "documentation": None, "is_union": False, "module": "pants.help.help_info_extracter_test", "name": "Foo", "provider": "help_info_extracter_test", "returned_by_rules": ("construct_scope_foo", ), "union_members": (), "union_type": None, "used_in_rules": (), }, "pants.engine.target.Target": { "consumed_by_rules": (), "dependees": (), "dependencies": (), "documentation": ("A Target represents an addressable set of metadata.\n\n Set the `help` " "class property with a description, which will be used in `./pants help`. For " "the\n best rendering, use soft wrapping (e.g. implicit string concatenation" ") within paragraphs, but\n hard wrapping (`\n`) to separate distinct " "paragraphs and/or lists.\n "), "is_union": False, "module": "pants.engine.target", "name": "Target", "provider": "help_info_extracter_test", "returned_by_rules": ("pants.help.help_info_extracter_test.test_get_all_help_info.rule_info_test", ), "union_members": (), "union_type": None, "used_in_rules": (), }, "pants.option.scope.Scope": { "consumed_by_rules": (), "dependees": (), "dependencies": (), "documentation": "An options scope.", "is_union": False, "module": "pants.option.scope", "name": "Scope", "provider": "pants.option.scope", "returned_by_rules": (), "union_members": (), "union_type": None, "used_in_rules": ("construct_scope_foo", ), }, }, } # Break down this colossal structure into pieces so it is easier to spot where the issue is. # Check keys equality first, then contents assert set(expected_all_help_info_dict) == set(all_help_info_dict) for key in all_help_info_dict: actual = all_help_info_dict[key] expected = expected_all_help_info_dict[key] assert expected == actual
def test_get_all_help_info(): class Global(Subsystem): options_scope = GLOBAL_SCOPE help = "Global options." opt1 = IntOption("-o", "--opt1", default=42, help="Option 1") class Foo(Subsystem): options_scope = "foo" help = "A foo." opt2 = BoolOption("--opt2", default=True, advanced=True, help="Option 2") class Bar(GoalSubsystem): name = "bar" help = "The bar goal." deprecated_options_scope = "bar-old" deprecated_options_scope_removal_version = "9.9.999" class QuxField(StringField): alias = "qux" default = "blahblah" help = "A qux string." class QuuxField(IntField): alias = "quux" required = True help = "A quux int.\n\nMust be non-zero. Or zero. Whatever you like really." class BazLibrary(Target): alias = "baz_library" help = "A library of baz-es.\n\nUse it however you like." core_fields = [QuxField, QuuxField] options = Options.create( env={}, config=Config.load([]), known_scope_infos=[ Global.get_scope_info(), Foo.get_scope_info(), Bar.get_scope_info() ], args=["./pants"], bootstrap_option_values=None, ) Global.register_options_on_scope(options) Foo.register_options_on_scope(options) Bar.register_options_on_scope(options) @rule def rule_info_test(foo: Foo) -> Target: """This rule is for testing info extraction only.""" def fake_consumed_scopes_mapper(scope: str) -> Tuple[str, ...]: return ("somescope", f"used_by_{scope or 'GLOBAL_SCOPE'}") bc_builder = BuildConfiguration.Builder() bc_builder.register_subsystems("help_info_extracter_test", (Foo, Bar)) bc_builder.register_target_types("help_info_extracter_test", (BazLibrary, )) bc_builder.register_rules("help_info_extracter_test", collect_rules(locals())) all_help_info = HelpInfoExtracter.get_all_help_info( options, UnionMembership({}), fake_consumed_scopes_mapper, RegisteredTargetTypes({BazLibrary.alias: BazLibrary}), bc_builder.create(), ) all_help_info_dict = all_help_info.asdict() expected_all_help_info_dict = { "scope_to_help_info": { GLOBAL_SCOPE: { "scope": GLOBAL_SCOPE, "description": "Global options.", "provider": "", "is_goal": False, "deprecated_scope": None, "basic": ({ "display_args": ("-o=<int>", "--opt1=<int>"), "comma_separated_display_args": "-o=<int>, --opt1=<int>", "scoped_cmd_line_args": ("-o", "--opt1"), "unscoped_cmd_line_args": ("-o", "--opt1"), "config_key": "opt1", "env_var": "PANTS_OPT1", "value_history": { "ranked_values": ( { "rank": Rank.NONE, "value": None, "details": None }, { "rank": Rank.HARDCODED, "value": 42, "details": None }, ), }, "typ": int, "default": 42, "help": "Option 1", "deprecation_active": False, "deprecated_message": None, "removal_version": None, "removal_hint": None, "choices": None, "comma_separated_choices": None, }, ), "advanced": tuple(), "deprecated": tuple(), }, "foo": { "scope": "foo", "provider": "help_info_extracter_test", "description": "A foo.", "is_goal": False, "deprecated_scope": None, "basic": (), "advanced": ({ "display_args": ("--[no-]foo-opt2", ), "comma_separated_display_args": "--[no-]foo-opt2", "scoped_cmd_line_args": ("--foo-opt2", "--no-foo-opt2"), "unscoped_cmd_line_args": ("--opt2", "--no-opt2"), "config_key": "opt2", "env_var": "PANTS_FOO_OPT2", "value_history": { "ranked_values": ( { "rank": Rank.NONE, "value": None, "details": None }, { "rank": Rank.HARDCODED, "value": True, "details": None }, ), }, "typ": bool, "default": True, "help": "Option 2", "deprecation_active": False, "deprecated_message": None, "removal_version": None, "removal_hint": None, "choices": None, "comma_separated_choices": None, }, ), "deprecated": tuple(), }, "bar": { "scope": "bar", "provider": "help_info_extracter_test", "description": "The bar goal.", "is_goal": True, "deprecated_scope": "bar-old", "basic": tuple(), "advanced": tuple(), "deprecated": tuple(), }, "bar-old": { "scope": "bar-old", "provider": "help_info_extracter_test", "description": "The bar goal.", "is_goal": True, "deprecated_scope": "bar-old", "basic": tuple(), "advanced": tuple(), "deprecated": tuple(), }, }, "rule_output_type_to_rule_infos": { "Foo": ({ "description": None, "help": "A foo.", "input_gets": ("Get(ScopedOptions, Scope, ..)", ), "input_types": (), "name": "construct_scope_foo", "output_desc": None, "output_type": "Foo", "provider": "help_info_extracter_test", }, ), "Target": ({ "description": None, "help": "This rule is for testing info extraction only.", "input_gets": (), "input_types": ("Foo", ), "name": "pants.help.help_info_extracter_test.rule_info_test", "output_desc": ("A Target represents an addressable set of metadata.\n\n Set the " "`help` class property with a description, which will be used in " "`./pants help`. For the\n best rendering, use soft wrapping (e.g. " "implicit string concatenation) within paragraphs, but\n hard wrapping " "(`\n`) to separate distinct paragraphs and/or lists.\n "), "output_type": "Target", "provider": "help_info_extracter_test", }, ), }, "name_to_goal_info": { "bar": { "name": "bar", "provider": "help_info_extracter_test", "description": "The bar goal.", "consumed_scopes": ("somescope", "used_by_bar"), "is_implemented": True, }, "bar-old": { "name": "bar", "provider": "help_info_extracter_test", "description": "The bar goal.", "consumed_scopes": ("somescope", "used_by_bar-old"), "is_implemented": True, }, }, "name_to_target_type_info": { "baz_library": { "alias": "baz_library", "provider": "help_info_extracter_test", "summary": "A library of baz-es.", "description": "A library of baz-es.\n\nUse it however you like.", "fields": ( { "alias": "qux", "provider": "", "default": "'blahblah'", "description": "A qux string.", "required": False, "type_hint": "str | None", }, { "alias": "quux", "provider": "", "default": None, "description": "A quux int.\n\nMust be non-zero. Or zero. " "Whatever you like really.", "required": True, "type_hint": "int", }, ), } }, } assert expected_all_help_info_dict == all_help_info_dict
def test_get_all_help_info(): class Global(Subsystem): options_scope = GLOBAL_SCOPE help = "Global options." @classmethod def register_options(cls, register): register("-o", "--opt1", type=int, default=42, help="Option 1") class Foo(Subsystem): options_scope = "foo" help = "A foo." @classmethod def register_options(cls, register): register("--opt2", type=bool, default=True, help="Option 2") register("--opt3", advanced=True, choices=["a", "b", "c"]) class Bar(GoalSubsystem): name = "bar" help = "The bar goal." class QuxField(StringField): alias = "qux" default = "blahblah" help = "A qux string." class QuuxField(IntField): alias = "quux" required = True help = "A quux int.\n\nMust be non-zero. Or zero. Whatever you like really." class BazLibrary(Target): alias = "baz_library" help = "A library of baz-es.\n\nUse it however you like." core_fields = [QuxField, QuuxField] options = Options.create( env={}, config=Config.load_file_contents(""), known_scope_infos=[ Global.get_scope_info(), Foo.get_scope_info(), Bar.get_scope_info() ], args=["./pants"], bootstrap_option_values=None, ) Global.register_options_on_scope(options) Foo.register_options_on_scope(options) Bar.register_options_on_scope(options) def fake_consumed_scopes_mapper(scope: str) -> Tuple[str, ...]: return ("somescope", f"used_by_{scope or 'GLOBAL_SCOPE'}") all_help_info = HelpInfoExtracter.get_all_help_info( options, UnionMembership({}), fake_consumed_scopes_mapper, RegisteredTargetTypes({BazLibrary.alias: BazLibrary}), ) all_help_info_dict = dataclasses.asdict(all_help_info) expected_all_help_info_dict = { "scope_to_help_info": { GLOBAL_SCOPE: { "scope": GLOBAL_SCOPE, "description": "Global options.", "is_goal": False, "basic": ({ "display_args": ("-o=<int>", "--opt1=<int>"), "comma_separated_display_args": "-o=<int>, --opt1=<int>", "scoped_cmd_line_args": ("-o", "--opt1"), "unscoped_cmd_line_args": ("-o", "--opt1"), "config_key": "opt1", "env_var": "PANTS_OPT1", "value_history": { "ranked_values": ( { "rank": Rank.NONE, "value": None, "details": None }, { "rank": Rank.HARDCODED, "value": 42, "details": None }, ), }, "typ": int, "default": 42, "help": "Option 1", "deprecation_active": False, "deprecated_message": None, "removal_version": None, "removal_hint": None, "choices": None, "comma_separated_choices": None, }, ), "advanced": tuple(), "deprecated": tuple(), }, "foo": { "scope": "foo", "description": "A foo.", "is_goal": False, "basic": ({ "display_args": ("--[no-]foo-opt2", ), "comma_separated_display_args": "--[no-]foo-opt2", "scoped_cmd_line_args": ("--foo-opt2", "--no-foo-opt2"), "unscoped_cmd_line_args": ("--opt2", "--no-opt2"), "config_key": "opt2", "env_var": "PANTS_FOO_OPT2", "value_history": { "ranked_values": ( { "rank": Rank.NONE, "value": None, "details": None }, { "rank": Rank.HARDCODED, "value": True, "details": None }, ), }, "typ": bool, "default": True, "help": "Option 2", "deprecation_active": False, "deprecated_message": None, "removal_version": None, "removal_hint": None, "choices": None, "comma_separated_choices": None, }, ), "advanced": ({ "display_args": ("--foo-opt3=<str>", ), "comma_separated_display_args": "--foo-opt3=<str>", "scoped_cmd_line_args": ("--foo-opt3", ), "unscoped_cmd_line_args": ("--opt3", ), "config_key": "opt3", "env_var": "PANTS_FOO_OPT3", "value_history": { "ranked_values": ({ "rank": Rank.NONE, "value": None, "details": None }, ), }, "typ": str, "default": None, "help": "No help available.", "deprecation_active": False, "deprecated_message": None, "removal_version": None, "removal_hint": None, "choices": ("a", "b", "c"), "comma_separated_choices": "a, b, c", }, ), "deprecated": tuple(), }, "bar": { "scope": "bar", "description": "The bar goal.", "is_goal": True, "basic": tuple(), "advanced": tuple(), "deprecated": tuple(), }, }, "name_to_goal_info": { "bar": { "name": "bar", "description": "The bar goal.", "consumed_scopes": ("somescope", "used_by_bar"), "is_implemented": True, } }, "name_to_target_type_info": { "baz_library": { "alias": "baz_library", "summary": "A library of baz-es.", "description": "A library of baz-es.\n\nUse it however you like.", "fields": ( { "alias": "qux", "default": "'blahblah'", "description": "A qux string.", "required": False, "type_hint": "str | None", }, { "alias": "quux", "default": None, "description": "A quux int.\n\nMust be non-zero. Or zero. " "Whatever you like really.", "required": True, "type_hint": "int", }, ), } }, } assert expected_all_help_info_dict == all_help_info_dict
def test_get_all_help_info(): class Global(Subsystem): """Global options.""" options_scope = GLOBAL_SCOPE @classmethod def register_options(cls, register): register("-o", "--opt1", type=int, default=42, help="Option 1") class Foo(Subsystem): """A foo.""" options_scope = "foo" @classmethod def register_options(cls, register): register("--opt2", type=bool, default=True, help="Option 2") register("--opt3", advanced=True, choices=["a", "b", "c"]) class Bar(GoalSubsystem): """The bar goal.""" name = "bar" options = Options.create( env={}, config=Config.load_file_contents(""), known_scope_infos=[ Global.get_scope_info(), Foo.get_scope_info(), Bar.get_scope_info() ], args=["./pants"], bootstrap_option_values=None, ) Global.register_options_on_scope(options) Foo.register_options_on_scope(options) Bar.register_options_on_scope(options) def fake_consumed_scopes_mapper(scope: str) -> Tuple[str, ...]: return ("somescope", f"used_by_{scope or 'GLOBAL_SCOPE'}") all_help_info = HelpInfoExtracter.get_all_help_info( options, UnionMembership({}), fake_consumed_scopes_mapper) all_help_info_dict = dataclasses.asdict(all_help_info) expected_all_help_info_dict = { "scope_to_help_info": { GLOBAL_SCOPE: { "scope": GLOBAL_SCOPE, "description": "Global options.", "is_goal": False, "basic": ({ "display_args": ("-o=<int>", "--opt1=<int>"), "comma_separated_display_args": "-o=<int>, --opt1=<int>", "scoped_cmd_line_args": ("-o", "--opt1"), "unscoped_cmd_line_args": ("-o", "--opt1"), "config_key": "opt1", "env_var": "PANTS_OPT1", "value_history": { "ranked_values": ( { "rank": Rank.NONE, "value": None, "details": None }, { "rank": Rank.HARDCODED, "value": 42, "details": None }, ), }, "typ": int, "default": 42, "help": "Option 1", "deprecated_message": None, "removal_version": None, "removal_hint": None, "choices": None, "comma_separated_choices": None, }, ), "advanced": tuple(), "deprecated": tuple(), }, "foo": { "scope": "foo", "description": "A foo.", "is_goal": False, "basic": ({ "display_args": ("--[no-]foo-opt2", ), "comma_separated_display_args": "--[no-]foo-opt2", "scoped_cmd_line_args": ("--foo-opt2", "--no-foo-opt2"), "unscoped_cmd_line_args": ("--opt2", "--no-opt2"), "config_key": "opt2", "env_var": "PANTS_FOO_OPT2", "value_history": { "ranked_values": ( { "rank": Rank.NONE, "value": None, "details": None }, { "rank": Rank.HARDCODED, "value": True, "details": None }, ), }, "typ": bool, "default": True, "help": "Option 2", "deprecated_message": None, "removal_version": None, "removal_hint": None, "choices": None, "comma_separated_choices": None, }, ), "advanced": ({ "display_args": ("--foo-opt3=<str>", ), "comma_separated_display_args": "--foo-opt3=<str>", "scoped_cmd_line_args": ("--foo-opt3", ), "unscoped_cmd_line_args": ("--opt3", ), "config_key": "opt3", "env_var": "PANTS_FOO_OPT3", "value_history": { "ranked_values": ({ "rank": Rank.NONE, "value": None, "details": None }, ), }, "typ": str, "default": None, "help": "No help available.", "deprecated_message": None, "removal_version": None, "removal_hint": None, "choices": ("a", "b", "c"), "comma_separated_choices": "a, b, c", }, ), "deprecated": tuple(), }, "bar": { "scope": "bar", "description": "The bar goal.", "is_goal": True, "basic": tuple(), "advanced": tuple(), "deprecated": tuple(), }, }, "name_to_goal_info": { "bar": { "name": "bar", "description": "The bar goal.", "consumed_scopes": ("somescope", "used_by_bar"), "is_implemented": True, } }, } assert expected_all_help_info_dict == all_help_info_dict
def test_scope_deprecation(self): # Note: This test demonstrates that two different new scopes can deprecate the same # old scope. I.e., it's possible to split an old scope's options among multiple new scopes. class DummyOptionable1(Optionable): options_scope = 'new-scope1' options_scope_category = ScopeInfo.SUBSYSTEM deprecated_options_scope = 'deprecated-scope' deprecated_options_scope_removal_version = '9999.9.9' class DummyOptionable2(Optionable): options_scope = 'new-scope2' options_scope_category = ScopeInfo.SUBSYSTEM deprecated_options_scope = 'deprecated-scope' deprecated_options_scope_removal_version = '9999.9.9' options = Options.create(env={}, config=self._create_config({ DummyOptionable1.options_scope: { 'foo': 'xx' }, DummyOptionable1.deprecated_options_scope: { 'foo': 'yy', 'bar': 'zz', 'baz': 'ww', 'qux': 'uu' }, }), known_scope_infos=[ DummyOptionable1.get_scope_info(), DummyOptionable2.get_scope_info() ], args=shlex.split('./pants --new-scope1-baz=vv'), option_tracker=OptionTracker()) options.register(DummyOptionable1.options_scope, '--foo') options.register(DummyOptionable1.options_scope, '--bar') options.register(DummyOptionable1.options_scope, '--baz') options.register(DummyOptionable2.options_scope, '--qux') with self.warnings_catcher() as w: vals1 = options.for_scope(DummyOptionable1.options_scope) # Check that we got a warning. self.assertEquals(1, len(w)) self.assertTrue(isinstance(w[0].message, DeprecationWarning)) # Check values. # Deprecated scope takes precedence at equal rank. self.assertEquals('yy', vals1.foo) self.assertEquals('zz', vals1.bar) # New scope takes precedence at higher rank. self.assertEquals('vv', vals1.baz) with self.warnings_catcher() as w: vals2 = options.for_scope(DummyOptionable2.options_scope) # Check that we got a warning. self.assertEquals(1, len(w)) self.assertTrue(isinstance(w[0].message, DeprecationWarning)) # Check values. self.assertEquals('uu', vals2.qux)