DPDK patches and discussions
 help / color / mirror / Atom feed
From: Luca Vizzarro <Luca.Vizzarro@arm.com>
To: "Juraj Linkeš" <juraj.linkes@pantheon.tech>
Cc: dev@dpdk.org, Jeremy Spewock <jspewock@iol.unh.edu>,
	Paul Szczepanek <paul.szczepanek@arm.com>
Subject: Re: [PATCH v5 1/3] dts: rework arguments framework
Date: Thu, 30 May 2024 19:43:04 +0100	[thread overview]
Message-ID: <defeff7f-4c1f-4b3b-839e-d2d905bc9836@arm.com> (raw)
In-Reply-To: <CAOb5WZbKA147-HGDSVyn4gz2XFNUwwyMrBxvDb9TWzVe_9Ng_g@mail.gmail.com>

On 30/05/2024 16:30, Juraj Linkeš wrote:
> There is a difference in behavior when I pass no arguments and then I
> either have or don't have an env var set:
> ./main.py
> usage: main.py [-h] [--config-file FILE_PATH] ...
> ...
> 
> -----------------------------
> DTS_SKIP_SETUP=Y ./main.py
> main.py: error: one of the arguments --tarball/--snapshot
> --revision/--rev/--git-ref is required
> 
> For help and usage, run the command with the --help flag.
> 
> I think this is because we're adding the env vars as actions (the
> second scenario thus has at least one) and the first scenario doesn't
> have any actions, which leads to the different output. I don't know
> whether we want to unify these, but it's a bit confusing, as the error
> applies to both cases.

The behaviour is just as expected. You get the same exact results if you 
were to run:
   ./main.py -s
which is equivalent to:
   DTS_SKIP_SETUP= ./main.py

The true equivalent to your example would be:
   ./main.py -s Y

The only anomaly is that no matter the value passed (true, false, y, 
n...) it's always going to be set because the action is `store_true`.
If we care to interpret a possible value then we need to implement a new 
action to cater for this.

>> diff --git a/dts/framework/settings.py b/dts/framework/settings.py
>> index 688e8679a7..b19f274f9d 100644
>> --- a/dts/framework/settings.py
>> +++ b/dts/framework/settings.py
> 
>> @@ -109,104 +116,224 @@ class Settings:
>>
>>   SETTINGS: Settings = Settings()
>>
>> +P = ParamSpec("P")
>>
>> -def _get_parser() -> argparse.ArgumentParser:
>> -    """Create the argument parser for DTS.
>> +#: Attribute name representing the env variable name to augment :class:`~argparse.Action` with.
>> +_ENV_VAR_NAME_ATTR = "env_var_name"
>> +#: Attribute name representing the value origin to augment :class:`~argparse.Action` with.
>> +_IS_FROM_ENV_ATTR = "is_from_env"
>>
>> -    Command line options take precedence over environment variables, which in turn take precedence
>> -    over default values.
>> +#: The prefix to be added to all of the environment variables.
>> +ENV_PREFIX = "DTS_"
>> +
>> +
>> +def is_action_in_args(action: Action) -> bool:
> 
> I don't think there's any expectation that these functions will be
> used outside this module, so I'd make them all private, in which case
> the short docstring would be fine.
> 
> The ParamSpec also maybe should be private. Same goes for ENV_PREFIX.

Ack.

> I'm also looking at the order of the various functions, classes and
> variables in the module and it looks all over the place. Maybe we can
> tidy it up a bit.

Could you please elaborate on this? It is also important to define 
what's the ordering expectation. At the moment they are mostly in order 
of usage, except for the Settings which I purposefully left on top as 
they are easier to find and probably the most "needed" for a user. As 
for the env var helper functions, most of them are used rightaway, but I 
left a couple that are used later there so that they are all grouped 
together (and related).

>> +class ArgumentParser(argparse.ArgumentParser):
> 
> I'd rename this to DTSArgumentParser and maybe also make the classes
> (this one and the formatter) private.
> 

Ack.
>> +    Instead of printing usage on every error, it prints instructions
>> +    on how to do it.
> 
> This sentence is confusing - how to do what?

This could do without the sentence to be honest, I'll just remove it.

>> +    def find_action(
>> +        self, action_name: str, filter_fn: Callable[[Action], bool] | None = None
>> +    ) -> Action | None:
>> +        """Find and return an action by its name.
>> +
>> +        Arguments:
>> +            action_name: the name of the action to find.
>> +            filter_fn: a filter function to use in the search.
> 
> It's not very clear from the description how this filter is applied.
> We should mention that the found action (and not action_name) is going
> to be passed to filter_fn.

Ack.

>> -        return os.environ.get(env_var) or default
>> +        it = (action for action in self._actions if action_name == _get_action_name(action))
>> +        action = next(it, None)
>> +
>> +        if action is not None and filter_fn is not None:
> 
> Would a simplified condition "if action and filter_fn" work?

Should actually work in this case. Will change it.


>> +class EnvVarHelpFormatter(ArgumentDefaultsHelpFormatter):
>> +    """Custom formatter to add environment variables in the help page."""
> 
> to the help page

Ack.

>> +        if env_var_name is not None:
>> +            help = f"[{env_var_name}] {help}"
>> +
>> +            env_var_value = os.environ.get(env_var_name)
>> +            if env_var_value is not None:
>> +                help += f" (env value: {env_var_value})"
> 
> Let's do this the same way as four lines above: help = f"{help} (env
> value: {env_var_value})"

Ack.

>> -    parser.add_argument(
>> +    add_argument_to_parser_with_env = augment_add_argument_with_env(parser.add_argument)
> 
> I'm wondering whether we could modify the actual add_argument methods
> of ArgumentParser and _MutuallyExclusiveGroup, either the class
> methods or instance methods. Have you tried that? It could be easier
> to understand.

I tried this, but it becomes overly complicated because add_argument is 
implemented in _ActionsContainer which in turn is inherited by several 
classes, which we ultimately use. The easiest and clearest approach is 
just a wrapper.

> Or, alternatively, we could do:
> 
> action = parser.add_argument(
>      "--config-file",
>      default=SETTINGS.config_file_path,
>      type=Path,
>      help="The configuration file that describes the test cases, SUTs
> and targets.",
>      metavar="FILE_PATH",
> )
> add_env_var_to_action(action, env_var_name="CFG_FILE")
> 
> This makes what we're trying to do a bit clearer, but requires two
> calls instead of one, so maybe it's not better. I'm not sure.

I can drop the HOF and use it as `_add_env_var_to_action` as a clearer 
replacement.

>> +
>> +    add_argument_to_parser_with_env(
>>           "--config-file",
> 
> We should rename Settings.config_file_path to correspond with this.
> Otherwise it's going to be ignored by get_settings().

This was actually a miss from me. Nice catch! I've solved this issue by 
adding `dest` to the other arguments, but clearly forgot about this one.

>> @@ -240,17 +369,12 @@ def _process_test_suites(args: str | list[list[str]]) -> list[TestSuiteConfig]:
>>       Returns:
>>           A list of test suite configurations to execute.
>>       """
>> -    if isinstance(args, str):
>> -        # Environment variable in the form of "suite case case, suite case, suite, ..."
>> -        args = [suite_with_cases.split() for suite_with_cases in args.split(",")]
>> +    test_suites = parser.find_action("test_suites", is_from_env)
>> +    if test_suites is not None:
>> +        # Environment variable in the form of "SUITE1 CASE1 CASE2, SUITE2 CASE1, SUITE3, ..."
>> +        args = [suite_with_cases.split() for suite_with_cases in args[0][0].split(",")]
>>
>> -    test_suites_to_run = []
>> -    for suite_with_cases in args:
>> -        test_suites_to_run.append(
>> -            TestSuiteConfig(test_suite=suite_with_cases[0], test_cases=suite_with_cases[1:])
>> -        )
>> -
>> -    return test_suites_to_run
>> +    return [TestSuiteConfig(test_suite, test_cases) for [test_suite, *test_cases] in args]
> 
> This doesn't work properly, if I use:
> DTS_TEST_SUITES="hello_world test_hello_world_single_core, os_udp test_os_udp"
> I'm getting:
> execution_setup - dts - ERROR - Invalid test suite configuration
> found: [TestSuiteConfig(test_suite='hello_world
> test_hello_world_single_core, os_udp test_os_udp', test_cases=[])].
> ...
> ModuleNotFoundError: No module named 'tests.TestSuite_hello_world
> test_hello_world_single_core, os_udp test_os_udp'

yes, nice catch! Must have forgot to test this bit again. The culprit is 
find_action as it's meant to find by dest and not action name.

  reply	other threads:[~2024-05-30 18:43 UTC|newest]

Thread overview: 53+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-01-22 18:26 [PATCH 0/4] dts: error and usage improvements Luca Vizzarro
2024-01-22 18:26 ` [PATCH 1/4] dts: constrain DPDK source flag Luca Vizzarro
2024-01-29 11:47   ` Juraj Linkeš
2024-02-23 19:09     ` Luca Vizzarro
2024-03-01 10:22       ` Juraj Linkeš
2024-01-22 18:26 ` [PATCH 2/4] dts: customise argparse error message Luca Vizzarro
2024-01-29 13:04   ` Juraj Linkeš
2024-02-23 19:12     ` Luca Vizzarro
2024-02-26  9:09       ` Juraj Linkeš
2024-01-22 18:26 ` [PATCH 3/4] dts: show help when DTS is ran without args Luca Vizzarro
2024-01-22 18:26 ` [PATCH 4/4] dts: log stderr with failed remote commands Luca Vizzarro
2024-01-29 13:10   ` Juraj Linkeš
2024-02-23 19:19     ` Luca Vizzarro
2024-02-26  9:05       ` Juraj Linkeš
2024-03-18 17:17 ` [PATCH v2 0/3] dts: error and usage improvements Luca Vizzarro
2024-03-18 17:17   ` [PATCH v2 1/3] dts: rework arguments framework Luca Vizzarro
2024-04-04  9:25     ` Juraj Linkeš
2024-04-09 15:14       ` Luca Vizzarro
2024-04-10  9:46         ` Juraj Linkeš
2024-03-18 17:17   ` [PATCH v2 2/3] dts: constrain DPDK source argument Luca Vizzarro
2024-03-18 17:17   ` [PATCH v2 3/3] dts: store stderr in RemoteCommandExecutionError Luca Vizzarro
2024-05-14 11:44   ` [PATCH v3 0/3] error and usage improvements Luca Vizzarro
2024-05-14 11:44     ` [PATCH v3 1/3] dts: rework arguments framework Luca Vizzarro
2024-05-14 11:55       ` [PATCH v3] " Luca Vizzarro
2024-05-14 11:44     ` [PATCH v3 2/3] dts: constrain DPDK source argument Luca Vizzarro
2024-05-14 11:44     ` [PATCH v3 3/3] dts: store stderr in RemoteCommandExecutionError Luca Vizzarro
2024-05-14 12:04 ` [PATCH v4 0/3] error and usage improvements Luca Vizzarro
2024-05-14 12:05   ` [PATCH v4 1/3] dts: update mypy static checker Luca Vizzarro
2024-05-14 12:05   ` [PATCH v4 2/3] dts: clean up config types Luca Vizzarro
2024-05-14 12:05   ` [PATCH v4 3/3] dts: rework arguments framework Luca Vizzarro
2024-05-14 12:10 ` [PATCH v5 0/3] error and usage improvements Luca Vizzarro
2024-05-14 12:10   ` [PATCH v5 1/3] dts: rework arguments framework Luca Vizzarro
2024-05-30 15:30     ` Juraj Linkeš
2024-05-30 18:43       ` Luca Vizzarro [this message]
2024-05-31  9:04         ` Juraj Linkeš
2024-05-14 12:10   ` [PATCH v5 2/3] dts: constrain DPDK source argument Luca Vizzarro
2024-05-30 15:41     ` Juraj Linkeš
2024-05-30 18:46       ` Luca Vizzarro
2024-05-14 12:10   ` [PATCH v5 3/3] dts: store stderr in RemoteCommandExecutionError Luca Vizzarro
2024-05-30 15:47     ` Juraj Linkeš
2024-05-30 18:48       ` Luca Vizzarro
2024-05-14 15:26   ` [PATCH v5 0/3] error and usage improvements Luca Vizzarro
2024-05-31 11:20 ` [PATCH v6 " Luca Vizzarro
2024-05-31 11:20   ` [PATCH v6 1/3] dts: rework arguments framework Luca Vizzarro
2024-05-31 12:49     ` Juraj Linkeš
2024-06-14 13:55     ` Jeremy Spewock
2024-05-31 11:20   ` [PATCH v6 2/3] dts: constrain DPDK source argument Luca Vizzarro
2024-05-31 12:50     ` Juraj Linkeš
2024-06-14 13:56       ` Jeremy Spewock
2024-05-31 11:20   ` [PATCH v6 3/3] dts: store stderr in RemoteCommandExecutionError Luca Vizzarro
2024-05-31 12:51     ` Juraj Linkeš
2024-06-14 13:56       ` Jeremy Spewock
2024-06-20  0:24   ` [PATCH v6 0/3] error and usage improvements Thomas Monjalon

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=defeff7f-4c1f-4b3b-839e-d2d905bc9836@arm.com \
    --to=luca.vizzarro@arm.com \
    --cc=dev@dpdk.org \
    --cc=jspewock@iol.unh.edu \
    --cc=juraj.linkes@pantheon.tech \
    --cc=paul.szczepanek@arm.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).