]> ocean-lang.org Git - ocean/blob - csrc/oceani-tests.mdc
1a36534abf4ba13b6079e4f80ecc22132ecc1b46
[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                 (d + b) = 12
879
880 ###### output: type_err1
881         .tmp.code:3:25: error: expected string found number
882         .tmp.code:3:28: error: expected string found number
883         .tmp.code:6:8: error: Cannot assign to a constant: b
884         .tmp.code:5:8: info: name was defined as a constant here
885         .tmp.code:8:11: error: Arithmetic returns number but Boolean expected
886         .tmp.code:9:20: error: expected number found label
887         .tmp.code:9:8: info: variable 'd' was set as number here.
888         .tmp.code:10:8: error: cannot assign to an rval
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:23:8: info: variable 'a1' was set as [5]number here.
971         .tmp.code:25:8: error: cannot assign value of type [5]number
972         .tmp.code:26:13: error: expected [5]number but variable 'a4' is [5]string
973         .tmp.code:23:51: info: this is where 'a4' was set to [5]string
974         .tmp.code:23:8: info: variable 'a1' was set as [5]number here.
975         .tmp.code:26:8: error: cannot assign value of type [5]number
976         .tmp.code:27:16: error: expected number found string
977         .tmp.code:28:16: error: expected string found Boolean
978         .tmp.code:29:12: error: have number but need string
979         .tmp.code:7:8: info: variable 'c' was set as string here.
980         .tmp.code:32:8: error: variable used but not declared: foo
981         .tmp.code:32:8: error: field reference on none is not supported
982         .tmp.code:32:16: error: expected none found number
983         .tmp.code:33:14: error: field reference on string is not supported
984         .tmp.code:34:14: error: cannot find requested field in foo
985         .tmp.code:35:17: error: have string but need number
986         .tmp.code:38:29: error: expected number found Boolean
987         oceani: type error in program - not running.
988
989 ###### test: type_err4
990         func main()
991                 a:=1; b=2; c::=3
992                 print a, b, c
993
994 ###### output: type_err4
995         .tmp.code:3:14: error: variable used but not declared: b
996         .tmp.code:3:16: error: expected none found number
997         .tmp.code:3:14: info: variable 'b' was set as none here.
998         oceani: type error in program - not running.
999
1000 ###### test: type_err5
1001         struct foo
1002                 bar:baz
1003                 a:number
1004         struct baz
1005                 bat:foo
1006                 b:string
1007         struct foo
1008                 c:number
1009
1010 ###### output: type_err5
1011         .tmp.code:8:7: error: type already declared: foo
1012         .tmp.code:2:7: info: this is location of declartion: foo
1013         .tmp.code:2:7: error: type has recursive definition: foo
1014         .tmp.code:5:7: error: type has recursive definition: baz
1015
1016 ###### test: type_err6
1017
1018         func main()
1019                 a:= "hello"
1020                 if ?a:
1021                         print "no"
1022                 print a ?? "there"
1023
1024 ###### output: type_err6
1025         .tmp.code:4:12: error: '?' requires a testable value, not string
1026         .tmp.code:6:14: error: "??" requires a testable value, not string
1027         oceani: type error in program - not running.
1028
1029
1030 ###### test list
1031         oceani_failing_tests += type_err_const type_err_const1 type_err_const2 missing_program bad_main
1032
1033 ###### test: type_err_const
1034         const
1035                 foo :: number = 45
1036                 bar ::= "string" + 56
1037         const
1038                 bar ::= "baz"
1039         func main()
1040                 foo := 4
1041                 print foo, bar
1042
1043         // trigger duplicate-main error
1044         func main()
1045                 foo := 6
1046                 print bar, foo
1047
1048 ###### output: type_err_const
1049         .tmp.code:6:8: error: name already declared: bar
1050         .tmp.code:4:8: info: this is where 'bar' was first declared
1051         .tmp.code:8:8: error: variable 'foo' redeclared
1052         .tmp.code:3:8: info: this is where 'foo' was first declared
1053         .tmp.code:12:5: error: function 'main' redeclared
1054         .tmp.code:7:5: info: this is where 'main' was first declared
1055         .tmp.code:13:8: error: variable 'foo' redeclared
1056         .tmp.code:3:8: info: this is where 'foo' was first declared
1057         .tmp.code:4:16: error: expected number found string
1058
1059 ###### test: type_err_const1
1060         const
1061                 foo : number = 45
1062                 bar := "string"
1063         func main()
1064                 foo := 4
1065                 print foo, bar
1066
1067 ###### output: type_err_const1
1068         .tmp.code:3:12: Syntax error in constant: :
1069         .tmp.code:4:12: Syntax error in constant: :
1070
1071 ###### test: type_err_const2
1072         const
1073                 four ::= two + two
1074                 two ::= four / 2
1075         
1076 ###### output: type_err_const2
1077         .tmp.code:3:8: error: const four cannot be resolved.
1078         .tmp.code:4:8: error: const two cannot be resolved.
1079
1080 ###### test: missing_program
1081         const
1082                 foo::="bar"
1083
1084 ###### output: missing_program
1085         oceani: no main function found.
1086
1087 ###### test: bad_main
1088         func main(foo:string)
1089                 print foo
1090
1091 ###### output: bad_main
1092         .tmp.code:2:10: error: expected []string but variable 'foo' is string
1093         .tmp.code:2:10: info: this is where 'foo' was set to string
1094         oceani: main has wrong type.
1095
1096 Test for type errors with functions
1097
1098 ###### test list
1099         oceani_failing_tests += func_err_args func_err_redeclare
1100
1101 ###### test: func_err_args
1102
1103         func test1(a:number; b:string; c:[3]Boolean)
1104                 print a, b, c[1]
1105
1106         func test2(a:number; b:string; c:[3]Boolean)
1107                 print a, b, c[1]
1108
1109         func test3()
1110                 # use undefined names
1111                 print a, z
1112
1113         func main()
1114                 truth:[3]Boolean
1115                 truth[1] = True
1116                 test1(1,"hello")
1117                 test1("hello",1)
1118                 test1(1, "two", truth)
1119                 test1(1, 2, truth)
1120                 test1(1, "lo", truth, 4)
1121                 print test(), test1(1,2,3)
1122                 if test1 == test2:
1123                         pass
1124
1125         func test4(a:number):string
1126                 use a * a
1127
1128         func test5(a:number):string
1129                 print a
1130
1131         struct foo
1132                 a: number
1133                 b:string = "hello"
1134
1135         func test6(a:number):foo
1136                 b:foo
1137                 b.a = a
1138                 use b
1139
1140 ###### output: func_err_args
1141         .tmp.code:34:5: error: function cannot return value of type foo
1142         .tmp.code:28:8: error: expected string, found none
1143         .tmp.code:25:8: error: expected string, found number
1144         .tmp.code:15:14: error: insufficient arguments to function.
1145         .tmp.code:16:14: error: expected number found string
1146         .tmp.code:16:22: error: expected string found number
1147         .tmp.code:16:14: error: insufficient arguments to function.
1148         .tmp.code:18:17: error: expected string found number
1149         .tmp.code:19:14: error: too many arguments to function.
1150         .tmp.code:20:14: error: attempt to call a non-function.
1151         .tmp.code:20:32: error: expected string found number
1152         .tmp.code:20:28: error: insufficient arguments to function.
1153         .tmp.code:21:20: error: expected "func test1" but variable 'test2' is "func test2"
1154         .tmp.code:5:5: info: this is where 'test2' was set to "func test2"
1155         .tmp.code:10:14: error: variable used but not declared: a
1156         .tmp.code:10:17: error: variable used but not declared: z
1157         oceani: type error in program - not running.
1158
1159 ###### test: func_err_redeclare
1160
1161         func test1(a:number; b:string; c:[3]Boolean)
1162                 print a, b, c[1]
1163
1164         func test1
1165         do
1166                 pass
1167
1168         func test1
1169                 b:Boolean
1170         do
1171                 pass
1172
1173 ###### output: func_err_redeclare
1174         .tmp.code:5:5: error: function 'test1' redeclared
1175         .tmp.code:2:5: info: this is where 'test1' was first declared
1176         .tmp.code:9:5: error: function 'test1' redeclared
1177         .tmp.code:2:5: info: this is where 'test1' was first declared
1178
1179 Test for errors with references
1180
1181 ###### test list
1182         oceani_failing_tests += ref_err1 ref_err2
1183
1184 ###### test: ref_err1
1185         func main()
1186                 ref:@number
1187                 @foo = ref
1188                 ref = @old()
1189                 if ref == @null:
1190                         print "null"
1191
1192 ###### output: ref_err1
1193         .tmp.code:4:9: error: only "@free" makes sense here: foo
1194         .tmp.code:5:15: error: Only reference function is "@new()": old
1195         .tmp.code:6:19: error: Only reference value is "@nil": null
1196
1197 ###### test: ref_err2
1198         func main()
1199                 ref:@number
1200                 ref2:@string
1201                 num:number = @new()
1202                 print num@
1203                 if num == @nil or ref == ref2 or ref == 2 or ref.foo:
1204                         @free = num
1205
1206 ###### output: ref_err2
1207         .tmp.code:5:22: error: @new() can only be used with references, not number
1208         .tmp.code:5:8: info: variable 'num' was set as number here.
1209         .tmp.code:6:14: error: Cannot dereference number
1210         .tmp.code:7:19: error: @nil can only be used with reference, not number
1211         .tmp.code:7:33: error: expected @number but variable 'ref2' is @string
1212         .tmp.code:4:8: info: this is where 'ref2' was set to @string
1213         .tmp.code:7:48: error: expected @number found number
1214         .tmp.code:7:53: error: field reference on number is not supported
1215         .tmp.code:7:56: error: have none but need Boolean
1216         .tmp.code:8:17: error: @free can only be assigned a reference, not number
1217         .tmp.code:8:17: error: @free can only be assigned a reference, not number
1218         oceani: type error in program - not running.
1219
1220 ## Test erroneous command line args
1221
1222 To improve coverage, we want to test correct handling of strange command
1223 line arguments.  These tests won't use code, so the exiting test types
1224 won't work.  So we need to be able to explicitly give the command line,
1225 and the expected output, and have that tested and the coverage assessed.
1226 Rather than having to spell out the whole command name, just give "cmd",
1227 and discard that.  Requiring but discarding the command make an empty
1228 command list possible.
1229
1230 ###### test code
1231         @for t in $(oceani_special_tests); do \
1232             echo -n "Test $$t ... ";\
1233             i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
1234             ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
1235             ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
1236             if ! cmp -s .tmp.want .tmp.have; then \
1237                echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
1238             echo  "passed"; \
1239             ./coverage_oceani $${1+"$$@"} > /dev/null 2>&1 ;\
1240         done || true
1241 ###### valgrind test code
1242         @[ -n "$$SKIP_VALGRIND" ] || for t in $(oceani_special_tests); do\
1243             echo -n "Valgrind $$t.. "; \
1244             i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
1245             if valgrind --error-exitcode=42 --log-file=.tmp.valg ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
1246                [ $$? -eq 42 ]; then \
1247                        echo "FAILED"; cat .tmp.valg; exit 1; fi ; \
1248             if grep 'LEAK SUMMARY' .tmp.valg > /dev/null; then \
1249                echo "valgrind found LEAKS"; cat .tmp.valg ; exit 1 ; fi; \
1250             if grep 'in use at exit [1-9]' .tmp.valg > /dev/null; then \
1251                echo "valgrind found memory in use at exit"; cat .tmp.valg ; exit 1 ; fi; \
1252             echo " passed"; \
1253         done
1254
1255 ###### test list
1256         oceani_special_tests += "cmd"
1257         oceani_special_tests += "cmd,-zyx"
1258         oceani_special_tests += "cmd,nofile"
1259         oceani_special_tests += "cmd,/dev/null"
1260         oceani_special_tests += "cmd,--section,toast:nothing,oceani-tests.mdc"
1261
1262 ###### output: cmd
1263         oceani: no input file given
1264
1265 ###### output: cmd,-zyx
1266         ./oceani: invalid option -- 'z'
1267         Usage: oceani --trace --print --noexec --brackets --section=SectionName prog.ocn
1268
1269 ###### output: cmd,nofile
1270         oceani: cannot open nofile
1271
1272 ###### output: cmd,/dev/null
1273         oceani: could not find any code in /dev/null
1274
1275 ###### output: cmd,--section,toast:nothing,oceani-tests.mdc
1276         oceani: cannot find section toast:nothing