On Tue, Mar 26, 2024 at 3:04 PM Luca Vizzarro <luca.vizza...@arm.com> wrote: > > Make it so that interactive shells accept an implementation of `Params` > for app arguments. Convert EalParameters to use `Params` instead. > > String command line parameters can still be supplied by using the > `StrParams` implementation. > > Signed-off-by: Luca Vizzarro <luca.vizza...@arm.com> > Reviewed-by: Jack Bond-Preston <jack.bond-pres...@arm.com> > Reviewed-by: Honnappa Nagarahalli <honnappa.nagaraha...@arm.com> > --- <snip> > @@ -40,7 +42,7 @@ class InteractiveShell(ABC): > _ssh_channel: Channel > _logger: DTSLogger > _timeout: float > - _app_args: str > + _app_args: Params | None >
I'm not sure if allowing None should be the solution for these shells as opposed to just supplying an empty parameter object. Maybe something that could be done is the factory method in sut_node allows it to be None, but when it comes time to make the abstract shell it creates an empty one if it doesn't exist, or the init method makes this an optional parameter but creates it if it doesn't exist. I suppose this logic would have to exist somewhere because the parameters aren't always required, it's just a question of where we should put it and if we should just assume that the interactive shell class just knows how to accept some parameters and put them into the shell. I would maybe leave this as something that cannot be None and handle it outside of the shell, but I'm not sure it's something really required and I don't have a super strong opinion on it. > #: Prompt to expect at the end of output when sending a command. > #: This is often overridden by subclasses. <snip> > @@ -118,8 +119,15 @@ def _start_application(self, get_privileged_command: > Callable[[str], str] | None > Also find the number of pci addresses which were allowed on the > command line when the app > was started. > """ > - self._app_args += " -i --mask-event intr_lsc" > - self.number_of_ports = self._app_args.count("-a ") > + from framework.testbed_model.sut_node import EalParameters > + > + assert isinstance(self._app_args, EalParameters) > + > + if isinstance(self._app_args.app_params, StrParams): > + self._app_args.app_params.value += " -i --mask-event intr_lsc" > + > + self.number_of_ports = len(self._app_args.ports) if > self._app_args.ports is not None else 0 I we should override the _app_args parameter in the testpmd shell to always be EalParameters instead of doing this import and assertion. It's a DPDK app, so we will always need EalParameters anyway, so we might as well put that as our typehint to start off as what we expect instead. The checking of an instance of StrParams also feels a little strange here, it might be more ideal if we could just add the parameters without this check. Maybe something we can do, just because these parameters are meant to be CLI commands anyway and will be rendered as a string, is replace the StrParams object with a method on the base Params dataclass that allows you to just add any string as a value only field. Then, we don't have to bother validating anything about the app parameters and we don't care what they are, we can just add a string to them of new parameters. I think this is something that likely also gets solved when you replace this with testpmd parameters, but it might be a good way to make the Params object more versatile in general so that people can diverge and add their own flags to it if needed. > + > super()._start_application(get_privileged_command) > > def start(self, verify: bool = True) -> None: <snip> > @@ -134,7 +136,7 @@ def create_interactive_shell( > shell_cls: Type[InteractiveShellType], > timeout: float, > privileged: bool, > - app_args: str, > + app_args: Params | None, This also falls in line with what I was saying before about where this logic is handled. This was made to always be a string originally because by this point it being optional was already handled by the sut_node.create_interactive_shell() and we should have some kind of value here (even if that value is an empty parameters dataclass) to pass into the application. It sort of semantically doesn't really change much, but at this point it might as well not be None, so we can simplify this type. > ) -> InteractiveShellType: > """Factory for interactive session handlers. > <snip> > +@dataclass(kw_only=True) > +class EalParameters(Params): > """The environment abstraction layer parameters. > > The string representation can be created by converting the instance to a > string. > """ > > - def __init__( > - self, > - lcore_list: LogicalCoreList, > - memory_channels: int, > - prefix: str, > - no_pci: bool, > - vdevs: list[VirtualDevice], > - ports: list[Port], > - other_eal_param: str, > - ): > - """Initialize the parameters according to inputs. > - > - Process the parameters into the format used on the command line. > + lcore_list: LogicalCoreList = field(metadata=params.short("l")) > + """The list of logical cores to use.""" > > - Args: > - lcore_list: The list of logical cores to use. > - memory_channels: The number of memory channels to use. > - prefix: Set the file prefix string with which to start DPDK, > e.g.: ``prefix='vf'``. > - no_pci: Switch to disable PCI bus e.g.: ``no_pci=True``. > - vdevs: Virtual devices, e.g.:: > + memory_channels: int = field(metadata=params.short("n")) > + """The number of memory channels to use.""" > > - vdevs=[ > - VirtualDevice('net_ring0'), > - VirtualDevice('net_ring1') > - ] > - ports: The list of ports to allow. > - other_eal_param: user defined DPDK EAL parameters, e.g.: > - ``other_eal_param='--single-file-segments'`` > - """ > - self._lcore_list = f"-l {lcore_list}" > - self._memory_channels = f"-n {memory_channels}" > - self._prefix = prefix > - if prefix: > - self._prefix = f"--file-prefix={prefix}" > - self._no_pci = "--no-pci" if no_pci else "" > - self._vdevs = " ".join(f"--vdev {vdev}" for vdev in vdevs) > - self._ports = " ".join(f"-a {port.pci}" for port in ports) > - self._other_eal_param = other_eal_param > - > - def __str__(self) -> str: > - """Create the EAL string.""" > - return ( > - f"{self._lcore_list} " > - f"{self._memory_channels} " > - f"{self._prefix} " > - f"{self._no_pci} " > - f"{self._vdevs} " > - f"{self._ports} " > - f"{self._other_eal_param}" > - ) > + prefix: str = field(metadata=params.long("file-prefix")) > + """Set the file prefix string with which to start DPDK, e.g.: > ``prefix="vf"``.""" Just a small note on docstrings, I believe generally in DTS we use the google docstring (https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) format where it applies with some small differences. Because these attributes aren't class vars however, I believe they should be in the docstring for the class in the `Attributes` section. I generally have trouble remembering exactly how it should look, but Juraj documented it in `doc/guides/tools/dts.rst` > + > + no_pci: params.Option > + """Switch to disable PCI bus e.g.: ``no_pci=True``.""" <snip> > -- > 2.34.1 >