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