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 "passed ... "; \
53 if ! valgrind --error-exitcode=1 --log-file=.tmp.valg ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} \
54 > /dev/null 2>&1 ; then \
55 echo "valgrind FAILED"; cat .tmp.valg; exit 1; fi ; \
56 if grep 'LEAK SUMMARY' .tmp.valg > /dev/null; then \
57 echo "valgrind found LEAKS"; cat .tmp.valg ; exit 1 ; fi; \
58 if grep 'in use at exit [1-9]' .tmp.valg > /dev/null; then \
59 echo "valgrind found memory in use at exit"; cat .tmp.valg ; exit 1 ; fi; \
60 echo -n "valgrind passed ... "; \
61 echo '``````' > .tmp.code1; echo '``````' > .tmp.code2 ;\
62 ./oceani --noexec --print --section "test: $$t" oceani-tests.mdc >> .tmp.code1; \
63 ./oceani --noexec --print .tmp.code1 >> .tmp.code2 ;\
64 if ! cmp -s .tmp.code1 .tmp.code2; then \
65 echo "Printing Failed"; diff -u .tmp.code1 .tmp.code2; exit1 ; fi ; \
66 echo "Printing passed"; \
67 ./coverage_oceani --print --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > /dev/null ; \
68 ./coverage_oceani -tpbn --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1; \
73 @gcov -o coverage oceani.mdc > /dev/null 2> /dev/null
74 @mv *.gcov coverage ; [ -f .gcov ] && mv .gcov coverage
75 @ awk '/NOTEST/ { next } /^ *[1-9]/ {ran+=1} /^ *###/ {skip+=1} \
76 END {printf "coverage: %6.2f%%\n", ran * 100 / (ran + skip); \
77 if (ran < (ran + skip) *0.93) exit(1) }' \
78 coverage/oceani.mdc.gcov
81 coverage_oceani: oceani.c
82 $(CC) $(CFLAGS) --coverage -fprofile-dir=coverage -o coverage_oceani oceani.c $(LDLIBS)
84 ## Values and variables
86 The first test stores values in variables and performs various
90 oceani_tests += "valvar"
96 a := 23; b:=12 ; b1 := -b
97 print a, b, a+b, a-b, a*b, a/b, a%b
98 print a<b, a<=b, a>b, a>=b, a<a, a==b, a==a
99 print +a, +b, +b1, -a, -b, -b1
100 x := True; y := False
101 print x and y, x or y, x and x, y or y, x and not x, x < y
103 c ::= "This is a string"
104 d ::= " field theory"
107 aconst :: string = "unchanging"
109 ###### output: valvar
111 23 12 35 11 276 1.91667 11
112 False False True True False False True
114 False True True False False False
115 This is a string field theory This is a string field theory
117 Next we change the value of variables
120 oceani_tests += "setvar"
127 a = (a + a) * (a + a)
131 ###### output: setvar
137 oceani_tests += "consts"
142 four ::= 2 + 2 ; five ::= 10/2
143 const pie ::= "I like Pie";
144 cake ::= "The cake is"
148 print "Hello World, what lovely oceans you have!"
149 print "are there", five, "?"
150 print pi, pie, "but", cake
152 ###### output: consts
153 Hello World, what lovely oceans you have!
155 3.14159 I like Pie but The cake is a lie
157 ## Conditions and Loops
159 Now we need to test if/else and some different loops
162 oceani_tests += cond_loop
164 ###### test: cond_loop
172 for b:=1; then b=b+b; while b < 100:
175 // Newtons method for square root of 2
181 current := guess * guess
182 use +(current - target) > 0.000000001
184 guess = (guess + (target / guess) ) / 2
187 print "error is ", target - guess * guess
189 for j:=0; then j = j+3 ; while j < 10:
190 if j != 0 and then 20 / j > 3:
191 print "20 /", j," =", 20 / j
193 print "I won't calculate 20 /", j
194 pi ::= 3.1415926535897
195 if 355/113 == pi or else +(pi - 355/113) < 0.001:
197 print "lower" if 355/113 < pi else "higher"
199 ###### output: cond_loop
206 error is -4.51095e-12
207 I won't calculate 20 / 0
210 I won't calculate 20 / 9
216 The demonstration code presented in the interpreted is suitable for the test suite.
217 Here I break it into two parts, keeping the array code separate.
220 oceani_tests += "sayhello,55,33"
221 oceani_tests += "sayhello,12,60"
223 ###### test: sayhello
226 print "Hello World, what lovely oceans you have!"
227 /* When a variable is defined in both branches of an 'if',
228 * and used afterwards, the variables are merged.
234 print "Is", A, "bigger than", B,"? ", bigger
235 /* If a variable is not used after the 'if', no
236 * merge happens, so types can be different
239 double:string = "yes"
240 print A, "is more than twice", B, "?", double
243 print "double", B, "is", double
254 print "GCD of", A, "and", B,"is", a
256 print a, "is not positive, cannot calculate GCD"
258 print b, "is not positive, cannot calculate GCD"
263 print "Fibonacci:", f1,f2,
272 /* Binary search... */
289 print "Yay, I found", target
291 print "Closest I found was", mid
293 ###### output: sayhello,55,33
294 Hello World, what lovely oceans you have!
295 Is 55 bigger than 33 ? yes
297 GCD of 55 and 33 is 11
298 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
299 Closest I found was 77.3438
301 ###### output: sayhello,12,60
302 Hello World, what lovely oceans you have!
303 Is 12 bigger than 60 ? no
305 GCD of 12 and 60 is 12
306 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
307 Closest I found was 77.3438
310 oceani_tests += "insert_sort"
311 ###### test: insert_sort
316 for i:=1; then i = i + 1; while i < size:
317 n := list[i-1] * list[i-1]
318 list[i] = (n / 100) % 10000
321 for i:=0; then i = i + 1; while i < size:
322 print "list[",i,"]=",list[i]
324 for i := 1; then i=i+1; while i < size:
325 for j:=i-1; then j=j-1; while j >= 0:
326 if list[j] > list[j+1]:
331 for i:=0; then i = i + 1; while i < size:
332 print "list[",i,"]=",list[i]
334 ###### output: insert_sort
450 We already have some array tests, but this is where we put other
451 ad-hoc things array related.
454 oceani_tests += arrays
462 bools[3] = strings[1] == "Hello"
463 bools[1] = strings[2] <= "there"
465 for i:=0; then i=i+1; while i<5:
469 ###### output: arrays
470 False True False False False
472 ## Test code with syntax errors
474 Syntax errors aren't handled well yet - the result is almost always a
475 single message about the first error. So this section will be fairly
476 thin until we add proper parsing recovery in the face of common errors.
478 A special case of syntax errors is token errors, when a token is only
479 accepted because the parser doesn't know quite enough to reject it.
480 There are handled better as they are quite local, so a single test
481 program can trigger most of the possible errors.
483 To handle erronous code, we need a different set of tests, as we need to
484 capture `stderr`. The same test code will be used for type errors too.
485 As error messages contain the line number, and we don't want changes to
486 this file to change the reported numbers, we copy the code into a
487 separate file first, then run from there.
490 @for t in $(oceani_failing_tests); do \
491 echo -n "Test $$t ... "; \
492 ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
493 echo '``````' > .tmp.code; \
494 ./md2c oceani-tests.mdc "test: $$t" | grep -v '^#' >> .tmp.code; \
495 ./oceani .tmp.code > .tmp.have 2>&1; \
496 if ! cmp -s .tmp.want .tmp.have; then \
497 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
499 ./coverage_oceani --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1 ;\
503 oceani_failing_tests := syn1
504 oceani_failing_tests += tokerr
509 if then else while do
512 .tmp.code:3:11: error: unhandled parse error: then
513 oceani: no program found.
517 a := 1i // imaginary numbers aren't understood
518 b:[2i]number // array sizes are handled separately
519 c:[3.14159]Boolean // array sizes must be integers
520 d:[1_000_000_000_000]number // they mustn't be huge
521 patn: string = "foo[ ,_]*bar"re // regexp strings are just a dream
524 This is a multiline string
525 With an unsupportable suffix
529 yy:[unknowable]number
531 zz:[zzsize]string // size must be constant, use ::=
533 // These numbers should be bad in all contexts: FIXME
536 ###### output: tokerr
537 .tmp.code:3:13: error: unsupported number suffix: 1i
538 .tmp.code:4:11: error: unsupported number suffix: 2i
539 .tmp.code:5:11: error: array size must be an integer: 3.14159
540 .tmp.code:6:11: error: array size is too large: 1_000_000_000_000
541 .tmp.code:7:23: error: unsupported string suffix: "foo[ ,_]*bar"re
542 .tmp.code:9:17: error: unsupported string suffix: """
543 This is a multiline string
544 With an unsupportable suffix
546 .tmp.code:14:11: error: undefined type: unknown
547 .tmp.code:15:12: error: name undeclared: unknowable
548 .tmp.code:17:12: error: array size must be a constant: zzsize
549 .tmp.code:20:12: error: unrecognised number: 00123
551 ## Tests for type errors
553 Type error don't cause parsing to abort, so we can fit many in the
554 one test program. Some type errors are found during the parse, others
555 during type analysis which doesn't run if parsing failed. So we cannot
556 fit everything in one.
558 These programs were generated by looking for the
559 various places that `type_err()` are called.
562 oceani_failing_tests += type_err1 type_err2 type_err3 type_err4
564 ###### test: type_err1
567 print "hello" ++ 5, 5 ++ "hello"
572 if 3 * 4 and not True: print "Weird"
574 ###### output: type_err1
575 .tmp.code:3:25: error: expected string found number
576 .tmp.code:3:28: error: expected string found number
577 .tmp.code:6:8: error: Cannot assign to a constant: b
578 .tmp.code:5:8: info: name was defined as a constant here
579 .tmp.code:6:8: error: Cannot assign to a constant: b
580 .tmp.code:5:8: info: name was defined as a constant here
581 .tmp.code:8:11: error: Arithmetic returns number but Boolean expected
582 oceani: type error in program - not running.
584 ###### test: type_err2
594 ###### output: type_err2
595 .tmp.code:4:8: error: variable 'a' redeclared
596 .tmp.code:3:8: info: this is where 'a' was first declared
597 .tmp.code:5:8: error: variable 'a' redeclared
598 .tmp.code:3:8: info: this is where 'a' was first declared
599 .tmp.code:6:8: error: variable 'a' redeclared
600 .tmp.code:3:8: info: this is where 'a' was first declared
601 .tmp.code:7:8: error: variable 'a' redeclared
602 .tmp.code:3:8: info: this is where 'a' was first declared
603 .tmp.code:8:8: Variable declared with no type or value: c
605 ###### test: type_err3
610 c = "hello" ++ (True and False)
612 print 45 + ( "Hello" ++ "there")
622 case "Hello": print "Hello"
624 a1:[5]number; a2:[5]number; a3:[10]number; a4:[5]string
632 ###### output: type_err3
633 .tmp.code:4:12: error: expected number but variable 'c' is string
634 .tmp.code:3:8: info: this is where 'c' was set to string
635 .tmp.code:4:12: error: Arithmetic returns number but string expected
636 .tmp.code:3:8: info: variable 'c' was set as string here.
637 .tmp.code:5:24: error: Boolean operation found where string expected
638 .tmp.code:6:12: error: Comparison returns Boolean but string expected
639 .tmp.code:3:8: info: variable 'c' was set as string here.
640 .tmp.code:7:21: error: Concat returns string but number expected
641 .tmp.code:8:8: error: string cannot be indexed
642 .tmp.code:8:8: error: string cannot be indexed
643 .tmp.code:17:13: error: expected number found string
644 .tmp.code:13:16: error: expected number, found string
645 .tmp.code:20:8: error: cannot assign value of type [5]number
646 .tmp.code:21:13: error: expected [5]number but variable 'a3' is [10]number
647 .tmp.code:19:36: info: this is where 'a3' was set to [10]number
648 .tmp.code:21:8: error: cannot assign value of type [5]number
649 .tmp.code:22:13: error: expected [5]number but variable 'a4' is [5]string
650 .tmp.code:19:51: info: this is where 'a4' was set to [5]string
651 .tmp.code:22:8: error: cannot assign value of type [5]number
652 .tmp.code:23:16: error: expected number found string
653 .tmp.code:24:16: error: expected string found Boolean
654 .tmp.code:25:12: error: have number but need string
655 .tmp.code:3:8: info: variable 'c' was set as string here.
656 oceani: type error in program - not running.
658 ###### test: type_err4
663 ###### output: type_err4
664 .tmp.code:3:14: error: expected *unknown*type* (labels not permitted) but variable 'b' is label
665 .tmp.code:3:14: info: this is where 'b' was set to label
666 .tmp.code:3:16: error: expected label found number
667 .tmp.code:3:14: info: variable 'b' was set as label here.
668 .tmp.code:4:17: error: expected *unknown*type* (labels not permitted) but variable 'b' is label
669 .tmp.code:3:14: info: this is where 'b' was set to label
670 oceani: type error in program - not running.
673 oceani_failing_tests += type_err_const type_err_const1
675 ###### test: type_err_const
678 bar ::= "string" + 56
685 ###### output: type_err_const
686 .tmp.code:4:16: error: expected number found string
687 .tmp.code:6:8: error: name already declared: bar
688 .tmp.code:4:8: info: this is where 'bar' was first declared
689 .tmp.code:8:8: error: variable 'foo' redeclared
690 .tmp.code:3:8: info: this is where 'foo' was first declared
692 ###### test: type_err_const1
700 ###### output: type_err_const1
701 .tmp.code:3:12: error: unhandled parse error: :
702 oceani: no program found.
705 ## Test erroneous command line args
707 To improve coverage, we want to test correct handling of strange command
708 line arguments. These tests won't use code, so the exiting test types
709 won't work. So we need to be able to explicitly give the command line,
710 and the expected output, and have that tested and the coverage assessed.
711 Rather than having to spell out the whole command name, just give "cmd",
712 and discard that. Requiring but discarding the command make an empty
713 command list possible.
716 @for t in $(oceani_special_tests); do \
717 echo -n "Test $$t ... ";\
718 i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
719 ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
720 ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
721 if ! cmp -s .tmp.want .tmp.have; then \
722 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
724 ./coverage_oceani $${1+"$$@"} > /dev/null 2>&1 ;\
729 oceani_special_tests += "cmd"
730 oceani_special_tests += "cmd,-zyx"
731 oceani_special_tests += "cmd,nofile"
732 oceani_special_tests += "cmd,/dev/null"
733 oceani_special_tests += "cmd,--section,toast:nothing,oceani-tests.mdc"
736 oceani: no input file given
738 ###### output: cmd,-zyx
739 ./oceani: invalid option -- 'z'
740 Usage: oceani --trace --print --noexec --brackets--section=SectionName prog.ocn
742 ###### output: cmd,nofile
743 oceani: cannot open nofile
745 ###### output: cmd,/dev/null
746 oceani: could not find any code in /dev/null
748 ###### output: cmd,--section,toast:nothing,oceani-tests.mdc
749 oceani: cannot find section toast:nothing