xref: /dpdk/dts/framework/settings.py (revision e9fd1ebf981f361844aea9ec94e17f4bda5e1479)
1# SPDX-License-Identifier: BSD-3-Clause
2# Copyright(c) 2010-2021 Intel Corporation
3# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
4# Copyright(c) 2022 University of New Hampshire
5
6"""Environment variables and command line arguments parsing.
7
8This is a simple module utilizing the built-in argparse module to parse command line arguments,
9augment them with values from environment variables and make them available across the framework.
10
11The command line value takes precedence, followed by the environment variable value,
12followed by the default value defined in this module.
13
14The command line arguments along with the supported environment variables are:
15
16.. option:: --config-file
17.. envvar:: DTS_CFG_FILE
18
19    The path to the YAML test run configuration file.
20
21.. option:: --output-dir, --output
22.. envvar:: DTS_OUTPUT_DIR
23
24    The directory where DTS logs and results are saved.
25
26.. option:: --compile-timeout
27.. envvar:: DTS_COMPILE_TIMEOUT
28
29    The timeout for compiling DPDK.
30
31.. option:: -t, --timeout
32.. envvar:: DTS_TIMEOUT
33
34    The timeout for all DTS operation except for compiling DPDK.
35
36.. option:: -v, --verbose
37.. envvar:: DTS_VERBOSE
38
39    Set to any value to enable logging everything to the console.
40
41.. option:: -s, --skip-setup
42.. envvar:: DTS_SKIP_SETUP
43
44    Set to any value to skip building DPDK.
45
46.. option:: --tarball, --snapshot, --git-ref
47.. envvar:: DTS_DPDK_TARBALL
48
49    The path to a DPDK tarball, git commit ID, tag ID or tree ID to test.
50
51.. option:: --test-cases
52.. envvar:: DTS_TESTCASES
53
54    A comma-separated list of test cases to execute. Unknown test cases will be silently ignored.
55
56.. option:: --re-run, --re_run
57.. envvar:: DTS_RERUN
58
59    Re-run each test case this many times in case of a failure.
60
61The module provides one key module-level variable:
62
63Attributes:
64    SETTINGS: The module level variable storing framework-wide DTS settings.
65
66Typical usage example::
67
68  from framework.settings import SETTINGS
69  foo = SETTINGS.foo
70"""
71
72import argparse
73import os
74from collections.abc import Callable, Iterable, Sequence
75from dataclasses import dataclass, field
76from pathlib import Path
77from typing import Any, TypeVar
78
79from .utils import DPDKGitTarball
80
81_T = TypeVar("_T")
82
83
84def _env_arg(env_var: str) -> Any:
85    """A helper method augmenting the argparse Action with environment variables.
86
87    If the supplied environment variable is defined, then the default value
88    of the argument is modified. This satisfies the priority order of
89    command line argument > environment variable > default value.
90
91    Arguments with no values (flags) should be defined using the const keyword argument
92    (True or False). When the argument is specified, it will be set to const, if not specified,
93    the default will be stored (possibly modified by the corresponding environment variable).
94
95    Other arguments work the same as default argparse arguments, that is using
96    the default 'store' action.
97
98    Returns:
99          The modified argparse.Action.
100    """
101
102    class _EnvironmentArgument(argparse.Action):
103        def __init__(
104            self,
105            option_strings: Sequence[str],
106            dest: str,
107            nargs: str | int | None = None,
108            const: bool | None = None,
109            default: Any = None,
110            type: Callable[[str], _T | argparse.FileType | None] = None,
111            choices: Iterable[_T] | None = None,
112            required: bool = False,
113            help: str | None = None,
114            metavar: str | tuple[str, ...] | None = None,
115        ) -> None:
116            env_var_value = os.environ.get(env_var)
117            default = env_var_value or default
118            if const is not None:
119                nargs = 0
120                default = const if env_var_value else default
121                type = None
122                choices = None
123                metavar = None
124            super(_EnvironmentArgument, self).__init__(
125                option_strings,
126                dest,
127                nargs=nargs,
128                const=const,
129                default=default,
130                type=type,
131                choices=choices,
132                required=required,
133                help=help,
134                metavar=metavar,
135            )
136
137        def __call__(
138            self,
139            parser: argparse.ArgumentParser,
140            namespace: argparse.Namespace,
141            values: Any,
142            option_string: str = None,
143        ) -> None:
144            if self.const is not None:
145                setattr(namespace, self.dest, self.const)
146            else:
147                setattr(namespace, self.dest, values)
148
149    return _EnvironmentArgument
150
151
152@dataclass(slots=True)
153class Settings:
154    """Default framework-wide user settings.
155
156    The defaults may be modified at the start of the run.
157    """
158
159    #:
160    config_file_path: Path = Path(__file__).parent.parent.joinpath("conf.yaml")
161    #:
162    output_dir: str = "output"
163    #:
164    timeout: float = 15
165    #:
166    verbose: bool = False
167    #:
168    skip_setup: bool = False
169    #:
170    dpdk_tarball_path: Path | str = "dpdk.tar.xz"
171    #:
172    compile_timeout: float = 1200
173    #:
174    test_cases: list[str] = field(default_factory=list)
175    #:
176    re_run: int = 0
177
178
179SETTINGS: Settings = Settings()
180
181
182def _get_parser() -> argparse.ArgumentParser:
183    parser = argparse.ArgumentParser(
184        description="Run DPDK test suites. All options may be specified with the environment "
185        "variables provided in brackets. Command line arguments have higher priority.",
186        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
187    )
188
189    parser.add_argument(
190        "--config-file",
191        action=_env_arg("DTS_CFG_FILE"),
192        default=SETTINGS.config_file_path,
193        type=Path,
194        help="[DTS_CFG_FILE] configuration file that describes the test cases, SUTs and targets.",
195    )
196
197    parser.add_argument(
198        "--output-dir",
199        "--output",
200        action=_env_arg("DTS_OUTPUT_DIR"),
201        default=SETTINGS.output_dir,
202        help="[DTS_OUTPUT_DIR] Output directory where DTS logs and results are saved.",
203    )
204
205    parser.add_argument(
206        "-t",
207        "--timeout",
208        action=_env_arg("DTS_TIMEOUT"),
209        default=SETTINGS.timeout,
210        type=float,
211        help="[DTS_TIMEOUT] The default timeout for all DTS operations except for compiling DPDK.",
212    )
213
214    parser.add_argument(
215        "-v",
216        "--verbose",
217        action=_env_arg("DTS_VERBOSE"),
218        default=SETTINGS.verbose,
219        const=True,
220        help="[DTS_VERBOSE] Specify to enable verbose output, logging all messages "
221        "to the console.",
222    )
223
224    parser.add_argument(
225        "-s",
226        "--skip-setup",
227        action=_env_arg("DTS_SKIP_SETUP"),
228        const=True,
229        help="[DTS_SKIP_SETUP] Specify to skip all setup steps on SUT and TG nodes.",
230    )
231
232    parser.add_argument(
233        "--tarball",
234        "--snapshot",
235        "--git-ref",
236        action=_env_arg("DTS_DPDK_TARBALL"),
237        default=SETTINGS.dpdk_tarball_path,
238        type=Path,
239        help="[DTS_DPDK_TARBALL] Path to DPDK source code tarball or a git commit ID, "
240        "tag ID or tree ID to test. To test local changes, first commit them, "
241        "then use the commit ID with this option.",
242    )
243
244    parser.add_argument(
245        "--compile-timeout",
246        action=_env_arg("DTS_COMPILE_TIMEOUT"),
247        default=SETTINGS.compile_timeout,
248        type=float,
249        help="[DTS_COMPILE_TIMEOUT] The timeout for compiling DPDK.",
250    )
251
252    parser.add_argument(
253        "--test-cases",
254        action=_env_arg("DTS_TESTCASES"),
255        default="",
256        help="[DTS_TESTCASES] Comma-separated list of test cases to execute. "
257        "Unknown test cases will be silently ignored.",
258    )
259
260    parser.add_argument(
261        "--re-run",
262        "--re_run",
263        action=_env_arg("DTS_RERUN"),
264        default=SETTINGS.re_run,
265        type=int,
266        help="[DTS_RERUN] Re-run each test case the specified number of times "
267        "if a test failure occurs",
268    )
269
270    return parser
271
272
273def get_settings() -> Settings:
274    """Create new settings with inputs from the user.
275
276    The inputs are taken from the command line and from environment variables.
277    """
278    parsed_args = _get_parser().parse_args()
279    return Settings(
280        config_file_path=parsed_args.config_file,
281        output_dir=parsed_args.output_dir,
282        timeout=parsed_args.timeout,
283        verbose=parsed_args.verbose,
284        skip_setup=parsed_args.skip_setup,
285        dpdk_tarball_path=Path(
286            Path(DPDKGitTarball(parsed_args.tarball, parsed_args.output_dir))
287            if not os.path.exists(parsed_args.tarball)
288            else Path(parsed_args.tarball)
289        ),
290        compile_timeout=parsed_args.compile_timeout,
291        test_cases=(parsed_args.test_cases.split(",") if parsed_args.test_cases else []),
292        re_run=parsed_args.re_run,
293    )
294