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