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