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.94) 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"
104 a := 23; b:=12 ; b1 := -b
105 print a, b, a+b, a-b, a*b, a/b, a%b
106 print a<b, a<=b, a>b, a>=b, a<a, a==b, a==a
107 print +a, +b, +b1, -a, -b, -b1
108 x := True; y := False
109 print x and y, x or y, x and x, y or y, x and not x, x < y
111 c ::= "This is a string"
112 d ::= " field theory"
115 aconst :: string = "unchanging"
124 ###### output: valvar
126 23 12 35 11 276 1.91667 11
127 False False True True False False True
129 False True True False False False
130 This is a string field theory This is a string field theory
133 Next we change the value of variables
136 oceani_tests += "setvar"
143 a = (a + a) * (a + a)
147 ###### output: setvar
153 oceani_tests += "consts"
158 four ::= 2 + 2 ; five ::= 10/2
159 const pie ::= "I like Pie";
160 cake ::= "The cake is"
164 print "Hello World, what lovely oceans you have!"
165 print "are there", five, "?"
166 print pi, pie, "but", cake
168 ###### output: consts
169 Hello World, what lovely oceans you have!
171 3.14159 I like Pie but The cake is a lie
173 Test merging of variables from multiple cases
176 oceani_tests += varmerge
178 ###### test: varmerge
181 for i:=0; then i=i+1; while i < 5:
191 ###### output: varmerge
192 zero , one , two , three , many ,
194 ## Conditions and Loops
196 Now we need to test if/else and some different loops
199 oceani_tests += cond_loop
201 ###### test: cond_loop
209 for b:=1; then b=b+b; while b < 100:
212 // Newtons method for square root of 2
218 current := guess * guess
219 use +(current - target) > 0.000000001
221 guess = (guess + (target / guess) ) / 2
224 print "error is ", target - guess * guess
226 for j:=0; then j = j+3 ; while j < 10:
227 if j != 0 and then 20 / j > 3:
228 print "20 /", j," =", 20 / j
230 print "I won't calculate 20 /", j
231 pi ::= 3.1415926535897
232 if 355/113 == pi or else +(pi - 355/113) < 0.001:
234 print "lower" if 355/113 < pi else "higher"
236 ###### output: cond_loop
243 error is -4.51095e-12
244 I won't calculate 20 / 0
247 I won't calculate 20 / 9
253 The demonstration code presented in the interpreted is suitable for the test suite.
254 Here I break it into two parts, keeping the array code separate.
257 oceani_tests += "sayhello,55,33,hello,True"
258 oceani_tests += "sayhello,12,60,there,False"
260 ###### test: sayhello
262 program A B astr bbool:
263 print "Hello World, what lovely oceans you have!"
264 /* When a variable is defined in both branches of an 'if',
265 * and used afterwards, the variables are merged.
271 print "Is", A, "bigger than", B,"? ", bigger
272 /* If a variable is not used after the 'if', no
273 * merge happens, so types can be different
276 double:string = "yes"
277 print A, "is more than twice", B, "?", double
280 print "double", B, "is", double
291 print "GCD of", A, "and", B,"is", a
293 print a, "is not positive, cannot calculate GCD"
295 print b, "is not positive, cannot calculate GCD"
300 print "Fibonacci:", f1,f2,
310 print astr ++ " was the str"
312 print "I found the str over " ++ astr
314 /* Binary search... */
331 print "Yay, I found", target
333 print "Closest I found was", mid
335 ###### output: sayhello,55,33,hello,True
336 Hello World, what lovely oceans you have!
337 Is 55 bigger than 33 ? yes
339 GCD of 55 and 33 is 11
340 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
342 Closest I found was 77.3438
344 ###### output: sayhello,12,60,there,False
345 Hello World, what lovely oceans you have!
346 Is 12 bigger than 60 ? no
348 GCD of 12 and 60 is 12
349 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
350 I found the str over there
351 Closest I found was 77.3438
354 oceani_tests += "insert_sort"
355 ###### test: insert_sort
360 for i:=1; then i = i + 1; while i < size:
361 n := list[i-1] * list[i-1]
362 list[i] = (n / 100) % 10000
365 for i:=0; then i = i + 1; while i < size:
366 print "list[",i,"]=",list[i]
368 for i := 1; then i=i+1; while i < size:
369 for j:=i-1; then j=j-1; while j >= 0:
370 if list[j] > list[j+1]:
375 for i:=0; then i = i + 1; while i < size:
376 print "list[",i,"]=",list[i]
378 ###### output: insert_sort
494 We already have some array tests, but this is where we put other
495 ad-hoc things array related.
498 oceani_tests += arrays
506 bools[3] = strings[1] == "Hello"
507 bools[1] = strings[2] <= "there"
509 for i:=0; then i=i+1; while i<5:
513 ###### output: arrays
514 False True False False False
518 Time to test if structure declarations and accesses work correctly.
521 oceani_tests += structs
530 struct baz { a:number; b:Boolean; }
535 for i:=0; then i=i+1; while i < 4:
543 info[i].size[0] = i*i
544 info[i].active = nm == "jane"
546 for i:=0; then i=i+1; while i < 4:
547 print info[i].name, info[i].active, info[i].size[0]
549 ###### output: structs
556 ## Test code with syntax errors
558 Syntax errors aren't handled well yet - the result is almost always a
559 single message about the first error. So this section will be fairly
560 thin until we add proper parsing recovery in the face of common errors.
562 A special case of syntax errors is token errors, when a token is only
563 accepted because the parser doesn't know quite enough to reject it.
564 There are handled better as they are quite local, so a single test
565 program can trigger most of the possible errors.
567 To handle erronous code, we need a different set of tests, as we need to
568 capture `stderr`. The same test code will be used for type errors too.
569 As error messages contain the line number, and we don't want changes to
570 this file to change the reported numbers, we copy the code into a
571 separate file first, then run from there.
574 @for t in $(oceani_failing_tests); do \
575 echo -n "Test $$t ... "; \
576 ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
577 echo '``````' > .tmp.code; \
578 ./md2c oceani-tests.mdc "test: $$t" | grep -v '^#' >> .tmp.code; \
579 ./oceani .tmp.code > .tmp.have 2>&1; \
580 if ! cmp -s .tmp.want .tmp.have; then \
581 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
583 ./coverage_oceani --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1 ;\
587 oceani_failing_tests := syn1
588 oceani_failing_tests += tokerr
593 if then else while do
596 .tmp.code:3:11: error: unhandled parse error: then
597 oceani: no program found.
601 a := 1i // imaginary numbers aren't understood
602 b:[2i]number // array sizes are handled separately
603 c:[3.14159]Boolean // array sizes must be integers
604 d:[1_000_000_000_000]number // they mustn't be huge
605 patn: string = "foo[ ,_]*bar"re // regexp strings are just a dream
608 This is a multiline string
609 With an unsupportable suffix
613 yy:[unknowable]number
615 zz:[zzsize]string // size must be constant, use ::=
617 // These numbers should be bad in all contexts: FIXME
620 ###### output: tokerr
621 .tmp.code:3:13: error: unsupported number suffix: 1i
622 .tmp.code:4:11: error: unsupported number suffix: 2i
623 .tmp.code:5:11: error: array size must be an integer: 3.14159
624 .tmp.code:6:11: error: array size is too large: 1_000_000_000_000
625 .tmp.code:7:23: error: unsupported string suffix: "foo[ ,_]*bar"re
626 .tmp.code:9:17: error: unsupported string suffix: """
627 This is a multiline string
628 With an unsupportable suffix
630 .tmp.code:14:11: error: undefined type: unknown
631 .tmp.code:15:12: error: name undeclared: unknowable
632 .tmp.code:17:12: error: array size must be a constant: zzsize
633 .tmp.code:20:12: error: unrecognised number: 00123
635 ## Tests for type errors
637 Type error don't cause parsing to abort, so we can fit many in the
638 one test program. Some type errors are found during the parse, others
639 during type analysis which doesn't run if parsing failed. So we cannot
640 fit everything in one.
642 These programs were generated by looking for the
643 various places that `type_err()` are called.
646 oceani_failing_tests += type_err1 type_err2 type_err3 type_err4
648 ###### test: type_err1
651 print "hello" ++ 5, 5 ++ "hello"
656 if 3 * 4 and not True: print "Weird"
658 ###### output: type_err1
659 .tmp.code:3:25: error: expected string found number
660 .tmp.code:3:28: error: expected string found number
661 .tmp.code:6:8: error: Cannot assign to a constant: b
662 .tmp.code:5:8: info: name was defined as a constant here
663 .tmp.code:6:8: error: Cannot assign to a constant: b
664 .tmp.code:5:8: info: name was defined as a constant here
665 .tmp.code:8:11: error: Arithmetic returns number but Boolean expected
666 oceani: type error in program - not running.
668 ###### test: type_err2
678 ###### output: type_err2
679 .tmp.code:4:8: error: variable 'a' redeclared
680 .tmp.code:3:8: info: this is where 'a' was first declared
681 .tmp.code:5:8: error: variable 'a' redeclared
682 .tmp.code:3:8: info: this is where 'a' was first declared
683 .tmp.code:6:8: error: variable 'a' redeclared
684 .tmp.code:3:8: info: this is where 'a' was first declared
685 .tmp.code:7:8: error: variable 'a' redeclared
686 .tmp.code:3:8: info: this is where 'a' was first declared
687 .tmp.code:8:8: Variable declared with no type or value: c
689 ###### test: type_err3
698 c = "hello" ++ (True and False)
700 print 45 + ( "Hello" ++ "there")
710 case "Hello": print "Hello"
712 a1:[5]number; a2:[5]number; a3:[10]number; a4:[5]string
724 ###### output: type_err3
725 .tmp.code:8:12: error: expected number but variable 'c' is string
726 .tmp.code:7:8: info: this is where 'c' was set to string
727 .tmp.code:8:12: error: Arithmetic returns number but string expected
728 .tmp.code:7:8: info: variable 'c' was set as string here.
729 .tmp.code:9:24: error: Boolean operation found where string expected
730 .tmp.code:10:12: error: Comparison returns Boolean but string expected
731 .tmp.code:7:8: info: variable 'c' was set as string here.
732 .tmp.code:11:21: error: Concat returns string but number expected
733 .tmp.code:12:8: error: string cannot be indexed
734 .tmp.code:12:8: error: string cannot be indexed
735 .tmp.code:21:13: error: expected number found string
736 .tmp.code:17:16: error: expected number, found string
737 .tmp.code:24:8: error: cannot assign value of type [5]number
738 .tmp.code:25:13: error: expected [5]number but variable 'a3' is [10]number
739 .tmp.code:23:36: info: this is where 'a3' was set to [10]number
740 .tmp.code:25:8: error: cannot assign value of type [5]number
741 .tmp.code:26:13: error: expected [5]number but variable 'a4' is [5]string
742 .tmp.code:23:51: info: this is where 'a4' was set to [5]string
743 .tmp.code:26:8: error: cannot assign value of type [5]number
744 .tmp.code:27:16: error: expected number found string
745 .tmp.code:28:16: error: expected string found Boolean
746 .tmp.code:29:12: error: have number but need string
747 .tmp.code:7:8: info: variable 'c' was set as string here.
748 .tmp.code:32:8: error: field reference attempted on label, not a struct
749 .tmp.code:32:16: error: expected none found number
750 .tmp.code:33:14: error: field reference attempted on string, not a struct
751 oceani: type error in program - not running.
753 ###### test: type_err4
758 ###### output: type_err4
759 .tmp.code:3:14: error: expected *unknown*type* (labels not permitted) but variable 'b' is label
760 .tmp.code:3:14: info: this is where 'b' was set to label
761 .tmp.code:3:16: error: expected label found number
762 .tmp.code:3:14: info: variable 'b' was set as label here.
763 .tmp.code:4:17: error: expected *unknown*type* (labels not permitted) but variable 'b' is label
764 .tmp.code:3:14: info: this is where 'b' was set to label
765 oceani: type error in program - not running.
768 oceani_failing_tests += type_err_const type_err_const1
770 ###### test: type_err_const
773 bar ::= "string" + 56
780 ###### output: type_err_const
781 .tmp.code:4:16: error: expected number found string
782 .tmp.code:6:8: error: name already declared: bar
783 .tmp.code:4:8: info: this is where 'bar' was first declared
784 .tmp.code:8:8: error: variable 'foo' redeclared
785 .tmp.code:3:8: info: this is where 'foo' was first declared
787 ###### test: type_err_const1
795 ###### output: type_err_const1
796 .tmp.code:3:12: error: unhandled parse error: :
797 oceani: no program found.
799 ## Test erroneous command line args
801 To improve coverage, we want to test correct handling of strange command
802 line arguments. These tests won't use code, so the exiting test types
803 won't work. So we need to be able to explicitly give the command line,
804 and the expected output, and have that tested and the coverage assessed.
805 Rather than having to spell out the whole command name, just give "cmd",
806 and discard that. Requiring but discarding the command make an empty
807 command list possible.
810 @for t in $(oceani_special_tests); do \
811 echo -n "Test $$t ... ";\
812 i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
813 ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
814 ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
815 if ! cmp -s .tmp.want .tmp.have; then \
816 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
818 ./coverage_oceani $${1+"$$@"} > /dev/null 2>&1 ;\
822 oceani_special_tests += "cmd"
823 oceani_special_tests += "cmd,-zyx"
824 oceani_special_tests += "cmd,nofile"
825 oceani_special_tests += "cmd,/dev/null"
826 oceani_special_tests += "cmd,--section,toast:nothing,oceani-tests.mdc"
829 oceani: no input file given
831 ###### output: cmd,-zyx
832 ./oceani: invalid option -- 'z'
833 Usage: oceani --trace --print --noexec --brackets--section=SectionName prog.ocn
835 ###### output: cmd,nofile
836 oceani: cannot open nofile
838 ###### output: cmd,/dev/null
839 oceani: could not find any code in /dev/null
841 ###### output: cmd,--section,toast:nothing,oceani-tests.mdc
842 oceani: cannot find section toast:nothing