Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- Add Renode device support.
- Refuse commands longer than the target CONFIG_LINE_MAX.

## 0.0.3

Expand Down
4 changes: 3 additions & 1 deletion Documentation/writing-test-cases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,9 @@ Decorators:
enabled

The :meth:`~ntfc.products.ProductsHandler.sendCommand` method handles
execution and verification.
execution and verification. A command that does not fit the target shell
line buffer (``CONFIG_LINE_MAX`` minus one character) is refused with a
``ValueError`` instead of being split by the shell.

Parametrized Command Test
--------------------------
Expand Down
24 changes: 24 additions & 0 deletions src/ntfc/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,28 @@ def _match_not_found(self, rematch: Optional[re.Match[Any]]) -> bool:
matched = matched.decode("utf-8", errors="ignore")
return self._device.no_cmd in matched

def _check_line_max(self, cmd: bytes) -> None:
"""Refuse a command the target shell cannot buffer in one line.

NSH reads at most ``CONFIG_LINE_MAX - 1`` characters per line and
executes the overflow as a second command, which fails in
confusing ways. The limit is only known when the Kconfig of the
image is available.

:param cmd: Encoded command including its line terminator.
:raises ValueError: Command does not fit the target line buffer.
"""
limit = self._conf.kv_check("CONFIG_LINE_MAX")
if not limit:
return
length = len(cmd.rstrip(b"\r\n"))
if length >= int(limit):
raise ValueError(
f"command is {length} characters, the target buffers at "
f"most {int(limit) - 1} (CONFIG_LINE_MAX={limit}): "
f"{cmd.decode(errors='replace').strip()!r}"
)

def sendCommand( # noqa: N802
self,
cmd: str,
Expand Down Expand Up @@ -162,6 +184,7 @@ def sendCommand( # noqa: N802
encoded = self._builder.build(
cmd, expects, args, flag, match_all, regexp, fail_pattern
)
self._check_line_max(encoded.cmd)

logger.debug(
f"Sending command: {encoded.cmd.decode()}, expecting: "
Expand Down Expand Up @@ -204,6 +227,7 @@ def sendCommandReadUntilPattern( # noqa: N802
:return: CmdReturn : command return data
"""
encoded = self._builder.build_raw(cmd, pattern, args, fail_pattern)
self._check_line_max(encoded.cmd)

logger.debug(
f"Sending command: {encoded.cmd.decode()}, expecting pattern: "
Expand Down
35 changes: 35 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from ntfc.core import CoreStatus, ProductCore
from ntfc.device.common import CmdReturn, CmdStatus
from ntfc.envconfig import EnvConfig


def test_core_init(envconfig_dummy):
Expand Down Expand Up @@ -102,6 +103,40 @@ def test_core_send_command(envconfig_dummy):
assert p.sendCommand("test") == CmdStatus.NOTFOUND


def test_core_rejects_command_longer_than_line_max(config_sim):
"""A command the shell cannot buffer is refused instead of split."""

with patch("ntfc.device.common.DeviceCommon") as mockdevice:
dev = mockdevice.return_value
dev.prompt = b"NSH> "
dev.no_cmd = "command not found"
dev.send_cmd_read_until_pattern.return_value = CmdReturn(
CmdStatus.SUCCESS
)
core = EnvConfig(config_sim).product[0].cfg_core(0)
assert core.kv_check("CONFIG_LINE_MAX") == 80
p = ProductCore(dev, core)

assert p.sendCommand("x" * 79) == CmdStatus.SUCCESS
with pytest.raises(ValueError, match="CONFIG_LINE_MAX"):
p.sendCommand("x" * 80)
with pytest.raises(ValueError, match="CONFIG_LINE_MAX"):
p.sendCommandReadUntilPattern("x" * 70, args=["y" * 10])


def test_core_line_max_unknown_is_not_checked(envconfig_dummy):

with patch("ntfc.device.common.DeviceCommon") as mockdevice:
dev = mockdevice.return_value
dev.prompt = b"NSH> "
dev.no_cmd = "command not found"
dev.send_cmd_read_until_pattern.return_value = CmdReturn(
CmdStatus.SUCCESS
)
p = ProductCore(dev, envconfig_dummy.product[0].cfg_core(0))
assert p.sendCommand("x" * 500) == CmdStatus.SUCCESS


def test_core_send_command_read_until_pattern(envconfig_dummy):
with patch("ntfc.device.common.DeviceCommon") as mockdevice:
dev = mockdevice.return_value
Expand Down
Loading