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 ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} \
54 > /dev/null 2> .tmp.valg; then \
55 echo "valgrind FAILED"; cat .tmp.valg; exit 1; fi ; \
56 echo -n "valgrind passed ... "; \
57 echo '``````' > .tmp.code1; echo '``````' > .tmp.code2 ;\
58 ./oceani --noexec --print --section "test: $$t" oceani-tests.mdc >> .tmp.code1; \
59 ./oceani --noexec --print .tmp.code1 >> .tmp.code2 ;\
60 if ! cmp -s .tmp.code1 .tmp.code2; then \
61 echo "Printing Failed"; diff -u .tmp.code1 .tmp.code2; exit1 ; fi ; \
62 echo "Printing passed"; \
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 '/^ *[1-9]/ {ran+=1} /^ *###/ {skip+=1} \
72 END {printf "coverage: %6.2f%%\n", ran * 100 / (ran + skip); \
73 if (ran < (ran + skip) *0.90) exit(1) }' \
74 coverage/oceani.mdc.gcov
77 coverage_oceani: oceani.c
78 $(CC) $(CFLAGS) --coverage -fprofile-dir=coverage -o coverage_oceani oceani.c $(LDLIBS)
80 ## Values and variables
82 The first test stores values in variables and performs various
86 oceani_tests += "valvar"
92 a := 23; b:=12 ; b1 := -b
93 print a, b, a+b, a-b, a*b, a/b, a%b
94 print a<b, a<=b, a>b, a>=b, a<a, a==b, a==a
95 print +a, +b, +b1, -a, -b, -b1
97 print x and y, x or y, x and x, y or y, x and not x, x < y
99 c ::= "This is a string"
100 d ::= " field theory"
103 aconst :: string = "unchanging"
105 ###### output: valvar
107 23 12 35 11 276 1.91667 11
108 False False True True False False True
110 False True True False False False
111 This is a string field theory This is a string field theory
113 Next we change the value of variables
116 oceani_tests += "setvar"
123 a = (a + a) * (a + a)
127 ###### output: setvar
130 ## Conditions and Loops
132 Now we need to test if/else and some different loops
135 oceani_tests += cond_loop
137 ###### test: cond_loop
145 for b:=1; then b=b+b; while b < 100:
148 // Newtons method for square root of 2
154 current := guess * guess
155 use +(current - target) > 0.000000001
157 guess = (guess + (target / guess) ) / 2
160 print "error is ", target - guess * guess
162 for j:=0; then j = j+3 ; while j < 10:
163 if j != 0 and then 20 / j > 3:
164 print "20 /", j," =", 20 / j
166 print "I won't calculate 20 /", j
167 pi ::= 3.1415926535897
168 if 355/113 == pi or else +(pi - 355/113) < 0.001:
170 print "lower" if 355/113 < pi else "higher"
172 ###### output: cond_loop
179 error is -4.51095e-12
180 I won't calculate 20 / 0
183 I won't calculate 20 / 9
189 The demonstration code presented in the interpreted is suitable for the test suite.
190 Here I break it into two parts, keeping the array code separate.
193 oceani_tests += "sayhello,55,33"
194 oceani_tests += "sayhello,12,60"
196 ###### test: sayhello
199 print "Hello World, what lovely oceans you have!"
200 /* When a variable is defined in both branches of an 'if',
201 * and used afterwards, the variables are merged.
207 print "Is", A, "bigger than", B,"? ", bigger
208 /* If a variable is not used after the 'if', no
209 * merge happens, so types can be different
212 double:string = "yes"
213 print A, "is more than twice", B, "?", double
216 print "double", B, "is", double
227 print "GCD of", A, "and", B,"is", a
229 print a, "is not positive, cannot calculate GCD"
231 print b, "is not positive, cannot calculate GCD"
236 print "Fibonacci:", f1,f2,
245 /* Binary search... */
262 print "Yay, I found", target
264 print "Closest I found was", mid
266 ###### output: sayhello,55,33
267 Hello World, what lovely oceans you have!
268 Is 55 bigger than 33 ? yes
270 GCD of 55 and 33 is 11
271 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
272 Closest I found was 77.3438
274 ###### output: sayhello,12,60
275 Hello World, what lovely oceans you have!
276 Is 12 bigger than 60 ? no
278 GCD of 12 and 60 is 12
279 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
280 Closest I found was 77.3438
283 oceani_tests += "insert_sort"
284 ###### test: insert_sort
289 for i:=1; then i = i + 1; while i < size:
290 n := list[i-1] * list[i-1]
291 list[i] = (n / 100) % 10000
294 for i:=0; then i = i + 1; while i < size:
295 print "list[",i,"]=",list[i]
297 for i := 1; then i=i+1; while i < size:
298 for j:=i-1; then j=j-1; while j >= 0:
299 if list[j] > list[j+1]:
304 for i:=0; then i = i + 1; while i < size:
305 print "list[",i,"]=",list[i]
307 ###### output: insert_sort
423 We already have some array tests, but this is where we put other
424 ad-hoc things array related.
427 oceani_tests += arrays
435 bools[3] = strings[1] == "Hello"
436 bools[1] = strings[2] <= "there"
438 for i:=0; then i=i+1; while i<5:
442 ###### output: arrays
443 False True False False False
445 ## Test code with syntax errors
447 Syntax errors aren't handled well yet - the result is almost always a
448 single message about the first error. So this section will be fairly
449 thin until we add proper parsing recovery in the face of common errors.
451 A special case of syntax errors is token errors, when a token is only
452 accepted because the parser doesn't know quite enough to reject it.
453 There are handled better as they are quite local, so a single test
454 program can trigger most of the possible errors.
456 To handle erronous code, we need a different set of tests, as we need to
457 capture `stderr`. The same test code will be used for type errors too.
458 As error messages contain the line number, and we don't want changes to
459 this file to change the reported numbers, we copy the code into a
460 separate file first, then run from there.
463 @for t in $(oceani_failing_tests); do \
464 echo -n "Test $$t ... "; \
465 ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
466 echo '``````' > .tmp.code; \
467 ./md2c oceani-tests.mdc "test: $$t" | grep -v '^#' >> .tmp.code; \
468 ./oceani .tmp.code > .tmp.have 2>&1; \
469 if ! cmp -s .tmp.want .tmp.have; then \
470 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
472 ./coverage_oceani --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1 ;\
476 oceani_failing_tests := syn1
477 oceani_failing_tests += tokerr
482 if then else while do
485 .tmp.code:3:11: error: unhandled parse error: then
489 a := 1i // imaginary numbers aren't understood
490 b:[2i]number // array sizes are handled separately
491 c:[3.14159]Boolean // array sizes must be integers
492 d:[1_000_000_000_000]number // they mustn't be huge
493 patn: string = "foo[ ,_]*bar"re // regexp strings are just a dream
496 This is a multiline string
497 With an unsupportable suffix
501 yy:[unknowable]number
503 zz:[zzsize]string // size must be constant, use ::=
505 // These numbers should be bad in all contexts: FIXME
508 ###### output: tokerr
509 .tmp.code:3:13: error: unsupported number suffix: 1i
510 .tmp.code:4:11: error: unsupported number suffix: 2i
511 .tmp.code:5:11: error: array size must be an integer: 3.14159
512 .tmp.code:6:11: error: array size is too large: 1_000_000_000_000
513 .tmp.code:7:23: error: unsupported string suffix: "foo[ ,_]*bar"re
514 .tmp.code:9:17: error: unsupported string suffix: """
515 This is a multiline string
516 With an unsupportable suffix
518 .tmp.code:14:11: error: undefined type: unknown
519 .tmp.code:15:12: error: name undeclared: unknowable
520 .tmp.code:17:12: error: array size must be a constant: zzsize
521 .tmp.code:20:12: error: unrecognised number: 00123
523 ## Tests for type errors
525 Type error don't cause parsing to abort, so we can fit many in the
526 one test program. Some type errors are found during the parse, others
527 during type analysis which doesn't run if parsing failed. So we cannot
528 fit everything in one.
530 These programs were generated by looking for the
531 various places that `type_err()` are called.
534 oceani_failing_tests += type_err1 type_err2 type_err3 type_err4
536 ###### test: type_err1
539 print "hello" ++ 5, 5 ++ "hello"
544 if 3 * 4 and not True: print "Weird"
546 ###### output: type_err1
547 .tmp.code:3:25: error: expected string found number
548 .tmp.code:3:28: error: expected string found number
549 .tmp.code:6:8: error: Cannot assign to a constant: b
550 .tmp.code:5:8: info: name was defined as a constant here
551 .tmp.code:6:8: error: Cannot assign to a constant: b
552 .tmp.code:5:8: info: name was defined as a constant here
553 .tmp.code:8:11: error: Arithmetic returns number but Boolean expected
554 oceani: type error in program - not running.
556 ###### test: type_err2
566 ###### output: type_err2
567 .tmp.code:4:8: error: variable 'a' redeclared
568 .tmp.code:3:8: info: this is where 'a' was first declared
569 .tmp.code:5:8: error: variable 'a' redeclared
570 .tmp.code:3:8: info: this is where 'a' was first declared
571 .tmp.code:6:8: error: variable 'a' redeclared
572 .tmp.code:3:8: info: this is where 'a' was first declared
573 .tmp.code:7:8: error: variable 'a' redeclared
574 .tmp.code:3:8: info: this is where 'a' was first declared
575 .tmp.code:8:8: Variable declared with no type or value: c
577 ###### test: type_err3
582 c = "hello" ++ (True and False)
584 print 45 + ( "Hello" ++ "there")
594 case "Hello": print "Hello"
596 a1:[5]number; a2:[5]number; a3:[10]number; a4:[5]string
604 ###### output: type_err3
605 .tmp.code:4:12: error: expected number but variable 'c' is string
606 .tmp.code:3:8: info: this is where 'c' was set to string
607 .tmp.code:4:12: error: Arithmetic returns number but string expected
608 .tmp.code:3:8: info: variable 'c' was set as string here.
609 .tmp.code:5:24: error: Boolean operation found where string expected
610 .tmp.code:6:12: error: Comparison returns Boolean but string expected
611 .tmp.code:3:8: info: variable 'c' was set as string here.
612 .tmp.code:7:21: error: Concat returns string but number expected
613 .tmp.code:8:8: error: string cannot be indexed
614 .tmp.code:8:8: error: string cannot be indexed
615 .tmp.code:17:13: error: expected number found string
616 .tmp.code:13:16: error: expected number, found string
617 .tmp.code:20:8: error: cannot assign value of type [5]number
618 .tmp.code:21:13: error: expected [5]number but variable 'a3' is [10]number
619 .tmp.code:19:36: info: this is where 'a3' was set to [10]number
620 .tmp.code:21:8: error: cannot assign value of type [5]number
621 .tmp.code:22:13: error: expected [5]number but variable 'a4' is [5]string
622 .tmp.code:19:51: info: this is where 'a4' was set to [5]string
623 .tmp.code:22:8: error: cannot assign value of type [5]number
624 .tmp.code:23:16: error: expected number found string
625 .tmp.code:24:16: error: expected string found Boolean
626 .tmp.code:25:12: error: have number but need string
627 .tmp.code:3:8: info: variable 'c' was set as string here.
628 oceani: type error in program - not running.
630 ###### test: type_err4
635 ###### output: type_err4
636 .tmp.code:3:14: error: expected *unknown*type* (labels not permitted) but variable 'b' is label
637 .tmp.code:3:14: info: this is where 'b' was set to label
638 .tmp.code:3:16: error: expected label found number
639 .tmp.code:3:14: info: variable 'b' was set as label here.
640 .tmp.code:4:17: error: expected *unknown*type* (labels not permitted) but variable 'b' is label
641 .tmp.code:3:14: info: this is where 'b' was set to label
642 oceani: type error in program - not running.
644 ## Test erroneous command line args
646 To improve coverage, we want to test correct handling of strange command
647 line arguments. These tests won't use code, so the exiting test types
648 won't work. So we need to be able to explicitly give the command line,
649 and the expected output, and have that tested and the coverage assessed.
650 Rather than having to spell out the whole command name, just give "cmd",
651 and discard that. Requiring but discarding the command make an empty
652 command list possible.
655 @for t in $(oceani_special_tests); do \
656 echo -n "Test $$t ... ";\
657 i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
658 ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
659 ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
660 if ! cmp -s .tmp.want .tmp.have; then \
661 echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
663 ./coverage_oceani $${1+"$$@"} > /dev/null 2>&1 ;\
668 oceani_special_tests += "cmd"
669 oceani_special_tests += "cmd,-zyx"
670 oceani_special_tests += "cmd,nofile"
671 oceani_special_tests += "cmd,/dev/null"
672 oceani_special_tests += "cmd,--section,toast:nothing,oceani-tests.mdc"
675 oceani: no input file given
677 ###### output: cmd,-zyx
678 ./oceani: invalid option -- 'z'
679 Usage: oceani --trace --print --noexec --brackets--section=SectionName prog.ocn
681 ###### output: cmd,nofile
682 oceani: cannot open nofile
684 ###### output: cmd,/dev/null
685 oceani: could not find any code in /dev/null
687 ###### output: cmd,--section,toast:nothing,oceani-tests.mdc
688 oceani: cannot find section toast:nothing