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
3 changes: 3 additions & 0 deletions tests/test_envfacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ def test_classify_forms_and_intents():
# heredoc bodies never become commands
assert observe.classify_command("python - <<'PY'\npytest_is_a_word = 1\nPY")[0].intent == \
"run_interpreter"
# a here-string (`<<<`) has no body: the commands after it are still read
assert [o.intent for o in observe.classify_command("read -r x <<< x\npytest -q\nx=1")] == \
["run_tests"]


def test_error_classes_are_environmental_only():
Expand Down
11 changes: 11 additions & 0 deletions tests/test_live_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,11 +679,22 @@ def test_the_opencode_side_call_adds_only_the_flags_its_cli_lists(monkeypatch):
# a quoted `|` in a sed script must not split the command (seen live: sympy-13091)
("sed -i -E 's/x (<|<=|>|>=) y/z/' numbers.py expr.py", ["numbers.py", "expr.py"]),
("sed -i 's/a/b/' x.py 2>&1 | tail -2", ["x.py"]),
# a here-string (`<<<`) is not a heredoc: the lines after it are still commands
("grep -q ok <<< \"ok\"\nsed -i 's/a/b/' pkg/mod.py", ["mod.py"]),
("read -r v <<< done\ncat > fix.py <<'EOF'\nx = 1\nEOF", ["fix.py"]),
])
def test_edits_made_through_the_shell_are_recognised(cmd, want):
assert guard.shell_edit_targets(cmd) == want


def test_an_edit_after_a_here_string_is_counted():
"""`<<<` feeds one word to a command; reading it as a heredoc tag made every later line of
the command a heredoc body, so a `sed -i` after it was not counted as an edit."""
g = guard.TurnGuard(turn_key="t")
g.fold("Bash", {"command": "grep -q ok <<< ok\nsed -i 's/a/b/' pkg/mod.py"}, "", 0)
assert g.edits == 1 and g.whole.edit_counts == {"mod.py": 1}


def test_shell_edits_reach_the_state_the_judge_reads_and_the_ended_green_proxy():
g = guard.TurnGuard(turn_key="t")
edit = "python - <<'E'\np='pkg/mod.py'; s=open(p).read()\nopen(p,'w').write(s[::-1])\nE"
Expand Down
2 changes: 2 additions & 0 deletions tests/test_loopguard_productive_turns.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
("SP=/tmp/x; cd $SP && python3 - <<'EOF'\nimport json\nprint('error outputs: 3')\nEOF",
True),
("python3 - <<'EOF'\nprint(1)\nEOF\npython -m pytest -q | tail -3", False),
("python repro.py <<< y", True),
("python repro.py <<< \"y\"\npython -m pytest -q | tail -3", False),
("", False),
])
def test_an_exit_status_covers_a_command_unless_its_work_could_fail_unseen(cmd, covers):
Expand Down
3 changes: 2 additions & 1 deletion tokeneconomizer/loopguard/shell_edits.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

from .state import basename

_HEREDOC_START = re.compile(r"<<-?\s*(['\"]?)([A-Za-z_]\w*)\1")
# `<<<` is a here-string: its word is the input itself, so no body follows on the next lines.
_HEREDOC_START = re.compile(r"(?<!<)<<(?!<)-?\s*(['\"]?)([A-Za-z_]\w*)\1")
_OPS = {"&&", "||", ";", ";;", "|", "|&", "&", "\n", "(", ")"}
_OUT = {">", ">>", ">|", "&>", "&>>"} # redirects that write their operand
_SKIP_OPERAND = {"<", "<<", "<<<", "<&", ">&", "<>"} # input, heredoc tags, fd duplication
Expand Down
2 changes: 1 addition & 1 deletion tokeneconomizer/microskills/observe.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class Vocabulary:
_ENV_ASSIGN_RE = re.compile(r"^(?:[A-Za-z_][A-Za-z0-9_]*=(?:\"[^\"]*\"|'[^']*'|\S+)\s+)+")
_ASSIGN_TOKEN_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=")
_WRAP_RE = re.compile(r"^/bin/(?:ba)?sh\s+-l?c\s+(.*)$", re.S)
_HEREDOC_RE = re.compile(r"<<-?\s*['\"]?(\w+)['\"]?[^\n]*\n.*?\n\s*\1\b", re.S)
_HEREDOC_RE = re.compile(r"(?<!<)<<(?!<)-?\s*['\"]?(\w+)['\"]?[^\n]*\n.*?\n\s*\1\b", re.S)
_URL_HOST_RE = re.compile(r"https?://([^/\s'\"`]+)")

# Error classes an ENVIRONMENT produces (as opposed to the code under test). Order matters:
Expand Down
Loading