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