]> ocean-lang.org Git - ocean/blob - csrc/oceani-tests.mdc
oceani: factor out field reference into per-type handler
[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         oceani_valg_tests := $(oceani_tests)
39         ## combine test lists
40
41         tests:: oceani_test_suite
42         oceani_test_suite: oceani coverage_oceani
43                 @echo -n Checking grammar ...
44                 @./parsergen --report --LALR --tag Parser oceani.mdc | grep " - no conflicts" > /dev/null || \
45                     { echo "Grammar contains conflicts, please review" ; exit 1; }
46                 @echo ok
47                 @rm -rf coverage; mkdir -p coverage
48                 @cp *.gcno coverage
49                 @for T in $(oceani_tests); do \
50                     echo -n "Test $$T.. "; \
51                     i="$$IFS"; IFS=,; set $$T; IFS="$$i"; t=$$1; shift; \
52                     ./md2c oceani-tests.mdc "output: $$T" | grep -v '^#' > .tmp.want; \
53                     ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > .tmp.have; \
54                     if ! cmp -s .tmp.want .tmp.have; then \
55                        echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
56                     echo -n "printing.. "; \
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 || exit 1;\
60                     if ! cmp -s .tmp.code1 .tmp.code2; then \
61                        echo " Failed"; diff -u .tmp.code1 .tmp.code2; exit 1 ; fi ; \
62                     echo -n "extra-newlines.. "; \
63                     sed -e 'i\
64                     ' .tmp.code1 > .tmp.code1a; \
65                     echo '``````' > .tmp.code2a ;\
66                     ./oceani --noexec --print .tmp.code1a >> .tmp.code2a || exit 1;\
67                     if ! cmp -s .tmp.code1 .tmp.code2a; then \
68                        echo " Failed"; diff -u .tmp.code1 .tmp.code2a; exit 1; fi ; \
69                     echo -n "exec-after-print.. "; \
70                     ./oceani .tmp.code1 $${1+"$$@"} > .tmp.have ; \
71                     if ! cmp -s .tmp.want .tmp.have; then \
72                        echo " FAILED"; diff -u .tmp.want .tmp.have; exit 1;fi; \
73                     echo " all passed"; \
74                     ./coverage_oceani --print --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > /dev/null ; \
75                     ./coverage_oceani -tpbn --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1; \
76                 done
77
78                 ## test code
79
80                 @for i in coverage/#*.gcda; do mv $$i coverage/$${i##*#}; done
81                 @gcov -o coverage oceani.mdc > /dev/null 2> /dev/null
82                 @mv *.gcov coverage ; [ -f .gcov ] && mv .gcov coverage || true
83                 @rm -f .tmp*
84
85                 @[ -n "$$SKIP_VALGRIND" ] || for T in $(oceani_valg_tests); do \
86                     echo -n "Valgrind $$T.. "; \
87                     i="$$IFS"; IFS=,; set $$T; IFS="$$i"; t=$$1; shift; \
88                     if valgrind --error-exitcode=42 --log-file=.tmp.valg ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} \
89                          > /dev/null 2>&1 ; [ $$? -eq 42 ] ; then \
90                        echo "FAILED"; cat .tmp.valg; exit 1; fi ; \
91                     if grep 'LEAK SUMMARY' .tmp.valg > /dev/null; then \
92                        echo "valgrind found LEAKS"; cat .tmp.valg ; exit 1 ; fi; \
93                     if grep 'in use at exit [1-9]' .tmp.valg > /dev/null; then \
94                        echo "valgrind found memory in use at exit"; cat .tmp.valg ; exit 1 ; fi; \
95                     echo " passed"; \
96                 done
97                 ## valgrind test code
98                 @[ -n "$$SKIP_COVERAGE_CHECK" ] || awk '/NOTEST/ { next } /^ *[1-9]/ {ran+=1} /^ *###/ {skip+=1} \
99                     END {printf "coverage: %6.2f%%\n", ran * 100 / (ran + skip); \
100                          if (ran < (ran + skip) *0.972) exit(1) }' \
101                         coverage/oceani.mdc.gcov
102
103         coverage_oceani: oceani.c
104                 $(CC) $(CFLAGS) --coverage -fprofile-dir=coverage -o coverage_oceani oceani.c $(LDLIBS)
105
106 ## Values and variables
107
108 The first test stores values in variables and performs various
109 calculations on them.
110
111 ###### test list
112        oceani_tests += "valvar"
113
114 ###### test: valvar
115
116         func main(argv:[argc::]string)
117                 a := 23; b:=12 ; b1 := -b
118                 print a, b, a+b, a-b, a*b, a/b, a%b
119                 print a<b, a<=b, a>b, a>=b, a<a, a==b, a==a
120                 print +a, +b, +b1, -a, -b, -b1
121                 x := True; y := False
122                 print x and y, x or y, x and x, y or y, x and not x, x < y
123
124                 c ::= "This is a string"
125                 d ::= " field theory"
126                 print c, d, c++d
127
128                 aconst :: string = "unchanging"
129
130                 // Check wrapping
131                 print
132                   a + b
133                   + (a*2)
134                   + b1
135                   + b
136
137                 z:Boolean= True
138                 w:Boolean = False
139                 if ?z:
140                         print "w??z", w??z
141                         print "z??w", z??w
142                         print "z??False", z??False, "w??False", w??False
143                 if ?w:
144                         print "Weird?"
145
146 ###### output: valvar
147
148         23 12 35 11 276 1.916666667 11
149         False False True True False False True
150         23 12 12 -23 -12 12
151         False True True False False False
152         This is a string  field theory This is a string field theory
153         81
154         w??z True
155         z??w True
156         z??False True w??False False
157
158 Next we change the value of variables
159
160 ###### test list
161        oceani_tests += "setvar"
162
163 ###### test: setvar
164
165         func main()
166                 a := 4
167                 a = a * a
168                 a = (a + a) * (a + a)
169                 a = a * a * a
170                 print a, a/a
171
172 ###### output: setvar
173         1073741824 1
174
175 Now some contants
176
177 ###### test list
178         oceani_tests += "consts"
179
180 ###### test: consts
181         const
182                 tau ::= pi * 2
183                 pi ::= 3.141 592 653
184                 four ::= 2 + 2 ; five ::= 10/2
185         const pie ::= "I like Pie";
186                 cake ::= "The cake is"
187                   ++ " a lie"
188
189         func main()
190                 print "Hello World, what lovely oceans you have!"
191                 print "are there", five, "?"
192                 print pi, pie, "but", cake, "Tau is", tau
193
194 ###### output: consts
195         Hello World, what lovely oceans you have!
196         are there 5 ?
197         3.141592653 I like Pie but The cake is a lie Tau is 6.283185306
198
199 Test merging of variables from multiple cases
200
201 ###### test list
202         oceani_tests += varmerge
203
204 ###### test: varmerge
205
206         func main()
207                 for i:=0; then i=i+1; while i < 5:
208                         switch i:
209                                 case 0: num:="zero"
210                                 case 1: scratch:=42; num:="one"
211                                 case 2: num:="two"
212                                 case 3: num:="three"
213                                 else    num:="many"
214                         print num,", ",
215                 print
216
217                 for i:=0; then i=i+1; while i < 5:
218                         switch i:
219                                 case 0: num:="zero"
220                                 case 1: num:="one"
221                                 case 2: num:="two"
222                                 case 3: num:="three"
223                                 else    num:="many"
224                         // re-declare a CondScope variable
225                         num := i*i
226                         print num,", ",
227                 print
228
229 ###### output: varmerge
230         zero , one , two , three , many , 
231         0 , 1 , 4 , 9 , 16 , 
232
233 ## Conditions and Loops
234
235 Now we need to test if/else and some different loops
236
237 ###### test list
238        oceani_tests += cond_loop
239
240 ###### test: cond_loop
241
242         func main()
243                 a := 4
244                 if a < 5:
245                         print "Success"
246                 else
247                         print "Failure"
248                 for b:=1; then b=b+b; while b < 100:
249                         print '', b,
250                 print
251                 // Newtons method for square root of 2
252                 target ::= 2
253                 guess := target
254                 for
255                         count: number = 0
256                 while
257                         current := guess * guess
258                         use +(current - target) > 0.000000001
259                 do
260                         guess = (guess + (target / guess) ) / 2
261                         print count, guess
262                         count = count + 1
263                 print "error is ", target - guess * guess
264
265                 for j:=0; then j = j+3 ; while j < 10:
266                         if j != 0 and then 20 / j > 3:
267                                 print "20 /", j," =", 20 / j
268                         else
269                                 print "I won't calculate 20 /", j
270                 pi ::= 3.1415926535897
271                 if 355/113 == pi or else +(pi - 355/113) < 0.001:
272                         print "Close enough"
273                 print "lower" if 355/113 < pi else "higher"
274
275                 if pi > 3 then print "pi exceeds three"; else print "need more pie"
276                 if (pi < 3) { print "not enough pi" } else { print "pi sufficient" }
277                 for { i := 0; sum := 0 }
278                 then { i = i+1 }
279                 while i <= 10:
280                         sum = sum + i
281                 else
282                         pass
283                         print "sum 1..10 is", sum
284                 if
285                         PI1 := 22/7
286                         use PI1 < pi
287                 then
288                         print "Smaller"
289                 else
290                         print 'larger'
291
292 ###### output: cond_loop
293         Success
294          1 2 4 8 16 32 64
295         0 1.5
296         1 1.416666667
297         2 1.414215686
298         3 1.414213562
299         error is  -4.510950445e-12
300         I won't calculate 20 / 0
301         20 / 3  = 6.666666667
302         20 / 6  = 3.333333333
303         I won't calculate 20 / 9
304         Close enough
305         higher
306         pi exceeds three
307         pi sufficient
308         sum 1..10 is 55
309         larger
310
311 ## Say Hello
312
313 The demonstration code presented in the interpreted is suitable for the test suite.
314 Here I break it into two parts, keeping the array code separate.
315
316 ###### test list
317         oceani_tests += "sayhello,55,33,hello,True"
318         oceani_tests += "sayhello,12,60,there,False"
319
320 ###### test: sayhello
321
322         func main(av:[ac::number]string)
323                 A := $av[1]; B := $av[2]
324                 astr := av[3]
325                 bbool := av[ac-1] == "True"
326                 print "Hello World, what lovely oceans you have!"
327                 /* When a variable is defined in both branches of an 'if',
328                  * and used afterwards, the variables are merged.
329                  */
330                 if A > B:
331                         bigger := "yes"
332                 else
333                         bigger := "no"
334                 print "Is", A, "bigger than", B,"? ", bigger
335                 /* If a variable is not used after the 'if', no
336                  * merge happens, so types can be different
337                  */
338                 if A > B * 2:
339                         double:string = "yes"
340                         print A, "is more than twice", B, "?", double
341                 else
342                         double := B*2
343                         print "double", B, "is", double
344
345                 a : number
346                 a = A;
347                 b:number = B
348                 print "still", bigger // check for regression in scoping
349                 if a > 0 and b > 0:
350                         while a != b:
351                                 if a < b:
352                                         b = b - a
353                                 else
354                                         a = a - b
355                         print "GCD of", A, "and", B,"is", a
356                 else if a <= 0:
357                         print a, "is not positive, cannot calculate GCD"
358                 else
359                         print b, "is not positive, cannot calculate GCD"
360
361                 for
362                         togo := 10
363                         f1 := 1; f2 := 1
364                         print "Fibonacci:", f1,f2,
365                 then togo = togo - 1
366                 while togo > 0:
367                         f3 := f1 + f2
368                         print "", f3,
369                         f1 = f2
370                         f2 = f3
371                 print ""
372
373                 if bbool:
374                         print astr ++ " was the str"
375                 else
376                         print "I found the str over " ++ astr
377
378                 /* Binary search... */
379                 for
380                         lo:= 0; hi := 100
381                         target := 77
382                 while
383                         mid := (lo + hi) / 2
384                         if mid == target:
385                                 use Found
386                         if mid < target:
387                                 lo = mid
388                         else
389                                 hi = mid
390                         if hi - lo < 1:
391                                 lo = mid
392                                 use GiveUp
393                         use True
394                 do pass
395                 case Found:
396                         print "Yay, I found", target
397                 case GiveUp:
398                         print "Closest I found was", lo
399
400 ###### output: sayhello,55,33,hello,True
401         Hello World, what lovely oceans you have!
402         Is 55 bigger than 33 ?  yes
403         double 33 is 66
404         still yes
405         GCD of 55 and 33 is 11
406         Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
407         hello was the str
408         Closest I found was 77.34375
409
410 ###### output: sayhello,12,60,there,False
411         Hello World, what lovely oceans you have!
412         Is 12 bigger than 60 ?  no
413         double 60 is 120
414         still no
415         GCD of 12 and 60 is 12
416         Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
417         I found the str over there
418         Closest I found was 77.34375
419
420 ###### test list
421         oceani_tests += "insert_sort"
422 ###### test: insert_sort
423         func main()
424                 size::=55
425                 list:[size]number
426                 list[0] = 1_234
427                 for i:=1; then i = i + 1; while i < size:
428                         n := list[i-1] * list[i-1]
429                         list[i] = (n / 100) % 10000
430
431                 print "Before sort:"
432                 for i:=0; then i = i + 1; while i < size:
433                         print "list[",i,"]=",list[i]
434
435                 for i := 1; then i=i+1; while i < size:
436                         for j:=i-1; then j=j-1; while j >= 0:
437                                 if list[j] > list[j+1]:
438                                         t:= list[j]
439                                         list[j] = list[j+1]
440                                         list[j+1] = t
441                 print "After sort:"
442                 for i:=0; then i = i + 1; while i < size:
443                         print "list[",i,"]=",list[i]
444
445 ###### output: insert_sort
446         Before sort:
447         list[ 0 ]= 1234
448         list[ 1 ]= 5227
449         list[ 2 ]= 3215
450         list[ 3 ]= 3362
451         list[ 4 ]= 3030
452         list[ 5 ]= 1809
453         list[ 6 ]= 2724
454         list[ 7 ]= 4201
455         list[ 8 ]= 6484
456         list[ 9 ]= 422
457         list[ 10 ]= 1780
458         list[ 11 ]= 1684
459         list[ 12 ]= 8358
460         list[ 13 ]= 8561
461         list[ 14 ]= 2907
462         list[ 15 ]= 4506
463         list[ 16 ]= 3040
464         list[ 17 ]= 2416
465         list[ 18 ]= 8370
466         list[ 19 ]= 569
467         list[ 20 ]= 3237
468         list[ 21 ]= 4781
469         list[ 22 ]= 8579
470         list[ 23 ]= 5992
471         list[ 24 ]= 9040
472         list[ 25 ]= 7216
473         list[ 26 ]= 706
474         list[ 27 ]= 4984
475         list[ 28 ]= 8402
476         list[ 29 ]= 5936
477         list[ 30 ]= 2360
478         list[ 31 ]= 5696
479         list[ 32 ]= 4444
480         list[ 33 ]= 7491
481         list[ 34 ]= 1150
482         list[ 35 ]= 3225
483         list[ 36 ]= 4006
484         list[ 37 ]= 480
485         list[ 38 ]= 2304
486         list[ 39 ]= 3084
487         list[ 40 ]= 5110
488         list[ 41 ]= 1121
489         list[ 42 ]= 2566
490         list[ 43 ]= 5843
491         list[ 44 ]= 1406
492         list[ 45 ]= 9768
493         list[ 46 ]= 4138
494         list[ 47 ]= 1230
495         list[ 48 ]= 5129
496         list[ 49 ]= 3066
497         list[ 50 ]= 4003
498         list[ 51 ]= 240
499         list[ 52 ]= 576
500         list[ 53 ]= 3317
501         list[ 54 ]= 24
502         After sort:
503         list[ 0 ]= 24
504         list[ 1 ]= 240
505         list[ 2 ]= 422
506         list[ 3 ]= 480
507         list[ 4 ]= 569
508         list[ 5 ]= 576
509         list[ 6 ]= 706
510         list[ 7 ]= 1121
511         list[ 8 ]= 1150
512         list[ 9 ]= 1230
513         list[ 10 ]= 1234
514         list[ 11 ]= 1406
515         list[ 12 ]= 1684
516         list[ 13 ]= 1780
517         list[ 14 ]= 1809
518         list[ 15 ]= 2304
519         list[ 16 ]= 2360
520         list[ 17 ]= 2416
521         list[ 18 ]= 2566
522         list[ 19 ]= 2724
523         list[ 20 ]= 2907
524         list[ 21 ]= 3030
525         list[ 22 ]= 3040
526         list[ 23 ]= 3066
527         list[ 24 ]= 3084
528         list[ 25 ]= 3215
529         list[ 26 ]= 3225
530         list[ 27 ]= 3237
531         list[ 28 ]= 3317
532         list[ 29 ]= 3362
533         list[ 30 ]= 4003
534         list[ 31 ]= 4006
535         list[ 32 ]= 4138
536         list[ 33 ]= 4201
537         list[ 34 ]= 4444
538         list[ 35 ]= 4506
539         list[ 36 ]= 4781
540         list[ 37 ]= 4984
541         list[ 38 ]= 5110
542         list[ 39 ]= 5129
543         list[ 40 ]= 5227
544         list[ 41 ]= 5696
545         list[ 42 ]= 5843
546         list[ 43 ]= 5936
547         list[ 44 ]= 5992
548         list[ 45 ]= 6484
549         list[ 46 ]= 7216
550         list[ 47 ]= 7491
551         list[ 48 ]= 8358
552         list[ 49 ]= 8370
553         list[ 50 ]= 8402
554         list[ 51 ]= 8561
555         list[ 52 ]= 8579
556         list[ 53 ]= 9040
557         list[ 54 ]= 9768
558
559 ## Arrays
560
561 We already have some array tests, but this is where we put other
562 ad-hoc things array related.
563
564 ###### test list
565         oceani_tests += arrays
566
567 ###### test: arrays
568
569         func main()
570                 bools:[5]Boolean
571                 strings:[4]string
572
573                 bools[3] = strings[1] == "Hello"
574                 bools[1] = strings[2] <= "there"
575
576                 for i:=0; then i=i+1; while i<5:
577                         j ::= i
578                         ra:[j]number
579                         ra[i-1] = i*i
580                         ra[6] = 42 // mustn't crash
581                         print '', bools[i], ra[j-1],
582                 print
583
584 ###### output: arrays
585          False 0 True 1 False 4 False 9 False 16
586
587 ## Structures
588
589 Time to test if structure declarations and accesses work correctly.
590
591 ###### test list
592         oceani_tests += structs
593
594 ###### test: structs
595
596         const three ::= 3
597         struct foo
598                 size:[three]number
599                 name:string
600                 thing:baz
601                 active:Boolean = True
602
603         struct baz { a:number; b:Boolean; }
604
605         func main
606         do
607                 info:[4]foo
608
609                 for i:=0; then i=i+1; while i < 4:
610                         switch i
611                         case 2: nm:= "peter"
612                         case 0: nm:= "bob"
613                         case 1: nm:= "jane"
614                         else    nm:= "janine"
615
616                         info[i].name = nm
617                         info[i].size[0] = i*i
618                         if nm != "jane":
619                                 info[i].active = False
620
621                 for i:=0; then i=i+1; while i < 4:
622                         print info[i].name, info[i].active, info[i].size[0]
623                 info[0].thing.b = True
624
625 ###### output: structs
626
627         bob False 0
628         jane True 1
629         peter False 4
630         janine False 9
631
632 ## Functions
633
634 Test functions.  They don't return anything, so we need to get them to print
635
636 ###### test list
637         oceani_tests += functions func_ret_type
638
639 ###### test: functions
640
641         func test1
642                 t: Boolean
643         do
644                 if t:
645                         print "true"
646
647         func noarg
648         do
649                 pass
650
651         func twoarg
652                 a:number
653                 b:string
654         do
655                 while a > 0:
656                         print b
657                         a = a - 1
658
659         func test(n:number; s:string)
660                 if n >= 1:
661                         print n,s,
662                         test(n-1, "."++s)
663                 else
664                         print "done"
665
666         func to_polar
667                 x:number; y:number
668         return
669                 rho:number
670                 theta:number
671         do
672                 rho = x + y
673                 theta = x - y
674
675         func main()
676                 for i:=0; then i = i + 1; while i < 5:
677                         test(i, " ")
678                 angular := to_polar(32, 23)
679                 print angular.rho, angular.theta
680
681 ###### output: functions
682         done
683         1  done
684         2  1 . done
685         3  2 . 1 .. done
686         4  3 . 2 .. 1 ... done
687         55 9
688
689 ###### test: func_ret_type
690
691         func double(n:number):number
692                 use n+n
693
694         func answer
695                 prefix:string
696                 suffix:string
697         return string
698         do
699                 use prefix ++ suffix
700
701         func noarg_returns
702         return Boolean
703         do
704                 use 22/7 == 3.14159
705
706         func main()
707                 for j:=10; then j = j - 3; while j > -5:
708                         print answer("dou","ble"), j, "is", double(j)
709
710 ###### output: func_ret_type
711         double 10 is 20
712         double 7 is 14
713         double 4 is 8
714         double 1 is 2
715         double -2 is -4
716
717 ## References
718
719 A simple linked list example
720
721 ###### test list
722         oceani_tests += "linked_list,one,two,three,four"
723
724 ###### test: linked_list
725
726         struct node
727                 next: @node
728                 this: string
729
730         func insert(list:@node; new:string):@node
731                 p:=list
732                 prev := @nil
733                 while ?p and then p@.this < new:
734                         prev = p
735                         p = p@.next
736                 if ?prev:
737                         prev@.next = @new()
738                         prev@.next@.next = p
739                         prev@.next@.this = new
740                 else
741                         list = @new()
742                         list@.next = p
743                         list@.this = new
744                 use list
745
746         func printlist(list:@node)
747                 while ?list:
748                         print list@.this
749                         list = list@.next
750
751         func freelist(list:@node)
752                 if list != @nil:
753                         freelist(list@.next)
754                         @free = list
755
756         func main(argv:[ac::]string)
757                 list := insert(@nil, "@start")
758                 list = insert(list, "~end")
759                 for i:=1; then i=i+1; while i < ac:
760                         list = insert(list, argv[i])
761                 list = insert(list, "Hello!")
762                 printlist(list)
763                 freelist(list)
764
765 ###### output: linked_list,one,two,three,four
766         @start
767         Hello!
768         four
769         one
770         three
771         two
772         ~end
773
774 ## Test code with syntax errors
775
776 Syntax errors aren't handled well yet - the result is almost always a
777 single message about the first error.  So this section will be fairly
778 thin until we add proper parsing recovery in the face of common errors.
779
780 A special case of syntax errors is token errors, when a token is only
781 accepted because the parser doesn't know quite enough to reject it.
782 There are handled better as they are quite local, so a single test
783 program can trigger most of the possible errors.
784
785 To handle erronous code, we need a different set of tests, as we need to
786 capture `stderr`. The same test code will be used for type errors too.
787 As error messages contain the line number, and we don't want changes to
788 this file to change the reported numbers, we copy the code into a
789 separate file first, then run from there.
790
791 ###### test code
792         @for t in $(oceani_failing_tests); do \
793             echo -n "Test $$t ... "; \
794             ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
795             echo '``````' > .tmp.code; \
796             ./md2c oceani-tests.mdc "test: $$t" | grep -v '^#' >> .tmp.code; \
797             ./oceani .tmp.code > .tmp.have 2>&1; \
798             if ! cmp -s .tmp.want .tmp.have; then \
799                echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
800             echo  "passed"; \
801             ./coverage_oceani --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1 ;\
802         done || true
803
804 ###### combine test lists
805         oceani_valg_tests += $(oceani_failing_tests)
806
807 ###### test list
808         oceani_failing_tests := syn1
809         oceani_failing_tests += tokerr
810
811 ###### test: syn1
812
813         func main()
814                 if then else while do
815
816 ###### output: syn1
817         .tmp.code:3:11: Syntax error in statement: then
818
819 ###### test: tokerr
820         func main()
821                 a := 1i  // imaginary numbers aren't understood
822                 b:[2i]number // array sizes are handled separately
823                 c:[3.14159]Boolean // array sizes must be integers
824                 d:[1_000_000_000_000]number // they mustn't be huge
825                 patn: string = "foo[ ,_]*bar"re // regexp strings are just a dream
826
827                 multi := """
828                 This is a multiline string
829                 With an unsupportable suffix
830                 """Aa
831
832                 xx:unknown = 24
833                 yy:[unknowable]number
834                 zzsize := 4
835                 zz:[zzsize]string // size must be constant, use ::=
836
837                 // These numbers should be bad in all contexts: FIXME
838                 aa:[00123]number
839
840 ###### output: tokerr
841         .tmp.code:3:13: error: unsupported number suffix: 1i
842         .tmp.code:4:11: error: unsupported number suffix: 2i
843         .tmp.code:5:11: error: array size must be an integer: 3.14159
844         .tmp.code:6:11: error: array size is too large: 1_000_000_000_000
845         .tmp.code:7:23: error: unsupported string suffix: "foo[ ,_]*bar"re
846         .tmp.code:9:17: error: unsupported string suffix: """
847                 This is a multiline string
848                 With an unsupportable suffix
849                 """Aa
850         .tmp.code:15:12: error: name undeclared: unknowable
851         .tmp.code:17:12: error: array size must be a constant: zzsize
852         .tmp.code:20:12: error: unrecognised number: 00123
853         .tmp.code:14:11: error: type used but not declared: unknown
854
855 ## Tests for type errors
856
857 Type error don't cause parsing to abort, so we can fit many in the
858 one test program.  Some type errors are found during the parse, others
859 during type analysis which doesn't run if parsing failed.  So we cannot
860 fit everything in one.
861
862 These programs were generated by looking for the
863 various places that `type_err()` are called.
864
865 ###### test list
866         oceani_failing_tests += type_err1 type_err2 type_err3 type_err4 type_err5 type_err6
867
868 ###### test: type_err1
869
870         func main()
871                 print "hello" ++ 5, 5 ++ "hello"
872
873                 b ::= 3
874                 b = b + 1
875
876                 if 3 * 4 and not True: print "Weird"
877
878 ###### output: type_err1
879         .tmp.code:3:25: error: expected string found number
880         .tmp.code:3:28: error: expected string found number
881         .tmp.code:6:8: error: Cannot assign to a constant: b
882         .tmp.code:5:8: info: name was defined as a constant here
883         .tmp.code:6:8: error: Cannot assign to a constant: b
884         .tmp.code:5:8: info: name was defined as a constant here
885         .tmp.code:8:11: error: Arithmetic returns number but Boolean expected
886         oceani: type error in program - not running.
887
888 ###### test: type_err2
889
890         func main()
891                 a := 1
892                 a := 2
893                 a ::= 3
894                 a:number = 4
895                 a ::number = 5
896                 c:
897
898 ###### output: type_err2
899         .tmp.code:4:8: error: variable 'a' redeclared
900         .tmp.code:3:8: info: this is where 'a' was first declared
901         .tmp.code:5:8: error: variable 'a' redeclared
902         .tmp.code:3:8: info: this is where 'a' was first declared
903         .tmp.code:6:8: error: variable 'a' redeclared
904         .tmp.code:3:8: info: this is where 'a' was first declared
905         .tmp.code:7:8: error: variable 'a' redeclared
906         .tmp.code:3:8: info: this is where 'a' was first declared
907         .tmp.code:8:8: Variable declared with no type or value: c
908
909 ###### test: type_err3
910
911         struct foo
912                 a: number
913                 b:string = "hello"
914
915         func main()
916                 c := "hello"
917                 c = c + 1
918                 c = "hello" ++ (True and False)
919                 c = 4 < 5
920                 print 45 + ( "Hello" ++ "there")
921                 c[5] = 1
922
923                 while
924                         use 1
925                         use True
926                         use "Hello"
927                 do
928                         print
929                 case 1: print "one"
930                 case "Hello": print "Hello"
931
932                 a1:[5]number; a2:[5]number; a3:[10]number; a4:[5]string
933                 a1 = a2
934                 a1 = a3
935                 a1 = a4
936                 a1[2] = "hello"
937                 a4[1] = True
938                 c = a2[3]
939
940                 bar:foo
941                 foo.c = 43
942                 print c.foo
943                 print bar.c
944                 print bar.b + 42
945
946                 // trigger 'labels not permitted' error message
947                 while 1 if True else False:
948                         print
949                 case 2: print "two"
950
951 ###### output: type_err3
952         .tmp.code:8:12: error: expected number but variable 'c' is string
953         .tmp.code:7:8: info: this is where 'c' was set to string
954         .tmp.code:8:12: error: Arithmetic returns number but string expected
955         .tmp.code:7:8: info: variable 'c' was set as string here.
956         .tmp.code:9:24: error: Boolean operation found where string expected
957         .tmp.code:10:12: error: Comparison returns Boolean but string expected
958         .tmp.code:7:8: info: variable 'c' was set as string here.
959         .tmp.code:11:21: error: Concat returns string but number expected
960         .tmp.code:12:8: error: string cannot be indexed
961         .tmp.code:12:8: error: string cannot be indexed
962         .tmp.code:21:13: error: expected number found string
963         .tmp.code:17:16: error: expected number, found string
964         .tmp.code:24:8: error: cannot assign value of type [5]number
965         .tmp.code:25:13: error: expected [5]number but variable 'a3' is [10]number
966         .tmp.code:23:36: info: this is where 'a3' was set to [10]number
967         .tmp.code:25:8: error: cannot assign value of type [5]number
968         .tmp.code:26:13: error: expected [5]number but variable 'a4' is [5]string
969         .tmp.code:23:51: info: this is where 'a4' was set to [5]string
970         .tmp.code:26:8: error: cannot assign value of type [5]number
971         .tmp.code:27:16: error: expected number found string
972         .tmp.code:28:16: error: expected string found Boolean
973         .tmp.code:29:12: error: have number but need string
974         .tmp.code:7:8: info: variable 'c' was set as string here.
975         .tmp.code:32:8: error: variable used but not declared: foo
976         .tmp.code:32:8: error: field reference on none is not supported
977         .tmp.code:32:16: error: expected none found number
978         .tmp.code:33:14: error: field reference on string is not supported
979         .tmp.code:34:14: error: cannot find requested field in foo
980         .tmp.code:35:17: error: have string but need number
981         .tmp.code:38:29: error: expected number (labels not permitted) found Boolean
982         oceani: type error in program - not running.
983
984 ###### test: type_err4
985         func main()
986                 a:=1; b=2; c::=3
987                 print a, b, c
988
989 ###### output: type_err4
990         .tmp.code:3:14: error: variable used but not declared: b
991         .tmp.code:3:16: error: expected none found number
992         .tmp.code:3:14: info: variable 'b' was set as none here.
993         oceani: type error in program - not running.
994
995 ###### test: type_err5
996         struct foo
997                 bar:baz
998                 a:number
999         struct baz
1000                 bat:foo
1001                 b:string
1002         struct foo
1003                 c:number
1004
1005 ###### output: type_err5
1006         .tmp.code:8:7: error: type already declared: foo
1007         .tmp.code:2:7: info: this is location of declartion: foo
1008         .tmp.code:2:7: error: type has recursive definition: foo
1009         .tmp.code:5:7: error: type has recursive definition: baz
1010
1011 ###### test: type_err6
1012
1013         func main()
1014                 a:= "hello"
1015                 if ?a:
1016                         print "no"
1017                 print a ?? "there"
1018
1019 ###### output: type_err6
1020         .tmp.code:4:12: error: '?' requires a testable value, not string
1021         .tmp.code:6:14: error: "??" requires a testable value, not string
1022         oceani: type error in program - not running.
1023
1024
1025 ###### test list
1026         oceani_failing_tests += type_err_const type_err_const1 type_err_const2 missing_program bad_main
1027
1028 ###### test: type_err_const
1029         const
1030                 foo :: number = 45
1031                 bar ::= "string" + 56
1032         const
1033                 bar ::= "baz"
1034         func main()
1035                 foo := 4
1036                 print foo, bar
1037
1038         // trigger duplicate-main error
1039         func main()
1040                 foo := 6
1041                 print bar, foo
1042
1043 ###### output: type_err_const
1044         .tmp.code:6:8: error: name already declared: bar
1045         .tmp.code:4:8: info: this is where 'bar' was first declared
1046         .tmp.code:8:8: error: variable 'foo' redeclared
1047         .tmp.code:3:8: info: this is where 'foo' was first declared
1048         .tmp.code:12:5: error: function 'main' redeclared
1049         .tmp.code:7:5: info: this is where 'main' was first declared
1050         .tmp.code:13:8: error: variable 'foo' redeclared
1051         .tmp.code:3:8: info: this is where 'foo' was first declared
1052         .tmp.code:4:16: error: expected number found string
1053
1054 ###### test: type_err_const1
1055         const
1056                 foo : number = 45
1057                 bar := "string"
1058         func main()
1059                 foo := 4
1060                 print foo, bar
1061
1062 ###### output: type_err_const1
1063         .tmp.code:3:12: Syntax error in constant: :
1064         .tmp.code:4:12: Syntax error in constant: :
1065
1066 ###### test: type_err_const2
1067         const
1068                 four ::= two + two
1069                 two ::= four / 2
1070         
1071 ###### output: type_err_const2
1072         .tmp.code:3:8: error: const four cannot be resolved.
1073         .tmp.code:4:8: error: const two cannot be resolved.
1074
1075 ###### test: missing_program
1076         const
1077                 foo::="bar"
1078
1079 ###### output: missing_program
1080         oceani: no main function found.
1081
1082 ###### test: bad_main
1083         func main(foo:string)
1084                 print foo
1085
1086 ###### output: bad_main
1087         .tmp.code:2:10: error: expected []string but variable 'foo' is string
1088         .tmp.code:2:10: info: this is where 'foo' was set to string
1089         oceani: main has wrong type.
1090
1091 Test for type errors with functions
1092
1093 ###### test list
1094         oceani_failing_tests += func_err_args func_err_redeclare
1095
1096 ###### test: func_err_args
1097
1098         func test1(a:number; b:string; c:[3]Boolean)
1099                 print a, b, c[1]
1100
1101         func test2(a:number; b:string; c:[3]Boolean)
1102                 print a, b, c[1]
1103
1104         func test3()
1105                 # use undefined names
1106                 print a, z
1107
1108         func main()
1109                 truth:[3]Boolean
1110                 truth[1] = True
1111                 test1(1,"hello")
1112                 test1("hello",1)
1113                 test1(1, "two", truth)
1114                 test1(1, 2, truth)
1115                 test1(1, "lo", truth, 4)
1116                 print test(), test1(1,2,3)
1117                 if test1 == test2:
1118                         pass
1119
1120         func test4(a:number):string
1121                 use a * a
1122
1123         func test5(a:number):string
1124                 print a
1125
1126         struct foo
1127                 a: number
1128                 b:string = "hello"
1129
1130         func test6(a:number):foo
1131                 b:foo
1132                 b.a = a
1133                 use b
1134
1135 ###### output: func_err_args
1136         .tmp.code:34:5: error: function cannot return value of type foo
1137         .tmp.code:28:8: error: expected string, found none
1138         .tmp.code:25:8: error: expected string, found number
1139         .tmp.code:15:14: error: insufficient arguments to function.
1140         .tmp.code:16:14: error: expected number found string
1141         .tmp.code:16:22: error: expected string found number
1142         .tmp.code:16:14: error: insufficient arguments to function.
1143         .tmp.code:18:17: error: expected string found number
1144         .tmp.code:19:14: error: too many arguments to function.
1145         .tmp.code:20:14: error: attempt to call a non-function.
1146         .tmp.code:20:32: error: expected string found number
1147         .tmp.code:20:28: error: insufficient arguments to function.
1148         .tmp.code:21:20: error: expected "func test1" but variable 'test2' is "func test2"
1149         .tmp.code:5:5: info: this is where 'test2' was set to "func test2"
1150         .tmp.code:10:14: error: variable used but not declared: a
1151         .tmp.code:10:17: error: variable used but not declared: z
1152         oceani: type error in program - not running.
1153
1154 ###### test: func_err_redeclare
1155
1156         func test1(a:number; b:string; c:[3]Boolean)
1157                 print a, b, c[1]
1158
1159         func test1
1160         do
1161                 pass
1162
1163         func test1
1164                 b:Boolean
1165         do
1166                 pass
1167
1168 ###### output: func_err_redeclare
1169         .tmp.code:5:5: error: function 'test1' redeclared
1170         .tmp.code:2:5: info: this is where 'test1' was first declared
1171         .tmp.code:9:5: error: function 'test1' redeclared
1172         .tmp.code:2:5: info: this is where 'test1' was first declared
1173
1174 Test for errors with references
1175
1176 ###### test list
1177         oceani_failing_tests += ref_err1 ref_err2
1178
1179 ###### test: ref_err1
1180         func main()
1181                 ref:@number
1182                 @foo = ref
1183                 ref = @old()
1184                 if ref == @null:
1185                         print "null"
1186
1187 ###### output: ref_err1
1188         .tmp.code:4:9: error: only "@free" makes sense here: foo
1189         .tmp.code:5:15: error: Only reference function is "@new()": old
1190         .tmp.code:6:19: error: Only reference value is "@nil": null
1191
1192 ###### test: ref_err2
1193         func main()
1194                 ref:@number
1195                 ref2:@string
1196                 num:number = @new()
1197                 print num@
1198                 if num == @nil or ref == ref2 or ref == 2:
1199                         @free = num
1200
1201 ###### output: ref_err2
1202         .tmp.code:5:22: error: @new() can only be used with references, not number
1203         .tmp.code:5:8: info: variable 'num' was set as number here.
1204         .tmp.code:6:14: error: Cannot dereference number
1205         .tmp.code:7:19: error: @nil can only be used with reference, not number
1206         .tmp.code:7:33: error: expected @number but variable 'ref2' is @string
1207         .tmp.code:4:8: info: this is where 'ref2' was set to @string
1208         .tmp.code:7:48: error: expected @number found number
1209         .tmp.code:8:17: error: @free can only be assigned a reference, not number
1210         .tmp.code:8:17: error: @free can only be assigned a reference, not number
1211         oceani: type error in program - not running.
1212
1213 ## Test erroneous command line args
1214
1215 To improve coverage, we want to test correct handling of strange command
1216 line arguments.  These tests won't use code, so the exiting test types
1217 won't work.  So we need to be able to explicitly give the command line,
1218 and the expected output, and have that tested and the coverage assessed.
1219 Rather than having to spell out the whole command name, just give "cmd",
1220 and discard that.  Requiring but discarding the command make an empty
1221 command list possible.
1222
1223 ###### test code
1224         @for t in $(oceani_special_tests); do \
1225             echo -n "Test $$t ... ";\
1226             i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
1227             ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
1228             ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
1229             if ! cmp -s .tmp.want .tmp.have; then \
1230                echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
1231             echo  "passed"; \
1232             ./coverage_oceani $${1+"$$@"} > /dev/null 2>&1 ;\
1233         done || true
1234 ###### valgrind test code
1235         @[ -n "$$SKIP_VALGRIND" ] || for t in $(oceani_special_tests); do\
1236             echo -n "Valgrind $$t.. "; \
1237             i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
1238             if valgrind --error-exitcode=42 --log-file=.tmp.valg ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
1239                [ $$? -eq 42 ]; then \
1240                        echo "FAILED"; cat .tmp.valg; exit 1; fi ; \
1241             if grep 'LEAK SUMMARY' .tmp.valg > /dev/null; then \
1242                echo "valgrind found LEAKS"; cat .tmp.valg ; exit 1 ; fi; \
1243             if grep 'in use at exit [1-9]' .tmp.valg > /dev/null; then \
1244                echo "valgrind found memory in use at exit"; cat .tmp.valg ; exit 1 ; fi; \
1245             echo " passed"; \
1246         done
1247
1248 ###### test list
1249         oceani_special_tests += "cmd"
1250         oceani_special_tests += "cmd,-zyx"
1251         oceani_special_tests += "cmd,nofile"
1252         oceani_special_tests += "cmd,/dev/null"
1253         oceani_special_tests += "cmd,--section,toast:nothing,oceani-tests.mdc"
1254
1255 ###### output: cmd
1256         oceani: no input file given
1257
1258 ###### output: cmd,-zyx
1259         ./oceani: invalid option -- 'z'
1260         Usage: oceani --trace --print --noexec --brackets --section=SectionName prog.ocn
1261
1262 ###### output: cmd,nofile
1263         oceani: cannot open nofile
1264
1265 ###### output: cmd,/dev/null
1266         oceani: could not find any code in /dev/null
1267
1268 ###### output: cmd,--section,toast:nothing,oceani-tests.mdc
1269         oceani: cannot find section toast:nothing