mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-27 17:03:49 +08:00
227 lines
10 KiB
Go
227 lines
10 KiB
Go
package intercept
|
||
|
||
import (
|
||
"encoding/json"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
func reviewCommandForTest(t *testing.T, dir, command string) ReviewInput {
|
||
t.Helper()
|
||
args, _ := json.Marshal(map[string]string{"command": command})
|
||
in, err := BuildReviewInput(WithReviewWorkingDirectory(t.Context(), dir), "Bash", args)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return in
|
||
}
|
||
|
||
func TestScriptResolutionLiteralConstantsAndInterpreterModes(t *testing.T) {
|
||
dir := t.TempDir()
|
||
for _, name := range []string{"check.py", "spaced name.py", "unicodeс.py", "literal[1].py"} {
|
||
if err := os.WriteFile(filepath.Join(dir, name), []byte("print('fixture')\n"), 0600); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
tests := []struct{ name, command, status string }{
|
||
{"curl_constant", `C='curl -sk -m 30'; $C https://example.test`, "not_applicable"},
|
||
{"constant_native_options", `C='timeout 3 python3'; $C ./check.py`, "complete"},
|
||
{"constant_interpreter", `C='python3'; $C ./check.py`, "complete"},
|
||
{"constant_script_entry", `C='./check.py'; $C`, "complete"},
|
||
{"constant_path", `D='` + dir + `'; python3 $D/check.py`, "complete"},
|
||
{"quoted_constant_path", `F='spaced name.py'; python3 "$F"`, "complete"},
|
||
{"escaped_path", `python3 spaced\ name.py`, "complete"},
|
||
{"quoted_brackets", `python3 'literal[1].py'`, "complete"},
|
||
{"ansi_path", `python3 $'unicode\321\201.py'`, "complete"},
|
||
{"constant_shell_inline", `C='echo ok'; bash -c "$C"`, "not_applicable"},
|
||
{"no_shell_reparse", `C='./check.py; echo'; $C ignored`, "incomplete"},
|
||
{"used_before_assignment", `$C ./check.py; C=python3`, "incomplete"},
|
||
{"reassigned_constant", `C=python3; C=unknown; $C ./check.py`, "incomplete"},
|
||
{"conditional_constant", `if test yes; then C=python3; fi; $C ./check.py`, "incomplete"},
|
||
{"read_can_change_constant", `C=python3; read C; $C ./check.py`, "incomplete"},
|
||
{"function_can_change_constant", `C=python3; change(){ C=unknown; }; change; $C ./check.py`, "incomplete"},
|
||
{"glob_expansion_unresolved", `F='*.py'; python3 $F`, "incomplete"},
|
||
{"python_version", `echo info; python3 --version`, "not_applicable"},
|
||
{"python_version_short", `python3 -V | head`, "not_applicable"},
|
||
{"node_version", `node -v 2>/dev/null; echo done`, "not_applicable"},
|
||
{"python_help", `python3 --help-env`, "not_applicable"},
|
||
{"script_version_argument", `python3 ./check.py --version`, "complete"},
|
||
{"empty_stdin_heredoc", "python3 - <<'PY'\nPY\n", "not_applicable"},
|
||
{"external_stdin", `cat input | python3 -`, "incomplete"},
|
||
{"gnu_time", `/usr/bin/time -f '%e' python3 ./check.py`, "complete"},
|
||
{"gnu_time_dynamic_format", `for i in 1 2; do /usr/bin/time -f "T${i}=%e" python3 ./check.py; done`, "complete"},
|
||
}
|
||
for _, test := range tests {
|
||
t.Run(test.name, func(t *testing.T) {
|
||
in := reviewCommandForTest(t, dir, test.command)
|
||
if in.ScriptCaptureStatus != test.status {
|
||
t.Fatalf("status=%s want=%s scripts=%+v", in.ScriptCaptureStatus, test.status, in.Scripts)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestGeneratedRelativeAndAppendedSnapshots(t *testing.T) {
|
||
command := "cat > run.py <<'PY'\nprint('known')\nPY\npython3 run.py"
|
||
in := reviewCommandForTest(t, "", command)
|
||
if in.ScriptCaptureStatus != "complete" || len(in.Scripts) != 1 || in.Scripts[0].Path != "run.py" || in.Scripts[0].Content != "print('known')\n" {
|
||
t.Fatalf("relative inline identity lost: %+v", in)
|
||
}
|
||
in = reviewCommandForTest(t, "", "cat > run.py <<'PY'\nfirst\nPY\ncat >> run.py <<'PY'\nsecond\nPY\npython3 run.py")
|
||
if in.ScriptCaptureStatus != "complete" || in.Scripts[0].Content != "first\nsecond\n" {
|
||
t.Fatalf("append of known generated content missing: %+v", in)
|
||
}
|
||
in = reviewCommandForTest(t, "", command[:strings.LastIndex(command, "python3")]+`cd "$UNKNOWN"; python3 run.py`)
|
||
if in.ScriptCaptureStatus != "incomplete" {
|
||
t.Fatal("reused a relative file after an unknown directory change")
|
||
}
|
||
dir := t.TempDir()
|
||
in = reviewCommandForTest(t, dir, "cat >> new.py <<'PY'\nfirst\nPY\npython3 new.py")
|
||
if in.ScriptCaptureStatus != "complete" || in.Scripts[0].Content != "first\n" {
|
||
t.Fatalf("append creating a new file: %+v", in)
|
||
}
|
||
if _, err := os.Stat(filepath.Join(dir, "new.py")); !os.IsNotExist(err) {
|
||
t.Fatal("snapshot analysis wrote a file")
|
||
}
|
||
}
|
||
|
||
func TestPythonModuleSourceResolutionWithoutExecution(t *testing.T) {
|
||
root := t.TempDir()
|
||
bin := filepath.Join(root, "bin")
|
||
library := filepath.Join(root, "lib", "python3.12")
|
||
cwd := filepath.Join(root, "work")
|
||
for _, dir := range []string{bin, library, cwd} {
|
||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
python := filepath.Join(bin, "python3.12")
|
||
if err := os.WriteFile(python, []byte("\x7fELF not executable test metadata"), 0700); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
write := func(path, content string) {
|
||
t.Helper()
|
||
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := os.WriteFile(path, []byte(content), 0600); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
sentinel := filepath.Join(root, "must-not-exist")
|
||
write(filepath.Join(library, "site.py"), "open('"+sentinel+"','w').write('startup hook executed')\n")
|
||
write(filepath.Join(library, "json", "__init__.py"), "STANDARD_INIT=1\n")
|
||
write(filepath.Join(library, "json", "tool.py"), "open('"+sentinel+"','w').write('module executed')\n")
|
||
write(filepath.Join(library, "site-packages", "toolpkg", "__init__.py"), "PACKAGE_INIT=1\n")
|
||
write(filepath.Join(library, "site-packages", "toolpkg", "__main__.py"), "PACKAGE_MAIN=1\n")
|
||
t.Setenv("PYTHONPATH", "")
|
||
t.Setenv("PYTHONHOME", "")
|
||
t.Setenv("PYTHONUSERBASE", "")
|
||
t.Setenv("PYTHONSAFEPATH", "")
|
||
in := reviewCommandForTest(t, cwd, python+" -m json.tool")
|
||
if in.ScriptCaptureStatus != "complete" || len(in.Scripts) != 2 || in.Scripts[1].Path != mustCanonicalPath(t, filepath.Join(library, "json", "tool.py")) {
|
||
t.Fatalf("missing standard module sources: %+v", in)
|
||
}
|
||
in = reviewCommandForTest(t, cwd, python+" -m toolpkg")
|
||
if in.ScriptCaptureStatus != "complete" || len(in.Scripts) != 2 || in.Scripts[1].Content != "PACKAGE_MAIN=1\n" {
|
||
t.Fatalf("missing package entry: %+v", in)
|
||
}
|
||
in = reviewCommandForTest(t, cwd, python+" -S -m toolpkg")
|
||
if in.ScriptCaptureStatus != "incomplete" {
|
||
t.Fatal("ignored -S and searched site-packages")
|
||
}
|
||
write(filepath.Join(cwd, "json", "__init__.py"), "LOCAL_INIT=1\n")
|
||
write(filepath.Join(cwd, "json", "tool.py"), "LOCAL_TOOL=1\n")
|
||
in = reviewCommandForTest(t, cwd, python+" -m json.tool")
|
||
if in.ScriptCaptureStatus != "complete" || in.Scripts[1].Content != "LOCAL_TOOL=1\n" {
|
||
t.Fatalf("ignored cwd module shadowing: %+v", in)
|
||
}
|
||
in = reviewCommandForTest(t, cwd, python+" -W -I -m json.tool")
|
||
if in.ScriptCaptureStatus != "complete" || in.Scripts[1].Content != "LOCAL_TOOL=1\n" {
|
||
t.Fatalf("warning option value mistaken for isolation flag: %+v", in)
|
||
}
|
||
in = reviewCommandForTest(t, cwd, python+" -I -m json.tool")
|
||
if in.ScriptCaptureStatus != "complete" || in.Scripts[1].Content == "LOCAL_TOOL=1\n" {
|
||
t.Fatal("isolated Python searched cwd")
|
||
}
|
||
missingDir := filepath.Join(root, "shadow")
|
||
if err := os.MkdirAll(missingDir, 0700); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
write(filepath.Join(missingDir, "json.py"), "NOT_A_PACKAGE=1\n")
|
||
in = reviewCommandForTest(t, missingDir, python+" -m json.tool")
|
||
if in.ScriptCaptureStatus != "incomplete" {
|
||
t.Fatal("substituted stdlib despite a shadowing non-package module")
|
||
}
|
||
extra := filepath.Join(root, "extra")
|
||
write(filepath.Join(extra, "one.py"), "EXTRA=1\n")
|
||
in = reviewCommandForTest(t, cwd, "PYTHONPATH="+extra+" "+python+" -m one")
|
||
if in.ScriptCaptureStatus != "complete" || in.Scripts[0].Content != "EXTRA=1\n" {
|
||
t.Fatalf("ignored explicit PYTHONPATH: %+v", in)
|
||
}
|
||
in = reviewCommandForTest(t, cwd, "PYTHONPATH="+extra+" "+python+" -E -m one")
|
||
if in.ScriptCaptureStatus != "incomplete" {
|
||
t.Fatal("ignored -E environment isolation")
|
||
}
|
||
for _, command := range []string{
|
||
"env -i " + python + " -m json.tool",
|
||
"C='env -i " + python + "'; $C -m json.tool",
|
||
"env -u PYTHONPATH " + python + " -m json.tool",
|
||
"PYTHONPATH=" + extra + "; export PYTHONPATH; " + python + " -m json.tool",
|
||
"PYTHONPATH=" + extra + "; " + python + " -m json.tool",
|
||
"eval 'export PYTHONPATH=unknown'; " + python + " -m json.tool",
|
||
} {
|
||
if in := reviewCommandForTest(t, cwd, command); in.ScriptCaptureStatus != "incomplete" {
|
||
t.Fatalf("guessed a module after an environment change: %+v", in)
|
||
}
|
||
}
|
||
in = reviewCommandForTest(t, cwd, "cat > generated.py <<'PY'\nGENERATED=1\nPY\n"+python+" -m generated")
|
||
if in.ScriptCaptureStatus != "complete" || in.Scripts[0].Source != "command" || in.Scripts[0].Content != "GENERATED=1\n" {
|
||
t.Fatalf("ignored generated local module: %+v", in)
|
||
}
|
||
in = reviewCommandForTest(t, "", python+" -m json.tool")
|
||
if in.ScriptCaptureStatus != "incomplete" {
|
||
t.Fatal("guessed module location with unknown cwd")
|
||
}
|
||
in = reviewCommandForTest(t, "", python+" -I -m json.tool")
|
||
if in.ScriptCaptureStatus != "complete" {
|
||
t.Fatalf("isolated stdlib does not require cwd: %+v", in)
|
||
}
|
||
if _, err := os.Stat(sentinel); !os.IsNotExist(err) {
|
||
t.Fatal("module resolution executed code or startup hooks")
|
||
}
|
||
}
|
||
|
||
func mustCanonicalPath(t *testing.T, path string) string {
|
||
t.Helper()
|
||
path, err := filepath.EvalSymlinks(path)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return path
|
||
}
|
||
|
||
func TestGeneratedPipelineSnapshots(t *testing.T) {
|
||
dir := t.TempDir()
|
||
for _, prefix := range []string{"", "cd " + dir + " && "} {
|
||
in := reviewCommandForTest(t, dir, prefix+"cat > run.py <<'PY' 2>&1 | tee run.log\nprint('known')\nPY\npython3 run.py")
|
||
if in.ScriptCaptureStatus != "complete" || len(in.Scripts) != 1 || in.Scripts[0].Content != "print('known')\n" {
|
||
t.Fatalf("literal pipeline producer lost: %+v", in)
|
||
}
|
||
}
|
||
for _, command := range []string{
|
||
"cat > run.py 2>&1 | tee run.log <<'PY'\nprint('consumer input')\nPY\npython3 run.py",
|
||
"cat > run.py <<'PY' 2>&1 | tee run.log &\nprint('background')\nPY\npython3 run.py",
|
||
"cat > run.py <<'PY' >&2 | tee run.log\nprint('wrong output')\nPY\npython3 run.py",
|
||
} {
|
||
if in := reviewCommandForTest(t, dir, command); in.ScriptCaptureStatus != "incomplete" {
|
||
t.Fatalf("opaque pipeline accepted: %+v", in)
|
||
}
|
||
}
|
||
if _, err := os.Stat(filepath.Join(dir, "run.py")); !os.IsNotExist(err) {
|
||
t.Fatal("pipeline analysis executed a writer")
|
||
}
|
||
}
|