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 @echo -n Checking grammar ...
42 @./parsergen --report --LALR --tag Parser oceani.mdc | grep " - no conflicts" > /dev/null || \
43 { echo "Grammar contains conflicts, please review" ; exit 1; }
45 @rm -rf coverage; mkdir -p coverage
47 @for T in $(oceani_tests); do \
48 echo -n "Test $$T.. "; \
49 i="$$IFS"; IFS=,; set $$T; IFS="$$i"; t=$$1; shift; \
50 ./md2c oceani-tests.mdc "output: $$T" | grep -v '^#' > .tmp.want; \
51 ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > .tmp.have; \
52 if ! cmp -s .tmp.want .tmp.have; then \
53 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
54 echo -n "printing.. "; \
55 echo '``````' > .tmp.code1; echo '``````' > .tmp.code2 ;\
56 ./oceani --noexec --print --section "test: $$t" oceani-tests.mdc >> .tmp.code1; \
57 ./oceani --noexec --print .tmp.code1 >> .tmp.code2 ;\
58 if ! cmp -s .tmp.code1 .tmp.code2; then \
59 echo " Failed"; diff -u .tmp.code1 .tmp.code2; exit 1 ; fi ; \
60 echo -n "extra-newlines.. "; \
62 ' .tmp.code1 > .tmp.code1a; \
63 echo '``````' > .tmp.code2a ;\
64 ./oceani --noexec --print .tmp.code1a >> .tmp.code2a;\
65 if ! cmp -s .tmp.code1 .tmp.code2a; then \
66 echo " Failed"; diff -u .tmp.code1 .tmp.code2a; exit 1; fi ; \
67 echo -n "exec-after-print.. "; \
68 ./oceani .tmp.code1 $${1+"$$@"} > .tmp.have ; \
69 if ! cmp -s .tmp.want .tmp.have; then \
70 echo " FAILED"; diff -u .tmp.want .tmp.have; exit 1;fi; \
72 ./coverage_oceani --print --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > /dev/null ; \
73 ./coverage_oceani -tpbn --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1; \
78 @gcov -o coverage oceani.mdc > /dev/null 2> /dev/null
79 @mv *.gcov coverage ; [ -f .gcov ] && mv .gcov coverage
80 @ awk '/NOTEST/ { next } /^ *[1-9]/ {ran+=1} /^ *###/ {skip+=1} \
81 END {printf "coverage: %6.2f%%\n", ran * 100 / (ran + skip); \
82 if (ran < (ran + skip) *0.94) exit(1) }' \
83 coverage/oceani.mdc.gcov
86 @for T in $(oceani_tests); do \
87 echo -n "Valgrind $$T.. "; \
88 i="$$IFS"; IFS=,; set $$T; IFS="$$i"; t=$$1; shift; \
89 if ! valgrind --error-exitcode=1 --log-file=.tmp.valg ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} \
90 > /dev/null 2>&1 ; then \
91 echo "FAILED"; cat .tmp.valg; exit 1; fi ; \
92 if grep 'LEAK SUMMARY' .tmp.valg > /dev/null; then \
93 echo "valgrind found LEAKS"; cat .tmp.valg ; exit 1 ; fi; \
94 if grep 'in use at exit [1-9]' .tmp.valg > /dev/null; then \
95 echo "valgrind found memory in use at exit"; cat .tmp.valg ; exit 1 ; fi; \
99 coverage_oceani: oceani.c
100 $(CC) $(CFLAGS) --coverage -fprofile-dir=coverage -o coverage_oceani oceani.c $(LDLIBS)
102 ## Values and variables
104 The first test stores values in variables and performs various
105 calculations on them.
108 oceani_tests += "valvar"
113 a := 23; b:=12 ; b1 := -b
114 print a, b, a+b, a-b, a*b, a/b, a%b
115 print a<b, a<=b, a>b, a>=b, a<a, a==b, a==a
116 print +a, +b, +b1, -a, -b, -b1
117 x := True; y := False
118 print x and y, x or y, x and x, y or y, x and not x, x < y
120 c ::= "This is a string"
121 d ::= " field theory"
124 aconst :: string = "unchanging"
133 ###### output: valvar
135 23 12 35 11 276 1.91667 11
136 False False True True False False True
138 False True True False False False
139 This is a string field theory This is a string field theory
142 Next we change the value of variables
145 oceani_tests += "setvar"
152 a = (a + a) * (a + a)
156 ###### output: setvar
162 oceani_tests += "consts"
167 four ::= 2 + 2 ; five ::= 10/2
168 const pie ::= "I like Pie";
169 cake ::= "The cake is"
173 print "Hello World, what lovely oceans you have!"
174 print "are there", five, "?"
175 print pi, pie, "but", cake
177 ###### output: consts
178 Hello World, what lovely oceans you have!
180 3.14159 I like Pie but The cake is a lie
182 Test merging of variables from multiple cases
185 oceani_tests += varmerge
187 ###### test: varmerge
190 for i:=0; then i=i+1; while i < 5:
200 ###### output: varmerge
201 zero , one , two , three , many ,
203 ## Conditions and Loops
205 Now we need to test if/else and some different loops
208 oceani_tests += cond_loop
210 ###### test: cond_loop
218 for b:=1; then b=b+b; while b < 100:
221 // Newtons method for square root of 2
227 current := guess * guess
228 use +(current - target) > 0.000000001
230 guess = (guess + (target / guess) ) / 2
233 print "error is ", target - guess * guess
235 for j:=0; then j = j+3 ; while j < 10:
236 if j != 0 and then 20 / j > 3:
237 print "20 /", j," =", 20 / j
239 print "I won't calculate 20 /", j
240 pi ::= 3.1415926535897
241 if 355/113 == pi or else +(pi - 355/113) < 0.001:
243 print "lower" if 355/113 < pi else "higher"
245 ###### output: cond_loop
252 error is -4.51095e-12
253 I won't calculate 20 / 0
256 I won't calculate 20 / 9
262 The demonstration code presented in the interpreted is suitable for the test suite.
263 Here I break it into two parts, keeping the array code separate.
266 oceani_tests += "sayhello,55,33,hello,True"
267 oceani_tests += "sayhello,12,60,there,False"
269 ###### test: sayhello
271 program A B astr bbool:
272 print "Hello World, what lovely oceans you have!"
273 /* When a variable is defined in both branches of an 'if',
274 * and used afterwards, the variables are merged.
280 print "Is", A, "bigger than", B,"? ", bigger
281 /* If a variable is not used after the 'if', no
282 * merge happens, so types can be different
285 double:string = "yes"
286 print A, "is more than twice", B, "?", double
289 print "double", B, "is", double
300 print "GCD of", A, "and", B,"is", a
302 print a, "is not positive, cannot calculate GCD"
304 print b, "is not positive, cannot calculate GCD"
309 print "Fibonacci:", f1,f2,
319 print astr ++ " was the str"
321 print "I found the str over " ++ astr
323 /* Binary search... */
340 print "Yay, I found", target
342 print "Closest I found was", mid
344 ###### output: sayhello,55,33,hello,True
345 Hello World, what lovely oceans you have!
346 Is 55 bigger than 33 ? yes
348 GCD of 55 and 33 is 11
349 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
351 Closest I found was 77.3438
353 ###### output: sayhello,12,60,there,False
354 Hello World, what lovely oceans you have!
355 Is 12 bigger than 60 ? no
357 GCD of 12 and 60 is 12
358 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
359 I found the str over there
360 Closest I found was 77.3438
363 oceani_tests += "insert_sort"
364 ###### test: insert_sort
369 for i:=1; then i = i + 1; while i < size:
370 n := list[i-1] * list[i-1]
371 list[i] = (n / 100) % 10000
374 for i:=0; then i = i + 1; while i < size:
375 print "list[",i,"]=",list[i]
377 for i := 1; then i=i+1; while i < size:
378 for j:=i-1; then j=j-1; while j >= 0:
379 if list[j] > list[j+1]:
384 for i:=0; then i = i + 1; while i < size:
385 print "list[",i,"]=",list[i]
387 ###### output: insert_sort
503 We already have some array tests, but this is where we put other
504 ad-hoc things array related.
507 oceani_tests += arrays
515 bools[3] = strings[1] == "Hello"
516 bools[1] = strings[2] <= "there"
518 for i:=0; then i=i+1; while i<5:
522 ###### output: arrays
523 False True False False False
527 Time to test if structure declarations and accesses work correctly.
530 oceani_tests += structs
539 struct baz { a:number; b:Boolean; }
544 for i:=0; then i=i+1; while i < 4:
552 info[i].size[0] = i*i
553 info[i].active = nm == "jane"
555 for i:=0; then i=i+1; while i < 4:
556 print info[i].name, info[i].active, info[i].size[0]
558 ###### output: structs
565 ## Test code with syntax errors
567 Syntax errors aren't handled well yet - the result is almost always a
568 single message about the first error. So this section will be fairly
569 thin until we add proper parsing recovery in the face of common errors.
571 A special case of syntax errors is token errors, when a token is only
572 accepted because the parser doesn't know quite enough to reject it.
573 There are handled better as they are quite local, so a single test
574 program can trigger most of the possible errors.
576 To handle erronous code, we need a different set of tests, as we need to
577 capture `stderr`. The same test code will be used for type errors too.
578 As error messages contain the line number, and we don't want changes to
579 this file to change the reported numbers, we copy the code into a
580 separate file first, then run from there.
583 @for t in $(oceani_failing_tests); do \
584 echo -n "Test $$t ... "; \
585 ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
586 echo '``````' > .tmp.code; \
587 ./md2c oceani-tests.mdc "test: $$t" | grep -v '^#' >> .tmp.code; \
588 ./oceani .tmp.code > .tmp.have 2>&1; \
589 if ! cmp -s .tmp.want .tmp.have; then \
590 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
592 ./coverage_oceani --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1 ;\
596 oceani_failing_tests := syn1
597 oceani_failing_tests += tokerr
602 if then else while do
605 .tmp.code:3:11: Syntax error in statement: then
609 a := 1i // imaginary numbers aren't understood
610 b:[2i]number // array sizes are handled separately
611 c:[3.14159]Boolean // array sizes must be integers
612 d:[1_000_000_000_000]number // they mustn't be huge
613 patn: string = "foo[ ,_]*bar"re // regexp strings are just a dream
616 This is a multiline string
617 With an unsupportable suffix
621 yy:[unknowable]number
623 zz:[zzsize]string // size must be constant, use ::=
625 // These numbers should be bad in all contexts: FIXME
628 ###### output: tokerr
629 .tmp.code:3:13: error: unsupported number suffix: 1i
630 .tmp.code:4:11: error: unsupported number suffix: 2i
631 .tmp.code:5:11: error: array size must be an integer: 3.14159
632 .tmp.code:6:11: error: array size is too large: 1_000_000_000_000
633 .tmp.code:7:23: error: unsupported string suffix: "foo[ ,_]*bar"re
634 .tmp.code:9:17: error: unsupported string suffix: """
635 This is a multiline string
636 With an unsupportable suffix
638 .tmp.code:14:11: error: undefined type: unknown
639 .tmp.code:15:12: error: name undeclared: unknowable
640 .tmp.code:17:12: error: array size must be a constant: zzsize
641 .tmp.code:20:12: error: unrecognised number: 00123
643 ## Tests for type errors
645 Type error don't cause parsing to abort, so we can fit many in the
646 one test program. Some type errors are found during the parse, others
647 during type analysis which doesn't run if parsing failed. So we cannot
648 fit everything in one.
650 These programs were generated by looking for the
651 various places that `type_err()` are called.
654 oceani_failing_tests += type_err1 type_err2 type_err3 type_err4
656 ###### test: type_err1
659 print "hello" ++ 5, 5 ++ "hello"
664 if 3 * 4 and not True: print "Weird"
666 ###### output: type_err1
667 .tmp.code:3:25: error: expected string found number
668 .tmp.code:3:28: error: expected string found number
669 .tmp.code:6:8: error: Cannot assign to a constant: b
670 .tmp.code:5:8: info: name was defined as a constant here
671 .tmp.code:6:8: error: Cannot assign to a constant: b
672 .tmp.code:5:8: info: name was defined as a constant here
673 .tmp.code:8:11: error: Arithmetic returns number but Boolean expected
674 oceani: type error in program - not running.
676 ###### test: type_err2
686 ###### output: type_err2
687 .tmp.code:4:8: error: variable 'a' redeclared
688 .tmp.code:3:8: info: this is where 'a' was first declared
689 .tmp.code:5:8: error: variable 'a' redeclared
690 .tmp.code:3:8: info: this is where 'a' was first declared
691 .tmp.code:6:8: error: variable 'a' redeclared
692 .tmp.code:3:8: info: this is where 'a' was first declared
693 .tmp.code:7:8: error: variable 'a' redeclared
694 .tmp.code:3:8: info: this is where 'a' was first declared
695 .tmp.code:8:8: Variable declared with no type or value: c
697 ###### test: type_err3
706 c = "hello" ++ (True and False)
708 print 45 + ( "Hello" ++ "there")
718 case "Hello": print "Hello"
720 a1:[5]number; a2:[5]number; a3:[10]number; a4:[5]string
732 ###### output: type_err3
733 .tmp.code:8:12: error: expected number but variable 'c' is string
734 .tmp.code:7:8: info: this is where 'c' was set to string
735 .tmp.code:8:12: error: Arithmetic returns number but string expected
736 .tmp.code:7:8: info: variable 'c' was set as string here.
737 .tmp.code:9:24: error: Boolean operation found where string expected
738 .tmp.code:10:12: error: Comparison returns Boolean but string expected
739 .tmp.code:7:8: info: variable 'c' was set as string here.
740 .tmp.code:11:21: error: Concat returns string but number expected
741 .tmp.code:12:8: error: string cannot be indexed
742 .tmp.code:12:8: error: string cannot be indexed
743 .tmp.code:21:13: error: expected number found string
744 .tmp.code:17:16: error: expected number, found string
745 .tmp.code:24:8: error: cannot assign value of type [5]number
746 .tmp.code:25:13: error: expected [5]number but variable 'a3' is [10]number
747 .tmp.code:23:36: info: this is where 'a3' was set to [10]number
748 .tmp.code:25:8: error: cannot assign value of type [5]number
749 .tmp.code:26:13: error: expected [5]number but variable 'a4' is [5]string
750 .tmp.code:23:51: info: this is where 'a4' was set to [5]string
751 .tmp.code:26:8: error: cannot assign value of type [5]number
752 .tmp.code:27:16: error: expected number found string
753 .tmp.code:28:16: error: expected string found Boolean
754 .tmp.code:29:12: error: have number but need string
755 .tmp.code:7:8: info: variable 'c' was set as string here.
756 .tmp.code:32:8: error: variable used but not declared: foo
757 .tmp.code:32:8: error: field reference attempted on none, not a struct
758 .tmp.code:32:16: error: expected none found number
759 .tmp.code:33:14: error: field reference attempted on string, not a struct
760 oceani: type error in program - not running.
762 ###### test: type_err4
767 ###### output: type_err4
768 .tmp.code:3:14: error: variable used but not declared: b
769 .tmp.code:3:16: error: expected none found number
770 .tmp.code:3:14: info: variable 'b' was set as none here.
771 oceani: type error in program - not running.
774 oceani_failing_tests += type_err_const type_err_const1
776 ###### test: type_err_const
779 bar ::= "string" + 56
786 ###### output: type_err_const
787 .tmp.code:4:16: error: expected number found string
788 .tmp.code:6:8: error: name already declared: bar
789 .tmp.code:4:8: info: this is where 'bar' was first declared
790 .tmp.code:8:8: error: variable 'foo' redeclared
791 .tmp.code:3:8: info: this is where 'foo' was first declared
793 ###### test: type_err_const1
801 ###### output: type_err_const1
802 .tmp.code:3:12: Syntax error in constant: :
803 .tmp.code:4:12: Syntax error in constant: :
805 ## Test erroneous command line args
807 To improve coverage, we want to test correct handling of strange command
808 line arguments. These tests won't use code, so the exiting test types
809 won't work. So we need to be able to explicitly give the command line,
810 and the expected output, and have that tested and the coverage assessed.
811 Rather than having to spell out the whole command name, just give "cmd",
812 and discard that. Requiring but discarding the command make an empty
813 command list possible.
816 @for t in $(oceani_special_tests); do \
817 echo -n "Test $$t ... ";\
818 i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
819 ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
820 ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
821 if ! cmp -s .tmp.want .tmp.have; then \
822 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
824 ./coverage_oceani $${1+"$$@"} > /dev/null 2>&1 ;\
828 oceani_special_tests += "cmd"
829 oceani_special_tests += "cmd,-zyx"
830 oceani_special_tests += "cmd,nofile"
831 oceani_special_tests += "cmd,/dev/null"
832 oceani_special_tests += "cmd,--section,toast:nothing,oceani-tests.mdc"
835 oceani: no input file given
837 ###### output: cmd,-zyx
838 ./oceani: invalid option -- 'z'
839 Usage: oceani --trace --print --noexec --brackets--section=SectionName prog.ocn
841 ###### output: cmd,nofile
842 oceani: cannot open nofile
844 ###### output: cmd,/dev/null
845 oceani: could not find any code in /dev/null
847 ###### output: cmd,--section,toast:nothing,oceani-tests.mdc
848 oceani: cannot find section toast:nothing