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