Add tester

This commit is contained in:
savalet
2025-04-27 18:44:43 +02:00
parent a47aa88454
commit 24f4215cab
5 changed files with 233 additions and 1000 deletions

1
.gitignore vendored
View File

@@ -34,6 +34,7 @@ result
# Cache
.cache
.fast
__pycache__
# Coverage
*.gc??

File diff suppressed because it is too large Load Diff

2
fixtures/exec.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
echo OK

104
validation_tests.py Normal file
View File

@@ -0,0 +1,104 @@
from validator import Test
TESTS = [
Test(
key="SIMPLE",
name="simple commands",
cmds=[
"ls\n",
"uname\n",
"who\n",
]
),
Test(
key="ARGS",
name="multiple arguments",
cmds=[
"echo kek\n",
"ls --all\n",
"cat -e Makefile\n",
"find . -type f\n",
"ls -a . . .\n",
"wc --chars --words --lines Makefile\n",
],
depends_on=("SIMPLE",)
),
Test(
key="BAD CMD",
name="invalid commands",
cmds=[
"plopdjksjesi\n",
"ls -PQRT\n",
],
depends_on=("ARGS",)
),
Test(
key="PATH",
name="path handing",
cmds=[
"/bin/sh --version\n",
"/../bin/sh --version\n",
"~/../../bin/sh --version\n",
"fixtures/exec.sh\n",
],
depends_on=("ARGS",)
),
Test(
key="ENV",
name="setenv / unsetenv",
cmds=[
"setenv Hello plop\n",
"setenv 1 nope\n",
"setenv @\n",
],
depends_on=("ARGS",)
),
Test(
key="PWD",
name="pwd",
cmds=["pwd\n"],
depends_on=("SIMPLE",)
),
Test(
key="SEMICOLON",
name="semicolon",
cmds=[
";\n",
"echo a; echo b\n",
"echo a;; echo b\n",
"echo a; echo b;\n",
],
depends_on=("ARGS",)
),
Test(
key="CD",
name="cd builtin",
cmds=[
"cd /; pwd\n",
"cd; pwd\n",
"cd ~; pwd\n",
"cd .; pwd\n",
"cd ..; pwd\n",
"cd /..; pwd\n",
"cd a\n",
"cd . .\n",
"cd src/; pwd\n",
],
depends_on=("ARGS", "PWD", "SEMICOLON")
),
Test(
key="FMT",
name="line formatting (space and tabs)",
cmds=[
" echo plop\n",
"\techo plop\n",
" \techo plop\n",
"echo\tplop\n",
"echo plop\n",
"echo\t\tplop\n",
"echo plop\t \n",
],
depends_on=("ARGS",)
),
]

126
validator.py Normal file
View File

@@ -0,0 +1,126 @@
from __future__ import annotations
from dataclasses import dataclass
import difflib
import subprocess
@dataclass
class Result:
stdout: str
stderr: str
exit_code: int
def run_shell(shell_cmd, user_cmd, timeout=2) -> Result:
process = subprocess.Popen(
shell_cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
try:
stdout, stderr = process.communicate(user_cmd + "\n", timeout=timeout)
except subprocess.TimeoutExpired:
process.kill()
stdout, stderr = process.communicate()
exit_code = process.returncode
return Result(stdout.strip(), stderr.strip(), exit_code)
def print_diff(cmd: str, out: Result, exp: Result):
print(f"\nFail: \033[34m{cmd!r}\033[0m") # ]]
if out.stdout != exp.stdout:
print("\n \033[36m--- STDOUT diff ---\033[0m") # ]]
diff = difflib.unified_diff(
out.stdout.splitlines(), exp.stdout.splitlines(),
fromfile="tcsh", tofile="42sh", lineterm="")
print(" " + "\n ".join(list(diff)[2:]))
if out.stderr != exp.stderr:
print("\n \033[36m--- STDERR diff ---\033[0m") # ]]
diff = difflib.unified_diff(
out.stderr.splitlines(), exp.stderr.splitlines(),
fromfile="tcsh", tofile="42sh", lineterm="")
print(" " + "\n ".join(list(diff)[2:]))
elif out.exit_code != exp.exit_code:
print("\n \033[36m--- EXIT CODE mismatch ---\033[0m\n" # ]]
f"42sh: {out.exit_code} | tcsh: {exp.exit_code}")
print("\n")
class Test:
def __init__(
self,
key: str,
name: str,
cmds: list[str],
depends_on: tuple[str, ...] = ()
):
self.key = key
self.name = name
self.cmds = cmds
self.depends_on = depends_on
self.has_run = False
def _run_each_cmd(self, cmd: str):
result_42sh = run_shell(["./42sh"], cmd)
result_tcsh = run_shell(["tcsh"], cmd)
if result_42sh.exit_code == 84 and result_tcsh.exit_code != 0:
result_tcsh.exit_code = 84
if result_42sh == result_tcsh:
print("\033[32m.\033[0m", end='') # ]]
return None
print("\033[31m.\033[0m", end='') # ]]
return cmd, result_42sh, result_tcsh
def run(self, test_map):
if self.has_run:
return
self.has_run = True
for dep_name in self.depends_on:
dep = test_map.get(dep_name)
if dep is None:
print("\033[33mOKWarning\033[0m:" # ]]
"Missing dependency:", dep_name)
continue
if not dep.has_run:
dep.run(test_map)
print(self.name, end=" ")
failures = []
for cmd in self.cmds:
if (failure := self._run_each_cmd(cmd)) is not None:
failures.append(failure)
if not failures:
print(" \033[32mOK\033[0m") # ]]
else:
print()
for fail in failures:
print_diff(*fail)
def main():
from validation_tests import TESTS
test_map = {test.key: test for test in TESTS}
for test in TESTS:
test.run(test_map=test_map)
if __name__ == "__main__":
main()