1 # Ocean Interpreter test code
3 Regular testing is, of course, important for developing any software.
4 The Ocean interpreted is no exception. This document allows easy
7 - a collection of test program
8 - the expected output of these programs when run with various arguments
9 - some "Makefile" code to tie it all together.
11 Three different sorts of tests are run. As soon as any fail, the whole
14 1/ Each program is run and the output is compared against the expected
16 2/ Each program is then run under valgrind, and an error is reported
17 if valgrind detects an error, or if it reports and lost or unfreed
19 3/ Each program is parsed and printed, then the result is parsed and printed.
20 The two results must match.
21 4/ Each program is run with a version of `oceani` with test-coverage
22 recording enabled. Once all programs have successfully run and
23 all the coverage data is available, we check that all lines have
24 been tested at least once. A few exceptions are allowed such as
25 lines that call `abort()`. If any non-exceptional lines have not
26 been run, this final test fails.
27 Until the tests suite is (more) completed, we only throw and error
28 if fewer than 75% of the lines have been tested.
30 Each test has a name, which used to identify the section in this file, and optionally some
31 arguments separated from the name by commas. For each test, there is a section named
32 "output:" followed by the name-and-arguments.
34 ###### File: oceani-tests.mk
39 tests:: oceani_test_suite
40 oceani_test_suite: oceani coverage_oceani
41 @./parsergen --report --LR1 --tag Parser oceani.mdc | grep " - no conflicts" > /dev/null || \
42 { echo "Grammar container conflicts, please review" ; exit 1; }
43 @rm -rf coverage; mkdir -p coverage
45 @for T in $(oceani_tests); do \
46 echo -n "Test $$T.. "; \
47 i="$$IFS"; IFS=,; set $$T; IFS="$$i"; t=$$1; shift; \
48 ./md2c oceani-tests.mdc "output: $$T" | grep -v '^#' > .tmp.want; \
49 ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > .tmp.have; \
50 if ! cmp -s .tmp.want .tmp.have; then \
51 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
52 echo -n "printing.. "; \
53 echo '``````' > .tmp.code1; echo '``````' > .tmp.code2 ;\
54 ./oceani --noexec --print --section "test: $$t" oceani-tests.mdc >> .tmp.code1; \
55 ./oceani --noexec --print .tmp.code1 >> .tmp.code2 ;\
56 if ! cmp -s .tmp.code1 .tmp.code2; then \
57 echo " Failed"; diff -u .tmp.code1 .tmp.code2; exit 1 ; fi ; \
58 echo -n "exec-after-print.. "; \
59 ./oceani .tmp.code1 $${1+"$$@"} > .tmp.have ; \
60 if ! cmp -s .tmp.want .tmp.have; then \
61 echo " FAILED"; diff -u .tmp.want .tmp.have; exit 1;fi; \
63 ./coverage_oceani --print --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > /dev/null ; \
64 ./coverage_oceani -tpbn --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1; \
69 @gcov -o coverage oceani.mdc > /dev/null 2> /dev/null
70 @mv *.gcov coverage ; [ -f .gcov ] && mv .gcov coverage
71 @ awk '/NOTEST/ { next } /^ *[1-9]/ {ran+=1} /^ *###/ {skip+=1} \
72 END {printf "coverage: %6.2f%%\n", ran * 100 / (ran + skip); \
73 if (ran < (ran + skip) *0.93) exit(1) }' \
74 coverage/oceani.mdc.gcov
77 @for T in $(oceani_tests); do \
78 echo -n "Valgrind $$T.. "; \
79 i="$$IFS"; IFS=,; set $$T; IFS="$$i"; t=$$1; shift; \
80 if ! valgrind --error-exitcode=1 --log-file=.tmp.valg ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} \
81 > /dev/null 2>&1 ; then \
82 echo "FAILED"; cat .tmp.valg; exit 1; fi ; \
83 if grep 'LEAK SUMMARY' .tmp.valg > /dev/null; then \
84 echo "valgrind found LEAKS"; cat .tmp.valg ; exit 1 ; fi; \
85 if grep 'in use at exit [1-9]' .tmp.valg > /dev/null; then \
86 echo "valgrind found memory in use at exit"; cat .tmp.valg ; exit 1 ; fi; \
90 coverage_oceani: oceani.c
91 $(CC) $(CFLAGS) --coverage -fprofile-dir=coverage -o coverage_oceani oceani.c $(LDLIBS)
93 ## Values and variables
95 The first test stores values in variables and performs various
99 oceani_tests += "valvar"
105 a := 23; b:=12 ; b1 := -b
106 print a, b, a+b, a-b, a*b, a/b, a%b
107 print a<b, a<=b, a>b, a>=b, a<a, a==b, a==a
108 print +a, +b, +b1, -a, -b, -b1
109 x := True; y := False
110 print x and y, x or y, x and x, y or y, x and not x, x < y
112 c ::= "This is a string"
113 d ::= " field theory"
116 aconst :: string = "unchanging"
118 ###### output: valvar
120 23 12 35 11 276 1.91667 11
121 False False True True False False True
123 False True True False False False
124 This is a string field theory This is a string field theory
126 Next we change the value of variables
129 oceani_tests += "setvar"
136 a = (a + a) * (a + a)
140 ###### output: setvar
146 oceani_tests += "consts"
151 four ::= 2 + 2 ; five ::= 10/2
152 const pie ::= "I like Pie";
153 cake ::= "The cake is"
157 print "Hello World, what lovely oceans you have!"
158 print "are there", five, "?"
159 print pi, pie, "but", cake
161 ###### output: consts
162 Hello World, what lovely oceans you have!
164 3.14159 I like Pie but The cake is a lie
166 ## Conditions and Loops
168 Now we need to test if/else and some different loops
171 oceani_tests += cond_loop
173 ###### test: cond_loop
181 for b:=1; then b=b+b; while b < 100:
184 // Newtons method for square root of 2
190 current := guess * guess
191 use +(current - target) > 0.000000001
193 guess = (guess + (target / guess) ) / 2
196 print "error is ", target - guess * guess
198 for j:=0; then j = j+3 ; while j < 10:
199 if j != 0 and then 20 / j > 3:
200 print "20 /", j," =", 20 / j
202 print "I won't calculate 20 /", j
203 pi ::= 3.1415926535897
204 if 355/113 == pi or else +(pi - 355/113) < 0.001:
206 print "lower" if 355/113 < pi else "higher"
208 ###### output: cond_loop
215 error is -4.51095e-12
216 I won't calculate 20 / 0
219 I won't calculate 20 / 9
225 The demonstration code presented in the interpreted is suitable for the test suite.
226 Here I break it into two parts, keeping the array code separate.
229 oceani_tests += "sayhello,55,33"
230 oceani_tests += "sayhello,12,60"
232 ###### test: sayhello
235 print "Hello World, what lovely oceans you have!"
236 /* When a variable is defined in both branches of an 'if',
237 * and used afterwards, the variables are merged.
243 print "Is", A, "bigger than", B,"? ", bigger
244 /* If a variable is not used after the 'if', no
245 * merge happens, so types can be different
248 double:string = "yes"
249 print A, "is more than twice", B, "?", double
252 print "double", B, "is", double
263 print "GCD of", A, "and", B,"is", a
265 print a, "is not positive, cannot calculate GCD"
267 print b, "is not positive, cannot calculate GCD"
272 print "Fibonacci:", f1,f2,
281 /* Binary search... */
298 print "Yay, I found", target
300 print "Closest I found was", mid
302 ###### output: sayhello,55,33
303 Hello World, what lovely oceans you have!
304 Is 55 bigger than 33 ? yes
306 GCD of 55 and 33 is 11
307 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
308 Closest I found was 77.3438
310 ###### output: sayhello,12,60
311 Hello World, what lovely oceans you have!
312 Is 12 bigger than 60 ? no
314 GCD of 12 and 60 is 12
315 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
316 Closest I found was 77.3438
319 oceani_tests += "insert_sort"
320 ###### test: insert_sort
325 for i:=1; then i = i + 1; while i < size:
326 n := list[i-1] * list[i-1]
327 list[i] = (n / 100) % 10000
330 for i:=0; then i = i + 1; while i < size:
331 print "list[",i,"]=",list[i]
333 for i := 1; then i=i+1; while i < size:
334 for j:=i-1; then j=j-1; while j >= 0:
335 if list[j] > list[j+1]:
340 for i:=0; then i = i + 1; while i < size:
341 print "list[",i,"]=",list[i]
343 ###### output: insert_sort
459 We already have some array tests, but this is where we put other
460 ad-hoc things array related.
463 oceani_tests += arrays
471 bools[3] = strings[1] == "Hello"
472 bools[1] = strings[2] <= "there"
474 for i:=0; then i=i+1; while i<5:
478 ###### output: arrays
479 False True False False False
481 ## Test code with syntax errors
483 Syntax errors aren't handled well yet - the result is almost always a
484 single message about the first error. So this section will be fairly
485 thin until we add proper parsing recovery in the face of common errors.
487 A special case of syntax errors is token errors, when a token is only
488 accepted because the parser doesn't know quite enough to reject it.
489 There are handled better as they are quite local, so a single test
490 program can trigger most of the possible errors.
492 To handle erronous code, we need a different set of tests, as we need to
493 capture `stderr`. The same test code will be used for type errors too.
494 As error messages contain the line number, and we don't want changes to
495 this file to change the reported numbers, we copy the code into a
496 separate file first, then run from there.
499 @for t in $(oceani_failing_tests); do \
500 echo -n "Test $$t ... "; \
501 ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
502 echo '``````' > .tmp.code; \
503 ./md2c oceani-tests.mdc "test: $$t" | grep -v '^#' >> .tmp.code; \
504 ./oceani .tmp.code > .tmp.have 2>&1; \
505 if ! cmp -s .tmp.want .tmp.have; then \
506 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
508 ./coverage_oceani --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1 ;\
512 oceani_failing_tests := syn1
513 oceani_failing_tests += tokerr
518 if then else while do
521 .tmp.code:3:11: error: unhandled parse error: then
522 oceani: no program found.
526 a := 1i // imaginary numbers aren't understood
527 b:[2i]number // array sizes are handled separately
528 c:[3.14159]Boolean // array sizes must be integers
529 d:[1_000_000_000_000]number // they mustn't be huge
530 patn: string = "foo[ ,_]*bar"re // regexp strings are just a dream
533 This is a multiline string
534 With an unsupportable suffix
538 yy:[unknowable]number
540 zz:[zzsize]string // size must be constant, use ::=
542 // These numbers should be bad in all contexts: FIXME
545 ###### output: tokerr
546 .tmp.code:3:13: error: unsupported number suffix: 1i
547 .tmp.code:4:11: error: unsupported number suffix: 2i
548 .tmp.code:5:11: error: array size must be an integer: 3.14159
549 .tmp.code:6:11: error: array size is too large: 1_000_000_000_000
550 .tmp.code:7:23: error: unsupported string suffix: "foo[ ,_]*bar"re
551 .tmp.code:9:17: error: unsupported string suffix: """
552 This is a multiline string
553 With an unsupportable suffix
555 .tmp.code:14:11: error: undefined type: unknown
556 .tmp.code:15:12: error: name undeclared: unknowable
557 .tmp.code:17:12: error: array size must be a constant: zzsize
558 .tmp.code:20:12: error: unrecognised number: 00123
560 ## Tests for type errors
562 Type error don't cause parsing to abort, so we can fit many in the
563 one test program. Some type errors are found during the parse, others
564 during type analysis which doesn't run if parsing failed. So we cannot
565 fit everything in one.
567 These programs were generated by looking for the
568 various places that `type_err()` are called.
571 oceani_failing_tests += type_err1 type_err2 type_err3 type_err4
573 ###### test: type_err1
576 print "hello" ++ 5, 5 ++ "hello"
581 if 3 * 4 and not True: print "Weird"
583 ###### output: type_err1
584 .tmp.code:3:25: error: expected string found number
585 .tmp.code:3:28: error: expected string found number
586 .tmp.code:6:8: error: Cannot assign to a constant: b
587 .tmp.code:5:8: info: name was defined as a constant here
588 .tmp.code:6:8: error: Cannot assign to a constant: b
589 .tmp.code:5:8: info: name was defined as a constant here
590 .tmp.code:8:11: error: Arithmetic returns number but Boolean expected
591 oceani: type error in program - not running.
593 ###### test: type_err2
603 ###### output: type_err2
604 .tmp.code:4:8: error: variable 'a' redeclared
605 .tmp.code:3:8: info: this is where 'a' was first declared
606 .tmp.code:5:8: error: variable 'a' redeclared
607 .tmp.code:3:8: info: this is where 'a' was first declared
608 .tmp.code:6:8: error: variable 'a' redeclared
609 .tmp.code:3:8: info: this is where 'a' was first declared
610 .tmp.code:7:8: error: variable 'a' redeclared
611 .tmp.code:3:8: info: this is where 'a' was first declared
612 .tmp.code:8:8: Variable declared with no type or value: c
614 ###### test: type_err3
619 c = "hello" ++ (True and False)
621 print 45 + ( "Hello" ++ "there")
631 case "Hello": print "Hello"
633 a1:[5]number; a2:[5]number; a3:[10]number; a4:[5]string
641 ###### output: type_err3
642 .tmp.code:4:12: error: expected number but variable 'c' is string
643 .tmp.code:3:8: info: this is where 'c' was set to string
644 .tmp.code:4:12: error: Arithmetic returns number but string expected
645 .tmp.code:3:8: info: variable 'c' was set as string here.
646 .tmp.code:5:24: error: Boolean operation found where string expected
647 .tmp.code:6:12: error: Comparison returns Boolean but string expected
648 .tmp.code:3:8: info: variable 'c' was set as string here.
649 .tmp.code:7:21: error: Concat returns string but number expected
650 .tmp.code:8:8: error: string cannot be indexed
651 .tmp.code:8:8: error: string cannot be indexed
652 .tmp.code:17:13: error: expected number found string
653 .tmp.code:13:16: error: expected number, found string
654 .tmp.code:20:8: error: cannot assign value of type [5]number
655 .tmp.code:21:13: error: expected [5]number but variable 'a3' is [10]number
656 .tmp.code:19:36: info: this is where 'a3' was set to [10]number
657 .tmp.code:21:8: error: cannot assign value of type [5]number
658 .tmp.code:22:13: error: expected [5]number but variable 'a4' is [5]string
659 .tmp.code:19:51: info: this is where 'a4' was set to [5]string
660 .tmp.code:22:8: error: cannot assign value of type [5]number
661 .tmp.code:23:16: error: expected number found string
662 .tmp.code:24:16: error: expected string found Boolean
663 .tmp.code:25:12: error: have number but need string
664 .tmp.code:3:8: info: variable 'c' was set as string here.
665 oceani: type error in program - not running.
667 ###### test: type_err4
672 ###### output: type_err4
673 .tmp.code:3:14: error: expected *unknown*type* (labels not permitted) but variable 'b' is label
674 .tmp.code:3:14: info: this is where 'b' was set to label
675 .tmp.code:3:16: error: expected label found number
676 .tmp.code:3:14: info: variable 'b' was set as label here.
677 .tmp.code:4:17: error: expected *unknown*type* (labels not permitted) but variable 'b' is label
678 .tmp.code:3:14: info: this is where 'b' was set to label
679 oceani: type error in program - not running.
682 oceani_failing_tests += type_err_const type_err_const1
684 ###### test: type_err_const
687 bar ::= "string" + 56
694 ###### output: type_err_const
695 .tmp.code:4:16: error: expected number found string
696 .tmp.code:6:8: error: name already declared: bar
697 .tmp.code:4:8: info: this is where 'bar' was first declared
698 .tmp.code:8:8: error: variable 'foo' redeclared
699 .tmp.code:3:8: info: this is where 'foo' was first declared
701 ###### test: type_err_const1
709 ###### output: type_err_const1
710 .tmp.code:3:12: error: unhandled parse error: :
711 oceani: no program found.
714 ## Test erroneous command line args
716 To improve coverage, we want to test correct handling of strange command
717 line arguments. These tests won't use code, so the exiting test types
718 won't work. So we need to be able to explicitly give the command line,
719 and the expected output, and have that tested and the coverage assessed.
720 Rather than having to spell out the whole command name, just give "cmd",
721 and discard that. Requiring but discarding the command make an empty
722 command list possible.
725 @for t in $(oceani_special_tests); do \
726 echo -n "Test $$t ... ";\
727 i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
728 ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
729 ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
730 if ! cmp -s .tmp.want .tmp.have; then \
731 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
733 ./coverage_oceani $${1+"$$@"} > /dev/null 2>&1 ;\
738 oceani_special_tests += "cmd"
739 oceani_special_tests += "cmd,-zyx"
740 oceani_special_tests += "cmd,nofile"
741 oceani_special_tests += "cmd,/dev/null"
742 oceani_special_tests += "cmd,--section,toast:nothing,oceani-tests.mdc"
745 oceani: no input file given
747 ###### output: cmd,-zyx
748 ./oceani: invalid option -- 'z'
749 Usage: oceani --trace --print --noexec --brackets--section=SectionName prog.ocn
751 ###### output: cmd,nofile
752 oceani: cannot open nofile
754 ###### output: cmd,/dev/null
755 oceani: could not find any code in /dev/null
757 ###### output: cmd,--section,toast:nothing,oceani-tests.mdc
758 oceani: cannot find section toast:nothing