mirror of
https://github.com/Savapitech/42sh.git
synced 2026-01-18 16:57:28 +01:00
Add tester
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -34,6 +34,7 @@ result
|
||||
# Cache
|
||||
.cache
|
||||
.fast
|
||||
__pycache__
|
||||
|
||||
# Coverage
|
||||
*.gc??
|
||||
|
||||
1000
fixtures/big.sh
1000
fixtures/big.sh
File diff suppressed because it is too large
Load Diff
2
fixtures/exec.sh
Executable file
2
fixtures/exec.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env sh
|
||||
echo OK
|
||||
104
validation_tests.py
Normal file
104
validation_tests.py
Normal 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
126
validator.py
Normal 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()
|
||||
Reference in New Issue
Block a user