From 7672da6ad3ebf37f05d2bc179a960d0696067bba Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Sun, 4 May 2025 12:40:40 +0200 Subject: [PATCH] Extends the test framework to prepare for compilation tests Fix: it2 would have different working for emit eof --- 0-lang0py/lang0py.py | 8 +- 1-lang0py/lang0py.lang0 | 4 +- 2-lang0c/lang0c.lang0 | 2 +- README.md | 14 +++ tests/.gitignore | 3 +- tests/Makefile | 35 +++--- .../{ => test_stdlib_constants}/.gitignore | 0 tests/build/test_stdlib_functions/.gitignore | 14 +++ tests/generate-recipes.py | 103 ++++++++++++++++++ tests/stdlibemit.lang0 | 3 - .../test_stdlib_constants/test_eof.exp.stdout | 1 + tests/test_stdlib_constants/test_eof.lang0 | 3 + .../test_stdlib_constants/test_eol.exp.stdout | 1 + tests/test_stdlib_constants/test_eol.lang0 | 3 + .../test_quote.exp.stdout | 1 + tests/test_stdlib_constants/test_quote.lang0 | 3 + .../test_emit.exp.stdout | 1 + tests/test_stdlib_functions/test_emit.lang0 | 4 + 18 files changed, 170 insertions(+), 33 deletions(-) rename tests/build/{ => test_stdlib_constants}/.gitignore (100%) create mode 100644 tests/build/test_stdlib_functions/.gitignore create mode 100644 tests/generate-recipes.py delete mode 100644 tests/stdlibemit.lang0 create mode 100644 tests/test_stdlib_constants/test_eof.exp.stdout create mode 100644 tests/test_stdlib_constants/test_eof.lang0 create mode 100644 tests/test_stdlib_constants/test_eol.exp.stdout create mode 100644 tests/test_stdlib_constants/test_eol.lang0 create mode 100644 tests/test_stdlib_constants/test_quote.exp.stdout create mode 100644 tests/test_stdlib_constants/test_quote.lang0 create mode 100644 tests/test_stdlib_functions/test_emit.exp.stdout create mode 100644 tests/test_stdlib_functions/test_emit.lang0 diff --git a/0-lang0py/lang0py.py b/0-lang0py/lang0py.py index 7de85d1..979b661 100644 --- a/0-lang0py/lang0py.py +++ b/0-lang0py/lang0py.py @@ -2,14 +2,14 @@ import os import sys def emit(string): - sys.stdout.write(string) + sys.stdout.buffer.write(string.encode('latin_1')) sys.stdout.flush() def trace(header, value): if os.environ.get('TRACE'): sys.stderr.write(f'{header}={value!r}\n') -eof = chr(0) +eof = chr(0xFF) eol = chr(10) quote = chr(34) PEEK = None @@ -354,14 +354,14 @@ def emitheader(): emitln(" return a + b[0]") emitln("") emitln("def emit(string):") - emitln(" sys.stdout.write(string)") + emitln(" sys.stdout.buffer.write(string.encode('latin_1'))") emitln(" sys.stdout.flush()") emitln("") emitln("def trace(header, value):") emitln(" if os.environ.get('TRACE'):") emitln(" sys.stderr.write(f'{header}={value!r}\\n')") emitln("") - emitln("eof = chr(0)") + emitln("eof = chr(0xFF)") emitln("eol = chr(10)") emitln("quote = chr(34)") emitln("") diff --git a/1-lang0py/lang0py.lang0 b/1-lang0py/lang0py.lang0 index d153d14..800696c 100644 --- a/1-lang0py/lang0py.lang0 +++ b/1-lang0py/lang0py.lang0 @@ -431,14 +431,14 @@ emitheader: emitln " return a + b[0]" emitln "" emitln "def emit(string):" - emitln " sys.stdout.write(string)" + emitln " sys.stdout.buffer.write(string.encode('latin_1'))" emitln " sys.stdout.flush()" emitln "" emitln "def trace(header, value):" emitln " if os.environ.get('TRACE'):" emitln " sys.stderr.write(f'{header}={value!r}\\n')" emitln "" - emitln "eof = chr(0)" + emitln "eof = chr(0xFF)" emitln "eol = chr(10)" emitln "quote = chr(34)" emitln "" diff --git a/2-lang0c/lang0c.lang0 b/2-lang0c/lang0c.lang0 index 9c19c72..9ba0503 100644 --- a/2-lang0c/lang0c.lang0 +++ b/2-lang0c/lang0c.lang0 @@ -532,7 +532,7 @@ emitheader: emitln "#include " emitln "" emitln "" - emitln "char var_eof[2] = {-1, 0};" + emitln "char var_eof[2] = {0xFF, 0};" emitln "char var_eol[2] = {10, 0};" emitln "char var_quote[2] = {34, 0};" emitln "char var_false[1] = {0};" diff --git a/README.md b/README.md index 1bee0d4..0ec0023 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,20 @@ Calls the given function with the given arguments. If the function's return valu Writes the name and value of the variable passed to stderr if the TRACE environment variable is set. +### Standard library constants + +#### eof + +End of file character, zero byte. + +#### eol + +End of line character. + +#### quote + +Double quote character. + ### Standard library functions #### addstringchar a b diff --git a/tests/.gitignore b/tests/.gitignore index fa47d71..01752f5 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1 +1,2 @@ -/*.results +/*/*.act.* +/Makefile.mk diff --git a/tests/Makefile b/tests/Makefile index 47dea90..faa525c 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,6 +1,5 @@ .SUFFIXES: .PHONY: all clean -.PRECIOUS: build/%.it0.py build/%.it0.c .DELETE_ON_ERROR: PYVERSION=3.12 @@ -8,31 +7,23 @@ PYPREFIX=/usr CYTHON=cython3 CC=gcc -# builtincheckfalse is separate since it's supposed to crash. It's a test for the test 'framework', if you will. -TESTLIST=$(shell ls *.lang0 | grep -v 'builtincheckfalse' | sed 's/.lang0//') +all: verify-results -all: check +Makefile.mk: generate-recipes.py $(wildcard test_stdlib_constants/*.exp.*) $(wildcard test_stdlib_functions/*.exp.*) + python3 generate-recipes.py Makefile.mk it0 it1 it2 -check: all-it0.results all-it1.results all-it2.results - ! grep -v 'Success' $^ - -all-it0.results: $(addprefix build/,$(addsuffix .it0, $(TESTLIST))) build/builtincheckfalse.it0 - -rm -f $@ - cat test-input.txt | build/builtincheckfalse.it0 2> /dev/null 2>&1 | grep 'Success' - $(foreach test,$(TESTLIST), echo -n "$(test) " >> $@; cat test-input.txt | ./build/$(test).it0 >> $@ ; echo "" >> $@ ;) - -all-it1.results: $(addprefix build/,$(addsuffix .it1, $(TESTLIST))) build/builtincheckfalse.it1 - -rm -f $@ - cat test-input.txt | build/builtincheckfalse.it1 2> /dev/null 2>&1 | grep 'Success' - $(foreach test,$(TESTLIST), echo -n "$(test) " >> $@; cat test-input.txt | ./build/$(test).it1 >> $@ ; echo "" >> $@ ;) - -all-it2.results: $(addprefix build/,$(addsuffix .it2, $(TESTLIST))) build/builtincheckfalse.it2 - -rm -f $@ - cat test-input.txt | build/builtincheckfalse.it2 2> /dev/null 2>&1 | grep 'Success' - $(foreach test,$(TESTLIST), echo -n "$(test) " >> $@; cat test-input.txt | ./build/$(test).it2 >> $@ ; echo "" >> $@ ;) +include Makefile.mk clean: - -rm -f *.results build/*.it0* build/*.it1* build/*.it2* + find build -name '*.c' -delete + find build -name '*.it0' -delete + find build -name '*.it1' -delete + find build -name '*.it2' -delete + find build -name '*.py' -delete + find build -name '*.tmp' -delete + find . -name '*.act.*.result' -delete + find . -name '*.act.*.stdout' -delete + find . -name '*.act.*.stderr' -delete ### # it0 diff --git a/tests/build/.gitignore b/tests/build/test_stdlib_constants/.gitignore similarity index 100% rename from tests/build/.gitignore rename to tests/build/test_stdlib_constants/.gitignore diff --git a/tests/build/test_stdlib_functions/.gitignore b/tests/build/test_stdlib_functions/.gitignore new file mode 100644 index 0000000..4dfaadf --- /dev/null +++ b/tests/build/test_stdlib_functions/.gitignore @@ -0,0 +1,14 @@ +/*.it0 +/*.it0.c +/*.it0.o +/*.it0.py +/*.it0.py.tmp +/*.it1 +/*.it1.c +/*.it1.o +/*.it1.py +/*.it1.py.tmp +/*.it2 +/*.it2.c +/*.it2.c.tmp +/*.it2.o diff --git a/tests/generate-recipes.py b/tests/generate-recipes.py new file mode 100644 index 0000000..023a06a --- /dev/null +++ b/tests/generate-recipes.py @@ -0,0 +1,103 @@ +import glob +import os +import sys + +class Rule: + def __init__(self, target: str, prerequisites: list[str], recipe: list[str]) -> None: + self.target = target + self.prerequisites = prerequisites + self.recipe = recipe + + def make_lines(self): + return [ + self.target + ': ' + ' '.join(self.prerequisites), + ] + [ + '\t' + line + for line in self.recipe + ] + [''] + +def get_file_list(): + return glob.glob('test_*/*.lang0') + +def make_rules(file_path, lang0it): + result: list[Rule] = [] + + act_result = file_path.replace('.lang0', f'.act.{lang0it}.result') + act_stdout = file_path.replace('.lang0', f'.act.{lang0it}.stdout') + act_stderr = file_path.replace('.lang0', f'.act.{lang0it}.stderr') + stdin = file_path.replace('.lang0', '.stdin') + exp_stdout = file_path.replace('.lang0', '.exp.stdout') + exp_stderr = file_path.replace('.lang0', '.exp.stderr') + + program = file_path.replace('.lang0', f'.{lang0it}') + + if os.path.isfile(exp_stdout) or os.path.isfile(exp_stderr): + result.append(Rule( + act_stdout, + ['build/' + program], + [ + (f'cat {stdin} | ' if os.path.isfile(stdin) else 'echo x | ') + + f'build/{program} 1> {act_stdout} 2> {act_stderr}', + ] + )) + + exp_list = [exp_stdout, exp_stderr] + act_list = [act_stdout, act_stderr] + + result.append(Rule( + act_result, + [ + fil + for exp_file, act_file in zip(exp_list, act_list, strict=True) + if os.path.isfile(exp_file) + for fil in [exp_file, act_file] + ], + [ + 'rm -f $@', + ] + [ + f'-diff {exp_file} {act_file} >> $@' + for exp_file, act_file in zip(exp_list, act_list, strict=True) + if os.path.isfile(exp_file) + ] + )) + + assert result[-1].prerequisites, f'Missing expectations for {file_path}' + + return result + +def main(program, write_to, *lang0it): + rule_list_list = [ + make_rules(file_path, it) + for file_path in get_file_list() + for it in lang0it + ] + + result_targets = [ + rule.target + for rule_list in rule_list_list + for rule in rule_list + if rule.target.endswith('.result') + ] + + rule_list_list.append([Rule( + 'verify-results', + result_targets, + [ + '@echo Finding failed test results...', + '@find $^ -not -empty -print -exec false {} +', + '@echo All tests passed.' + ] + )]) + + data = '\n'.join( + line + for rule_list in rule_list_list + for rule in rule_list + for line in rule.make_lines() + ) + + with open(write_to, 'wt', encoding='ASCII') as fil: + fil.write(data) + +if __name__ == '__main__': + main(*sys.argv) diff --git a/tests/stdlibemit.lang0 b/tests/stdlibemit.lang0 deleted file mode 100644 index 26a5d02..0000000 --- a/tests/stdlibemit.lang0 +++ /dev/null @@ -1,3 +0,0 @@ -main: - emit "Success" -/ diff --git a/tests/test_stdlib_constants/test_eof.exp.stdout b/tests/test_stdlib_constants/test_eof.exp.stdout new file mode 100644 index 0000000..ce542ef --- /dev/null +++ b/tests/test_stdlib_constants/test_eof.exp.stdout @@ -0,0 +1 @@ +ÿ \ No newline at end of file diff --git a/tests/test_stdlib_constants/test_eof.lang0 b/tests/test_stdlib_constants/test_eof.lang0 new file mode 100644 index 0000000..85773bd --- /dev/null +++ b/tests/test_stdlib_constants/test_eof.lang0 @@ -0,0 +1,3 @@ +main: + emit eof +/ diff --git a/tests/test_stdlib_constants/test_eol.exp.stdout b/tests/test_stdlib_constants/test_eol.exp.stdout new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/test_stdlib_constants/test_eol.exp.stdout @@ -0,0 +1 @@ + diff --git a/tests/test_stdlib_constants/test_eol.lang0 b/tests/test_stdlib_constants/test_eol.lang0 new file mode 100644 index 0000000..7b085b0 --- /dev/null +++ b/tests/test_stdlib_constants/test_eol.lang0 @@ -0,0 +1,3 @@ +main: + emit eol +/ diff --git a/tests/test_stdlib_constants/test_quote.exp.stdout b/tests/test_stdlib_constants/test_quote.exp.stdout new file mode 100644 index 0000000..9d68933 --- /dev/null +++ b/tests/test_stdlib_constants/test_quote.exp.stdout @@ -0,0 +1 @@ +" \ No newline at end of file diff --git a/tests/test_stdlib_constants/test_quote.lang0 b/tests/test_stdlib_constants/test_quote.lang0 new file mode 100644 index 0000000..4bd5bfa --- /dev/null +++ b/tests/test_stdlib_constants/test_quote.lang0 @@ -0,0 +1,3 @@ +main: + emit quote +/ diff --git a/tests/test_stdlib_functions/test_emit.exp.stdout b/tests/test_stdlib_functions/test_emit.exp.stdout new file mode 100644 index 0000000..5dd01c1 --- /dev/null +++ b/tests/test_stdlib_functions/test_emit.exp.stdout @@ -0,0 +1 @@ +Hello, world! \ No newline at end of file diff --git a/tests/test_stdlib_functions/test_emit.lang0 b/tests/test_stdlib_functions/test_emit.lang0 new file mode 100644 index 0000000..eec134b --- /dev/null +++ b/tests/test_stdlib_functions/test_emit.lang0 @@ -0,0 +1,4 @@ +main: + emit "Hello" + emit ", world!" +/