]> ocean-lang.org Git - ocean/blob - csrc/oceani-tests.mdc
oceani-tests: add tests for various type errors.
[ocean] / csrc / oceani-tests.mdc
1 # Ocean Interpreter test code
2
3 Regular testing is, of course, important for developing any software.
4 The Ocean interpreted is no exception.  This document allows easy
5 testing by providing:
6
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.
10
11 Three different sorts of tests are run.  As soon as any fail, the whole
12 test stops.
13
14 1/ Each program is run and the output is compared against the expected
15     output
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
18     memory.
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.
29
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.
33
34 ###### File: oceani-tests.mk
35
36         oceani_tests :=
37         ## test list
38
39         tests:: oceani_test_suite
40         oceani_test_suite: oceani coverage_oceani
41                 @rm -rf coverage; mkdir -p coverage
42                 @cp *.gcno coverage
43                 @for T in $(oceani_tests); do \
44                     echo -n "Test $$T ... "; \
45                     i="$$IFS"; IFS=,; set $$T; IFS="$$i"; t=$$1; shift; \
46                     ./md2c oceani-tests.mdc "output: $$T" | grep -v '^#' > .tmp.want; \
47                     ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > .tmp.have; \
48                     if ! cmp -s .tmp.want .tmp.have; then \
49                        echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
50                     echo -n "passed ... "; \
51                     if ! valgrind ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} \
52                          > /dev/null 2> .tmp.valg; then \
53                        echo "valgrind FAILED"; cat .tmp.valg; exit 1; fi ; \
54                     echo -n "valgrind passed ... "; \
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 "Printing Failed"; diff -u .tmp.code1 .tmp.code2; exit1 ; fi ; \
60                     echo "Printing passed"; \
61                     ./coverage_oceani --print --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > /dev/null ; \
62                     ./coverage_oceani -tpbn --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1; \
63                 done
64
65                 ## test code
66
67                 @gcov -o coverage oceani.mdc > /dev/null 2> /dev/null
68                 @mv *.gcov coverage ; [ -f .gcov ] && mv .gcov coverage
69                 @ awk '/^ *[1-9]/ {ran+=1} /^ *###/ {skip+=1} \
70                     END {printf "coverage: %6.2f%%\n", ran * 100 / (ran + skip); \
71                          if (ran < (ran + skip) *0.85) exit(1) }' \
72                         coverage/oceani.mdc.gcov
73                 @rm -f .tmp*
74
75         coverage_oceani: oceani.c
76                 $(CC) $(CFLAGS) --coverage -fprofile-dir=coverage -o coverage_oceani oceani.c $(LDLIBS)
77
78 ## Values and variables
79
80 The first test stores values in variables and performs various
81 calculations on them.
82
83 ###### test list
84        oceani_tests += "valvar"
85
86
87 ###### test: valvar
88
89         program:
90                 a := 23; b:=12
91                 print a, b, a+b, a-b, a*b, a/b, a%b
92                 print a<b, a<=b, a>b, a>=b, a<a, a==b, a==a
93
94                 c ::= "This is a string"
95                 d ::= " field theory"
96                 print c, d, c++d
97
98 ###### output: valvar
99
100         23 12 35 11 276 1.91667 11
101         False False True True False False True
102         This is a string  field theory This is a string field theory
103
104 Next we change the value of variables
105
106 ###### test list
107        oceani_tests += "setvar"
108
109 ###### test: setvar
110
111         program:
112                 a := 4
113                 a = a * a
114                 a = (a + a) * (a + a)
115                 a = a * a * a
116                 print a, a/a
117
118 ###### output: setvar
119         1.07374e+09 1
120
121 ## Conditions and Loops
122
123 Now we need to test if/else and some different loops
124
125 ###### test list
126        oceani_tests += cond_loop
127
128 ###### test: cond_loop
129
130         program:
131                 a := 4
132                 if a < 5:
133                         print "Success"
134                 else:
135                         print "Failure"
136                 for b:=1; then b=b+b; while b < 100:
137                         print '', b,
138                 print
139                 // Newtons method for square root of 2
140                 target ::= 2
141                 guess := target
142                 for:
143                         count: number = 0
144                 while:
145                         current := guess * guess
146                         use +(current - target) > 0.000000001
147                 do:
148                         guess = (guess + (target / guess) ) / 2
149                         print count, guess
150                         count = count + 1
151                 print "error is ", target - guess * guess
152
153 ###### output: cond_loop
154         Success
155          1 2 4 8 16 32 64
156         0 1.5
157         1 1.41667
158         2 1.41422
159         3 1.41421
160         error is  -4.51095e-12
161
162 ## Say Hello
163
164 The demonstration code presented in the interpreted is suitable for the test suite.
165 Here I break it into two parts, keeping the array code separate.
166
167 ###### test list
168         oceani_tests += "sayhello,55,33"
169         oceani_tests += "sayhello,12,60"
170
171 ###### test: sayhello
172
173         program A B:
174                 print "Hello World, what lovely oceans you have!"
175                 /* When a variable is defined in both branches of an 'if',
176                  * and used afterwards, the variables are merged.
177                  */
178                 if A > B:
179                         bigger := "yes"
180                 else:
181                         bigger := "no"
182                 print "Is", A, "bigger than", B,"? ", bigger
183                 /* If a variable is not used after the 'if', no
184                  * merge happens, so types can be different
185                  */
186                 if A > B * 2:
187                         double:string = "yes"
188                         print A, "is more than twice", B, "?", double
189                 else:
190                         double := B*2
191                         print "double", B, "is", double
192
193                 a : number
194                 a = A;
195                 b:number = B
196                 if a > 0 and b > 0:
197                         while a != b:
198                                 if a < b:
199                                         b = b - a
200                                 else:
201                                         a = a - b
202                         print "GCD of", A, "and", B,"is", a
203                 else if a <= 0:
204                         print a, "is not positive, cannot calculate GCD"
205                 else:
206                         print b, "is not positive, cannot calculate GCD"
207
208                 for:
209                         togo := 10
210                         f1 := 1; f2 := 1
211                         print "Fibonacci:", f1,f2,
212                 then togo = togo - 1
213                 while togo > 0:
214                         f3 := f1 + f2
215                         print "", f3,
216                         f1 = f2
217                         f2 = f3
218                 print ""
219
220                 /* Binary search... */
221                 for:
222                         lo:= 0; hi := 100
223                         target := 77
224                 while:
225                         mid := (lo + hi) / 2
226                         if mid == target:
227                                 use Found
228                         if mid < target:
229                                 lo = mid
230                         else:
231                                 hi = mid
232                         if hi - lo < 1:
233                                 use GiveUp
234                         use True
235                 do: pass
236                 case Found:
237                         print "Yay, I found", target
238                 case GiveUp:
239                         print "Closest I found was", mid
240
241 ###### output: sayhello,55,33
242         Hello World, what lovely oceans you have!
243         Is 55 bigger than 33 ?  yes
244         double 33 is 66
245         GCD of 55 and 33 is 11
246         Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
247         Closest I found was 77.3438
248
249 ###### output: sayhello,12,60
250         Hello World, what lovely oceans you have!
251         Is 12 bigger than 60 ?  no
252         double 60 is 120
253         GCD of 12 and 60 is 12
254         Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
255         Closest I found was 77.3438
256
257 ###### test list
258         oceani_tests += "insert_sort"
259 ###### test: insert_sort
260         program:
261                 size::=55
262                 list:[size]number
263                 list[0] = 1234
264                 for i:=1; then i = i + 1; while i < size:
265                         n := list[i-1] * list[i-1]
266                         list[i] = (n / 100) % 10000
267
268                 print "Before sort:"
269                 for i:=0; then i = i + 1; while i < size:
270                         print "list[",i,"]=",list[i]
271
272                 for i := 1; then i=i+1; while i < size:
273                         for j:=i-1; then j=j-1; while j >= 0:
274                                 if list[j] > list[j+1]:
275                                         t:= list[j]
276                                         list[j] = list[j+1]
277                                         list[j+1] = t
278                 print "After sort:"
279                 for i:=0; then i = i + 1; while i < size:
280                         print "list[",i,"]=",list[i]
281
282 ###### output: insert_sort
283         Before sort:
284         list[ 0 ]= 1234
285         list[ 1 ]= 5227
286         list[ 2 ]= 3215
287         list[ 3 ]= 3362
288         list[ 4 ]= 3030
289         list[ 5 ]= 1809
290         list[ 6 ]= 2724
291         list[ 7 ]= 4201
292         list[ 8 ]= 6484
293         list[ 9 ]= 422
294         list[ 10 ]= 1780
295         list[ 11 ]= 1684
296         list[ 12 ]= 8358
297         list[ 13 ]= 8561
298         list[ 14 ]= 2907
299         list[ 15 ]= 4506
300         list[ 16 ]= 3040
301         list[ 17 ]= 2416
302         list[ 18 ]= 8370
303         list[ 19 ]= 569
304         list[ 20 ]= 3237
305         list[ 21 ]= 4781
306         list[ 22 ]= 8579
307         list[ 23 ]= 5992
308         list[ 24 ]= 9040
309         list[ 25 ]= 7216
310         list[ 26 ]= 706
311         list[ 27 ]= 4984
312         list[ 28 ]= 8402
313         list[ 29 ]= 5936
314         list[ 30 ]= 2360
315         list[ 31 ]= 5696
316         list[ 32 ]= 4444
317         list[ 33 ]= 7491
318         list[ 34 ]= 1150
319         list[ 35 ]= 3225
320         list[ 36 ]= 4006
321         list[ 37 ]= 480
322         list[ 38 ]= 2304
323         list[ 39 ]= 3084
324         list[ 40 ]= 5110
325         list[ 41 ]= 1121
326         list[ 42 ]= 2566
327         list[ 43 ]= 5843
328         list[ 44 ]= 1406
329         list[ 45 ]= 9768
330         list[ 46 ]= 4138
331         list[ 47 ]= 1230
332         list[ 48 ]= 5129
333         list[ 49 ]= 3066
334         list[ 50 ]= 4003
335         list[ 51 ]= 240
336         list[ 52 ]= 576
337         list[ 53 ]= 3317
338         list[ 54 ]= 24
339         After sort:
340         list[ 0 ]= 24
341         list[ 1 ]= 240
342         list[ 2 ]= 422
343         list[ 3 ]= 480
344         list[ 4 ]= 569
345         list[ 5 ]= 576
346         list[ 6 ]= 706
347         list[ 7 ]= 1121
348         list[ 8 ]= 1150
349         list[ 9 ]= 1230
350         list[ 10 ]= 1234
351         list[ 11 ]= 1406
352         list[ 12 ]= 1684
353         list[ 13 ]= 1780
354         list[ 14 ]= 1809
355         list[ 15 ]= 2304
356         list[ 16 ]= 2360
357         list[ 17 ]= 2416
358         list[ 18 ]= 2566
359         list[ 19 ]= 2724
360         list[ 20 ]= 2907
361         list[ 21 ]= 3030
362         list[ 22 ]= 3040
363         list[ 23 ]= 3066
364         list[ 24 ]= 3084
365         list[ 25 ]= 3215
366         list[ 26 ]= 3225
367         list[ 27 ]= 3237
368         list[ 28 ]= 3317
369         list[ 29 ]= 3362
370         list[ 30 ]= 4003
371         list[ 31 ]= 4006
372         list[ 32 ]= 4138
373         list[ 33 ]= 4201
374         list[ 34 ]= 4444
375         list[ 35 ]= 4506
376         list[ 36 ]= 4781
377         list[ 37 ]= 4984
378         list[ 38 ]= 5110
379         list[ 39 ]= 5129
380         list[ 40 ]= 5227
381         list[ 41 ]= 5696
382         list[ 42 ]= 5843
383         list[ 43 ]= 5936
384         list[ 44 ]= 5992
385         list[ 45 ]= 6484
386         list[ 46 ]= 7216
387         list[ 47 ]= 7491
388         list[ 48 ]= 8358
389         list[ 49 ]= 8370
390         list[ 50 ]= 8402
391         list[ 51 ]= 8561
392         list[ 52 ]= 8579
393         list[ 53 ]= 9040
394         list[ 54 ]= 9768
395
396 ## Test code with syntax errors
397
398 Syntax errors aren't handled well yet - the result is almost always a
399 single message about the first error.  So this section will be fairly
400 thin until we add proper parsing recovery in the face of common errors.
401
402 A special case of syntax errors is token errors, when a token is only
403 accepted because the parser doesn't know quite enough to reject it.
404 There are handled better as they are quite local, so a single test
405 program can trigger most of the possible errors.
406
407 To handle erronous code, we need a different set of tests, as we need to
408 capture `stderr`. The same test code will be used for type errors too.
409 As error messages contain the line number, and we don't want changes to
410 this file to change the reported numbers, we copy the code into a
411 separate file first, then run from there.
412
413 ###### test code
414         @for t in $(oceani_failing_tests); do \
415             echo -n "Test $$t ... "; \
416             ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
417             echo '``````' > .tmp.code; \
418             ./md2c oceani-tests.mdc "test: $$t" | grep -v '^#' >> .tmp.code; \
419             ./oceani .tmp.code > .tmp.have 2>&1; \
420             if ! cmp -s .tmp.want .tmp.have; then \
421                echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
422             echo  "passed"; \
423             ./coverage_oceani --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1 ;\
424         done || true
425
426 ###### test list
427         oceani_failing_tests := syn1
428         oceani_failing_tests += tokerr
429
430 ###### test: syn1
431
432         program:
433                 if then else while do
434
435 ###### output: syn1
436         .tmp.code:3:11: error: unhandled parse error: then
437
438 ###### test: tokerr
439         program:
440                 a := 1i  // imaginary numbers aren't understood
441                 b:[2i]number // array sizes are handled separately
442                 c:[3.14159]Boolean // array sizes must be integers
443                 d:[1_000_000_000_000]number // they mustn't be huge
444                 patn: string = "foo[ ,_]*bar"re // regexp strings are just a dream
445
446                 multi := """
447                 This is a multiline string
448                 With an unsupportable suffix
449                 """Aa
450
451                 xx:unknown = 24
452                 yy:[unknowable]number
453                 zzsize := 4
454                 zz:[zzsize]string // size must be constant, use ::=
455
456                 // These numbers should be bad in all contexts: FIXME
457                 aa:[00123]number
458
459 ###### output: tokerr
460         .tmp.code:3:13: error: unsupported number suffix: 1i
461         .tmp.code:4:11: error: unsupported number suffix: 2i
462         .tmp.code:5:11: error: array size must be an integer: 3.14159
463         .tmp.code:6:11: error: array size is too large: 1_000_000_000_000
464         .tmp.code:7:23: error: unsupported string suffix: "foo[ ,_]*bar"re
465         .tmp.code:9:17: error: unsupported string suffix: """
466                 This is a multiline string
467                 With an unsupportable suffix
468                 """Aa
469         .tmp.code:14:11: error: undefined type: unknown
470         .tmp.code:15:12: error: name undeclared: unknowable
471         .tmp.code:17:12: error: array size must be a constant: zzsize
472         .tmp.code:20:12: error: unrecognised number: 00123
473
474 ## Tests for type errors
475
476 Type error don't cause parsing to abort, so we can fit many in the
477 one test program.  Some type errors are found during the parse, others
478 during type analysis which doesn't run if parsing failed.  So we cannot
479 fit everything in one.
480
481 These programs were generated by looking for the
482 various places that `type_err()` are called.
483
484 ###### test list
485         oceani_failing_tests += type_err1 type_err2 type_err3
486
487 ###### test: type_err1
488
489         program:
490                 print "hello" ++ 5, 5 ++ "hello"
491
492                 b ::= 3
493                 b = b + 1
494
495                 if 3 * 4 and not True: print "Weird"
496
497 ###### output: type_err1
498         .tmp.code:3:25: error: expected string found number
499         .tmp.code:3:28: error: expected string found number
500         .tmp.code:6:8: error: Cannot assign to a constant: b
501         .tmp.code:5:8: info: name was defined as a constant here
502         .tmp.code:6:8: error: Cannot assign to a constant: b
503         .tmp.code:5:8: info: name was defined as a constant here
504         .tmp.code:8:11: error: Arithmetic returns number but Boolean expected
505         oceani: type error in program - not running.
506
507 ###### test: type_err2
508
509         program:
510                 a := 1
511                 a := 2
512                 a ::= 3
513                 a:number = 4
514                 a ::number = 5
515                 c:
516
517 ###### output: type_err2
518         .tmp.code:4:8: error: variable 'a' redeclared
519         .tmp.code:3:8: info: this is where 'a' was first declared
520         .tmp.code:5:8: error: variable 'a' redeclared
521         .tmp.code:3:8: info: this is where 'a' was first declared
522         .tmp.code:6:8: error: variable 'a' redeclared
523         .tmp.code:3:8: info: this is where 'a' was first declared
524         .tmp.code:7:8: error: variable 'a' redeclared
525         .tmp.code:3:8: info: this is where 'a' was first declared
526         .tmp.code:8:8: Variable declared with no type or value: c
527
528 ###### test: type_err3
529
530         program:
531                 c := "hello"
532                 c = c + 1
533                 c = "hello" ++ (True and False)
534                 c = 4 < 5
535                 print 45 + ( "Hello" ++ "there")
536                 c[5] = 1
537
538                 while:
539                         use 1
540                         use True
541                         use "Hello"
542                 do:
543                         print
544                 case 1: print "one"
545                 case "Hello": print "Hello"
546
547                 a1:[5]number; a2:[5]number; a3:[10]number; a4:[5]string
548                 a1 = a2
549                 a1 = a3
550                 a1 = a4
551                 a1[2] = "hello"
552                 a4[1] = True
553                 c = a2[3]
554
555 ###### output: type_err3
556         .tmp.code:4:12: error: expected number but variable 'c' is string
557         .tmp.code:3:8: info: this is where 'c' was set to string
558         .tmp.code:4:12: error: Arithmetic returns number but string expected
559         .tmp.code:3:8: info: variable 'c' was set as string here.
560         .tmp.code:5:24: error: Boolean operation found where string expected
561         .tmp.code:6:12: error: Comparison returns Boolean but string expected
562         .tmp.code:3:8: info: variable 'c' was set as string here.
563         .tmp.code:7:21: error: Concat returns string but number expected
564         .tmp.code:8:8: error: string cannot be indexed
565         .tmp.code:8:8: error: string cannot be indexed
566         .tmp.code:17:13: error: expected number found string
567         .tmp.code:13:16: error: expected number, found string
568         .tmp.code:20:8: error: cannot assign value of type [5]number
569         .tmp.code:21:13: error: expected [5]number but variable 'a3' is [10]number
570         .tmp.code:19:36: info: this is where 'a3' was set to [10]number
571         .tmp.code:21:8: error: cannot assign value of type [5]number
572         .tmp.code:22:13: error: expected [5]number but variable 'a4' is [5]string
573         .tmp.code:19:51: info: this is where 'a4' was set to [5]string
574         .tmp.code:22:8: error: cannot assign value of type [5]number
575         .tmp.code:23:16: error: expected number found string
576         .tmp.code:24:16: error: expected string found Boolean
577         .tmp.code:25:12: error: have number but need string
578         .tmp.code:3:8: info: variable 'c' was set as string here.
579         oceani: type error in program - not running.
580
581
582 ## Test erroneous command line args
583
584 To improve coverage, we want to test correct handling of strange command
585 line arguments.  These tests won't use code, so the exiting test types
586 won't work.  So we need to be able to explicitly give the command line,
587 and the expected output, and have that tested and the coverage assessed.
588 Rather than having to spell out the whole command name, just give "cmd",
589 and discard that.  Requiring but discarding the command make an empty
590 command list possible.
591
592 ###### test code
593         @for t in $(oceani_special_tests); do \
594             echo -n "Test $$t ... ";\
595             i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
596             ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
597             ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
598             if ! cmp -s .tmp.want .tmp.have; then \
599                echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
600             echo  "passed"; \
601             ./coverage_oceani $${1+"$$@"} > /dev/null 2>&1 ;\
602         done || true
603
604
605 ###### test list
606         oceani_special_tests += "cmd"
607         oceani_special_tests += "cmd,-zyx"
608         oceani_special_tests += "cmd,nofile"
609         oceani_special_tests += "cmd,/dev/null"
610         oceani_special_tests += "cmd,--section,toast:nothing,oceani-tests.mdc"
611
612 ###### output: cmd
613         oceani: no input file given
614
615 ###### output: cmd,-zyx
616         ./oceani: invalid option -- 'z'
617         Usage: oceani --trace --print --noexec --brackets--section=SectionName prog.ocn
618
619 ###### output: cmd,nofile
620         oceani: cannot open nofile
621
622 ###### output: cmd,/dev/null
623         oceani: could not find any code in /dev/null
624
625 ###### output: cmd,--section,toast:nothing,oceani-tests.mdc
626         oceani: cannot find section toast:nothing
627