Replaces LICENSE (GPLv3 -> AGPLv3) and prepends `SPDX-License-Identifier: AGPL-3.0-or-later` to every source file across decnet/, decnet_web/, tests/, scripts/, and tools/. Rationale: closes the GPLv3 ASP loophole so any party operating a modified DECNET as a network service must offer their modified source. Personal copyright (Samuel Paschuan) + inbound=outbound contributions make a future unilateral relicense infeasible. - LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt) - COPYRIGHT: project copyright notice - tools/add_spdx_headers.py: idempotent header injector (shebang- and PEP 263-aware) Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh). No behavior change; comments only.
103 lines
3.6 KiB
Python
103 lines
3.6 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""Step 4: command segmentation in SessionContext.
|
|
|
|
PII discipline: full command body never enters the engine. Only the
|
|
first-token sha256 hash is retained.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from decnet.profiler.behave_shell._ctx import build_session_context
|
|
from decnet.profiler.behave_shell._parse import AsciinemaEvent, hash_token
|
|
|
|
|
|
def _ctx(events: list[AsciinemaEvent]):
|
|
return build_session_context(events, sid="t-cmds", source="test")
|
|
|
|
|
|
def test_no_input_means_no_commands() -> None:
|
|
ctx = _ctx([])
|
|
assert ctx.commands == ()
|
|
assert ctx.inter_cmd_iats == ()
|
|
assert ctx.output_per_cmd == ()
|
|
|
|
|
|
def test_unterminated_input_yields_no_command() -> None:
|
|
# No trailing newline → no command boundary observed
|
|
events: list[AsciinemaEvent] = [(0.0, "i", "ls"), (0.1, "i", " -la")]
|
|
ctx = _ctx(events)
|
|
assert ctx.commands == ()
|
|
|
|
|
|
def test_single_command_carriage_return_terminator() -> None:
|
|
events: list[AsciinemaEvent] = [
|
|
(0.0, "i", "l"), (0.1, "i", "s"), (0.2, "i", "\r"),
|
|
]
|
|
ctx = _ctx(events)
|
|
assert len(ctx.commands) == 1
|
|
cmd = ctx.commands[0]
|
|
assert cmd.start_ts == 0.0
|
|
assert cmd.end_ts == 0.2
|
|
assert cmd.first_token_hash == hash_token("ls")
|
|
|
|
|
|
def test_paste_event_with_full_command() -> None:
|
|
# A pasted command line all in one event, terminated by \r in the
|
|
# paste itself.
|
|
events: list[AsciinemaEvent] = [(1.0, "i", "echo hello world\r")]
|
|
ctx = _ctx(events)
|
|
assert len(ctx.commands) == 1
|
|
cmd = ctx.commands[0]
|
|
assert cmd.first_token_hash == hash_token("echo")
|
|
# start_ts and end_ts both come from the single event timestamp
|
|
assert cmd.start_ts == 1.0
|
|
assert cmd.end_ts == 1.0
|
|
|
|
|
|
def test_lf_terminator_also_segments() -> None:
|
|
events: list[AsciinemaEvent] = [(0.0, "i", "ls -la\n")]
|
|
ctx = _ctx(events)
|
|
assert len(ctx.commands) == 1
|
|
assert ctx.commands[0].first_token_hash == hash_token("ls")
|
|
|
|
|
|
def test_multiple_commands_get_inter_cmd_iats() -> None:
|
|
events: list[AsciinemaEvent] = [
|
|
(0.0, "i", "a"), (0.1, "i", "\r"), # cmd "a" ends at 0.1
|
|
(1.5, "i", "b"), (1.6, "i", "\r"), # cmd "b" starts at 1.5, ends at 1.6
|
|
(3.0, "i", "c"), (3.1, "i", "\r"), # cmd "c" starts at 3.0
|
|
]
|
|
ctx = _ctx(events)
|
|
assert len(ctx.commands) == 3
|
|
# IATs between command end and next command start
|
|
assert ctx.inter_cmd_iats == (1.5 - 0.1, 3.0 - 1.6)
|
|
|
|
|
|
def test_output_per_cmd_counts_bytes_between_command_boundaries() -> None:
|
|
events: list[AsciinemaEvent] = [
|
|
(0.0, "i", "ls\r"),
|
|
(0.1, "o", "file.txt\r\n"), # 10 bytes
|
|
(0.2, "o", "other.txt\r\n"), # 11 bytes
|
|
(1.0, "i", "ps\r"),
|
|
(1.1, "o", "PID TTY\r\n"), # 9 bytes (after cmd 2; tail beyond paired output)
|
|
]
|
|
ctx = _ctx(events)
|
|
assert len(ctx.commands) == 2
|
|
# Pair is (cmd0.end_ts, cmd1.start_ts) = (0.0, 1.0); 21 bytes fall in
|
|
assert ctx.output_per_cmd == (21,)
|
|
|
|
|
|
def test_first_token_only_hashes_first_word() -> None:
|
|
events: list[AsciinemaEvent] = [(0.0, "i", "curl -sS https://target/\r")]
|
|
ctx = _ctx(events)
|
|
assert ctx.commands[0].first_token_hash == hash_token("curl")
|
|
# Argument values are not stored anywhere in SessionContext
|
|
assert "target" not in str(ctx.commands)
|
|
|
|
|
|
def test_blank_line_does_not_emit_command() -> None:
|
|
# Hitting Enter on an empty prompt should not register a command
|
|
events: list[AsciinemaEvent] = [(0.0, "i", "\r"), (0.5, "i", "ls\r")]
|
|
ctx = _ctx(events)
|
|
assert len(ctx.commands) == 1
|
|
assert ctx.commands[0].first_token_hash == hash_token("ls")
|