Examples testing
This commit is contained in:
parent
bde30f5d99
commit
10d1223f9f
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ coverage
|
|||||||
doc/musique
|
doc/musique
|
||||||
doc/build
|
doc/build
|
||||||
doc/source/api
|
doc/source/api
|
||||||
|
__pycache__
|
||||||
|
@ -19,6 +19,12 @@ Interpreter języka Musique. Możliwy do wykorzystywania jako:
|
|||||||
|
|
||||||
- `make unit-tests` - Uruchamia testy jednostkowe interpretera
|
- `make unit-tests` - Uruchamia testy jednostkowe interpretera
|
||||||
- `make unit-test-coverage` - Uruchamia raport pokrycia kodu przez testy jednostkowe
|
- `make unit-test-coverage` - Uruchamia raport pokrycia kodu przez testy jednostkowe
|
||||||
|
- `etc/tools/test.py test examples` - Uruchamia testy zachowań przykładów
|
||||||
|
- `etc/tools/test.py record examples` - Nagrywa testy zachowań przykładów
|
||||||
|
|
||||||
|
### Debugowanie
|
||||||
|
|
||||||
|
- `etc/tools/log-function-calls.sh` - Tworzy listę wywołań funkcji używając GDB
|
||||||
|
|
||||||
## Budowa projektu
|
## Budowa projektu
|
||||||
|
|
||||||
@ -29,6 +35,7 @@ Interpreter języka Musique. Możliwy do wykorzystywania jako:
|
|||||||
├── doc Dokumentacja języka, interpretera
|
├── doc Dokumentacja języka, interpretera
|
||||||
│ ├── build Miejsce produkcji dokumentacji
|
│ ├── build Miejsce produkcji dokumentacji
|
||||||
│ └── source Źródła dokumentacji Sphinx
|
│ └── source Źródła dokumentacji Sphinx
|
||||||
|
├── etc/tools Dodatkowe narzędzia
|
||||||
├── lib Zewnętrzne zależności projektu
|
├── lib Zewnętrzne zależności projektu
|
||||||
│ ├── expected
|
│ ├── expected
|
||||||
│ └── ut
|
│ └── ut
|
||||||
|
146
etc/tools/test.py
Executable file
146
etc/tools/test.py
Executable file
@ -0,0 +1,146 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from glob import glob
|
||||||
|
from sys import argv
|
||||||
|
from sys import exit
|
||||||
|
import os.path
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
from unittest import case
|
||||||
|
|
||||||
|
Interpreter = "bin/musique"
|
||||||
|
|
||||||
|
def directories_in_path(path: str):
|
||||||
|
dirs = []
|
||||||
|
while True:
|
||||||
|
dirname, _ = os.path.split(path)
|
||||||
|
if not dirname:
|
||||||
|
break
|
||||||
|
dirs.append(dirname)
|
||||||
|
path = dirname
|
||||||
|
if path == "/":
|
||||||
|
break
|
||||||
|
dirs.reverse()
|
||||||
|
return dirs
|
||||||
|
|
||||||
|
def mkdir_recursive(path: str):
|
||||||
|
for directory in directories_in_path(path):
|
||||||
|
try:
|
||||||
|
os.mkdir(directory)
|
||||||
|
except FileExistsError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Test_Case:
|
||||||
|
returncode = 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_file(fname : str):
|
||||||
|
with open(fname) as f:
|
||||||
|
content = json.load(f)
|
||||||
|
tc = Test_Case()
|
||||||
|
for name in content:
|
||||||
|
# assert hasattr(tc, name), "Test_Case does not have attribute %s" % (name,)
|
||||||
|
setattr(tc, name, content[name])
|
||||||
|
return tc
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_run(run, flags=[]):
|
||||||
|
tc = Test_Case()
|
||||||
|
for attr in ["returncode", "stdout", "stderr"]: ### TODO FLAGS
|
||||||
|
try:
|
||||||
|
run_attr = getattr(run, attr).decode()
|
||||||
|
except (UnicodeDecodeError, AttributeError):
|
||||||
|
run_attr = getattr(run, attr)
|
||||||
|
|
||||||
|
setattr(tc, attr, run_attr)
|
||||||
|
setattr(tc, "flags", flags)
|
||||||
|
return tc
|
||||||
|
|
||||||
|
def save(self, fname : str):
|
||||||
|
j = {}
|
||||||
|
for attr in ["returncode", "stdout", "stderr", "flags"]:
|
||||||
|
j[attr] = getattr(self, attr)
|
||||||
|
|
||||||
|
mkdir_recursive(fname)
|
||||||
|
with open(fname, 'w') as f:
|
||||||
|
json.dump(j, f, indent=4)
|
||||||
|
|
||||||
|
def cmd_run_echoed(cmd, **kwargs):
|
||||||
|
print("[CMD] %s" % " ".join(map(shlex.quote, cmd)))
|
||||||
|
return subprocess.run(cmd, **kwargs)
|
||||||
|
|
||||||
|
def find_path_for_test_case(path: str) -> str:
|
||||||
|
directory, filename = os.path.split(path)
|
||||||
|
return (directory if directory else ".") + "/.tests_cache/" + filename + ".json"
|
||||||
|
|
||||||
|
def run_tests(file_paths: list):
|
||||||
|
return_code = 0
|
||||||
|
for program_file in file_paths:
|
||||||
|
test_case_file = find_path_for_test_case(program_file)
|
||||||
|
tc = Test_Case.from_file(test_case_file) if os.path.exists(test_case_file) else Test_Case()
|
||||||
|
|
||||||
|
flags_list = [Interpreter]
|
||||||
|
if hasattr(tc, "flags"):
|
||||||
|
flags_list.extend(tc.flags)
|
||||||
|
flags_list.append(program_file)
|
||||||
|
|
||||||
|
res = cmd_run_echoed(flags_list, capture_output=True)
|
||||||
|
|
||||||
|
for attr in [a for a in dir(tc) if a in ["returncode", "stdout", "stderr"]]:
|
||||||
|
tc_attr = getattr(tc, attr)
|
||||||
|
res_attr = getattr(res, attr)
|
||||||
|
try:
|
||||||
|
res_attr = res_attr.decode()
|
||||||
|
except (UnicodeDecodeError, AttributeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if tc_attr != res_attr:
|
||||||
|
print(f"[ERROR] Failed test {program_file}")
|
||||||
|
print(f"Expected {attr} = ")
|
||||||
|
print(tc_attr)
|
||||||
|
print(f"Received {attr} = ")
|
||||||
|
print(res_attr)
|
||||||
|
return_code = 1
|
||||||
|
exit(return_code)
|
||||||
|
|
||||||
|
def record_tests(file_paths: list):
|
||||||
|
for program_file in file_paths:
|
||||||
|
test_case_file = find_path_for_test_case(program_file)
|
||||||
|
|
||||||
|
res = cmd_run_echoed([Interpreter, program_file], capture_output=True)
|
||||||
|
tc = Test_Case.from_run(res, [])
|
||||||
|
tc.save(test_case_file)
|
||||||
|
|
||||||
|
# list of files to test
|
||||||
|
def main():
|
||||||
|
file_paths, mode = [], run_tests
|
||||||
|
|
||||||
|
if len(argv) < 2:
|
||||||
|
print("[ERROR] Expected mode argument (either 'record' or 'test')")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
if argv[1] == "test":
|
||||||
|
mode = run_tests
|
||||||
|
elif argv[1] == "record":
|
||||||
|
mode = record_tests
|
||||||
|
else:
|
||||||
|
print(f"[ERROR] Unrecognized mode '{argv[1]}'")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
if len(argv) < 3:
|
||||||
|
print("[ERROR] Expected test case")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
for path in argv[2:]:
|
||||||
|
if os.path.exists(path):
|
||||||
|
if os.path.isdir(path):
|
||||||
|
file_paths.extend(glob(f"{path}/*.mq"))
|
||||||
|
else:
|
||||||
|
file_paths.append(path)
|
||||||
|
|
||||||
|
mode(file_paths)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
6
examples/.tests_cache/arithmetic.mq.json
Normal file
6
examples/.tests_cache/arithmetic.mq.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"returncode": 0,
|
||||||
|
"stdout": "4\n30\n42\nnil\n",
|
||||||
|
"stderr": "",
|
||||||
|
"flags": []
|
||||||
|
}
|
6
examples/.tests_cache/church.mq.json
Normal file
6
examples/.tests_cache/church.mq.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"returncode": 0,
|
||||||
|
"stdout": "100\n200\n120\nnil\n",
|
||||||
|
"stderr": "",
|
||||||
|
"flags": []
|
||||||
|
}
|
6
examples/.tests_cache/factorial.mq.json
Normal file
6
examples/.tests_cache/factorial.mq.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"returncode": 0,
|
||||||
|
"stdout": "1\n2\n6\n24\n120\n720\n5040\n40320\n362880\n3628800\nnil\n",
|
||||||
|
"stderr": "",
|
||||||
|
"flags": []
|
||||||
|
}
|
6
examples/.tests_cache/variables.mq.json
Normal file
6
examples/.tests_cache/variables.mq.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"returncode": 0,
|
||||||
|
"stdout": "11\nnil\n",
|
||||||
|
"stderr": "",
|
||||||
|
"flags": []
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user