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 if pi > 3 then print "pi exceeds three"; else print "need more pie"
246 if (pi < 3) { print "not enough pi" } else { print "pi sufficient" }
247 for { i := 0; sum := 0 }
253 print "sum 1..10 is", sum
255 ###### output: cond_loop
262 error is -4.51095e-12
263 I won't calculate 20 / 0
266 I won't calculate 20 / 9
275 The demonstration code presented in the interpreted is suitable for the test suite.
276 Here I break it into two parts, keeping the array code separate.
279 oceani_tests += "sayhello,55,33,hello,True"
280 oceani_tests += "sayhello,12,60,there,False"
282 ###### test: sayhello
284 program A B astr bbool:
285 print "Hello World, what lovely oceans you have!"
286 /* When a variable is defined in both branches of an 'if',
287 * and used afterwards, the variables are merged.
293 print "Is", A, "bigger than", B,"? ", bigger
294 /* If a variable is not used after the 'if', no
295 * merge happens, so types can be different
298 double:string = "yes"
299 print A, "is more than twice", B, "?", double
302 print "double", B, "is", double
313 print "GCD of", A, "and", B,"is", a
315 print a, "is not positive, cannot calculate GCD"
317 print b, "is not positive, cannot calculate GCD"
322 print "Fibonacci:", f1,f2,
332 print astr ++ " was the str"
334 print "I found the str over " ++ astr
336 /* Binary search... */
353 print "Yay, I found", target
355 print "Closest I found was", mid
357 ###### output: sayhello,55,33,hello,True
358 Hello World, what lovely oceans you have!
359 Is 55 bigger than 33 ? yes
361 GCD of 55 and 33 is 11
362 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
364 Closest I found was 77.3438
366 ###### output: sayhello,12,60,there,False
367 Hello World, what lovely oceans you have!
368 Is 12 bigger than 60 ? no
370 GCD of 12 and 60 is 12
371 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
372 I found the str over there
373 Closest I found was 77.3438
376 oceani_tests += "insert_sort"
377 ###### test: insert_sort
382 for i:=1; then i = i + 1; while i < size:
383 n := list[i-1] * list[i-1]
384 list[i] = (n / 100) % 10000
387 for i:=0; then i = i + 1; while i < size:
388 print "list[",i,"]=",list[i]
390 for i := 1; then i=i+1; while i < size:
391 for j:=i-1; then j=j-1; while j >= 0:
392 if list[j] > list[j+1]:
397 for i:=0; then i = i + 1; while i < size:
398 print "list[",i,"]=",list[i]
400 ###### output: insert_sort
516 We already have some array tests, but this is where we put other
517 ad-hoc things array related.
520 oceani_tests += arrays
528 bools[3] = strings[1] == "Hello"
529 bools[1] = strings[2] <= "there"
531 for i:=0; then i=i+1; while i<5:
535 ###### output: arrays
536 False True False False False
540 Time to test if structure declarations and accesses work correctly.
543 oceani_tests += structs
552 struct baz { a:number; b:Boolean; }
557 for i:=0; then i=i+1; while i < 4:
565 info[i].size[0] = i*i
566 info[i].active = nm == "jane"
568 for i:=0; then i=i+1; while i < 4:
569 print info[i].name, info[i].active, info[i].size[0]
571 ###### output: structs
578 ## Test code with syntax errors
580 Syntax errors aren't handled well yet - the result is almost always a
581 single message about the first error. So this section will be fairly
582 thin until we add proper parsing recovery in the face of common errors.
584 A special case of syntax errors is token errors, when a token is only
585 accepted because the parser doesn't know quite enough to reject it.
586 There are handled better as they are quite local, so a single test
587 program can trigger most of the possible errors.
589 To handle erronous code, we need a different set of tests, as we need to
590 capture `stderr`. The same test code will be used for type errors too.
591 As error messages contain the line number, and we don't want changes to
592 this file to change the reported numbers, we copy the code into a
593 separate file first, then run from there.
596 @for t in $(oceani_failing_tests); do \
597 echo -n "Test $$t ... "; \
598 ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
599 echo '``````' > .tmp.code; \
600 ./md2c oceani-tests.mdc "test: $$t" | grep -v '^#' >> .tmp.code; \
601 ./oceani .tmp.code > .tmp.have 2>&1; \
602 if ! cmp -s .tmp.want .tmp.have; then \
603 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
605 ./coverage_oceani --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1 ;\
609 oceani_failing_tests := syn1
610 oceani_failing_tests += tokerr
615 if then else while do
618 .tmp.code:3:11: Syntax error in statement: then
622 a := 1i // imaginary numbers aren't understood
623 b:[2i]number // array sizes are handled separately
624 c:[3.14159]Boolean // array sizes must be integers
625 d:[1_000_000_000_000]number // they mustn't be huge
626 patn: string = "foo[ ,_]*bar"re // regexp strings are just a dream
629 This is a multiline string
630 With an unsupportable suffix
634 yy:[unknowable]number
636 zz:[zzsize]string // size must be constant, use ::=
638 // These numbers should be bad in all contexts: FIXME
641 ###### output: tokerr
642 .tmp.code:3:13: error: unsupported number suffix: 1i
643 .tmp.code:4:11: error: unsupported number suffix: 2i
644 .tmp.code:5:11: error: array size must be an integer: 3.14159
645 .tmp.code:6:11: error: array size is too large: 1_000_000_000_000
646 .tmp.code:7:23: error: unsupported string suffix: "foo[ ,_]*bar"re
647 .tmp.code:9:17: error: unsupported string suffix: """
648 This is a multiline string
649 With an unsupportable suffix
651 .tmp.code:14:11: error: undefined type: unknown
652 .tmp.code:15:12: error: name undeclared: unknowable
653 .tmp.code:17:12: error: array size must be a constant: zzsize
654 .tmp.code:20:12: error: unrecognised number: 00123
656 ## Tests for type errors
658 Type error don't cause parsing to abort, so we can fit many in the
659 one test program. Some type errors are found during the parse, others
660 during type analysis which doesn't run if parsing failed. So we cannot
661 fit everything in one.
663 These programs were generated by looking for the
664 various places that `type_err()` are called.
667 oceani_failing_tests += type_err1 type_err2 type_err3 type_err4
669 ###### test: type_err1
672 print "hello" ++ 5, 5 ++ "hello"
677 if 3 * 4 and not True: print "Weird"
679 ###### output: type_err1
680 .tmp.code:3:25: error: expected string found number
681 .tmp.code:3:28: error: expected string found number
682 .tmp.code:6:8: error: Cannot assign to a constant: b
683 .tmp.code:5:8: info: name was defined as a constant here
684 .tmp.code:6:8: error: Cannot assign to a constant: b
685 .tmp.code:5:8: info: name was defined as a constant here
686 .tmp.code:8:11: error: Arithmetic returns number but Boolean expected
687 oceani: type error in program - not running.
689 ###### test: type_err2
699 ###### output: type_err2
700 .tmp.code:4:8: error: variable 'a' redeclared
701 .tmp.code:3:8: info: this is where 'a' was first declared
702 .tmp.code:5:8: error: variable 'a' redeclared
703 .tmp.code:3:8: info: this is where 'a' was first declared
704 .tmp.code:6:8: error: variable 'a' redeclared
705 .tmp.code:3:8: info: this is where 'a' was first declared
706 .tmp.code:7:8: error: variable 'a' redeclared
707 .tmp.code:3:8: info: this is where 'a' was first declared
708 .tmp.code:8:8: Variable declared with no type or value: c
710 ###### test: type_err3
719 c = "hello" ++ (True and False)
721 print 45 + ( "Hello" ++ "there")
731 case "Hello": print "Hello"
733 a1:[5]number; a2:[5]number; a3:[10]number; a4:[5]string
745 ###### output: type_err3
746 .tmp.code:8:12: error: expected number but variable 'c' is string
747 .tmp.code:7:8: info: this is where 'c' was set to string
748 .tmp.code:8:12: error: Arithmetic returns number but string expected
749 .tmp.code:7:8: info: variable 'c' was set as string here.
750 .tmp.code:9:24: error: Boolean operation found where string expected
751 .tmp.code:10:12: error: Comparison returns Boolean but string expected
752 .tmp.code:7:8: info: variable 'c' was set as string here.
753 .tmp.code:11:21: error: Concat returns string but number expected
754 .tmp.code:12:8: error: string cannot be indexed
755 .tmp.code:12:8: error: string cannot be indexed
756 .tmp.code:21:13: error: expected number found string
757 .tmp.code:17:16: error: expected number, found string
758 .tmp.code:24:8: error: cannot assign value of type [5]number
759 .tmp.code:25:13: error: expected [5]number but variable 'a3' is [10]number
760 .tmp.code:23:36: info: this is where 'a3' was set to [10]number
761 .tmp.code:25:8: error: cannot assign value of type [5]number
762 .tmp.code:26:13: error: expected [5]number but variable 'a4' is [5]string
763 .tmp.code:23:51: info: this is where 'a4' was set to [5]string
764 .tmp.code:26:8: error: cannot assign value of type [5]number
765 .tmp.code:27:16: error: expected number found string
766 .tmp.code:28:16: error: expected string found Boolean
767 .tmp.code:29:12: error: have number but need string
768 .tmp.code:7:8: info: variable 'c' was set as string here.
769 .tmp.code:32:8: error: variable used but not declared: foo
770 .tmp.code:32:8: error: field reference attempted on none, not a struct
771 .tmp.code:32:16: error: expected none found number
772 .tmp.code:33:14: error: field reference attempted on string, not a struct
773 oceani: type error in program - not running.
775 ###### test: type_err4
780 ###### output: type_err4
781 .tmp.code:3:14: error: variable used but not declared: b
782 .tmp.code:3:16: error: expected none found number
783 .tmp.code:3:14: info: variable 'b' was set as none here.
784 oceani: type error in program - not running.
787 oceani_failing_tests += type_err_const type_err_const1
789 ###### test: type_err_const
792 bar ::= "string" + 56
799 ###### output: type_err_const
800 .tmp.code:4:16: error: expected number found string
801 .tmp.code:6:8: error: name already declared: bar
802 .tmp.code:4:8: info: this is where 'bar' was first declared
803 .tmp.code:8:8: error: variable 'foo' redeclared
804 .tmp.code:3:8: info: this is where 'foo' was first declared
806 ###### test: type_err_const1
814 ###### output: type_err_const1
815 .tmp.code:3:12: Syntax error in constant: :
816 .tmp.code:4:12: Syntax error in constant: :
818 ## Test erroneous command line args
820 To improve coverage, we want to test correct handling of strange command
821 line arguments. These tests won't use code, so the exiting test types
822 won't work. So we need to be able to explicitly give the command line,
823 and the expected output, and have that tested and the coverage assessed.
824 Rather than having to spell out the whole command name, just give "cmd",
825 and discard that. Requiring but discarding the command make an empty
826 command list possible.
829 @for t in $(oceani_special_tests); do \
830 echo -n "Test $$t ... ";\
831 i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
832 ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
833 ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
834 if ! cmp -s .tmp.want .tmp.have; then \
835 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
837 ./coverage_oceani $${1+"$$@"} > /dev/null 2>&1 ;\
841 oceani_special_tests += "cmd"
842 oceani_special_tests += "cmd,-zyx"
843 oceani_special_tests += "cmd,nofile"
844 oceani_special_tests += "cmd,/dev/null"
845 oceani_special_tests += "cmd,--section,toast:nothing,oceani-tests.mdc"
848 oceani: no input file given
850 ###### output: cmd,-zyx
851 ./oceani: invalid option -- 'z'
852 Usage: oceani --trace --print --noexec --brackets--section=SectionName prog.ocn
854 ###### output: cmd,nofile
855 oceani: cannot open nofile
857 ###### output: cmd,/dev/null
858 oceani: could not find any code in /dev/null
860 ###### output: cmd,--section,toast:nothing,oceani-tests.mdc
861 oceani: cannot find section toast:nothing