]> ocean-lang.org Git - ocean/blob - csrc/oceani-tests.mdc
0feb0d20ba0b86f17869b47931e998705a23a0d3
[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
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:6:8: error: Cannot assign to a constant: b
932         .tmp.code:5:8: info: name was defined as a constant here
933         .tmp.code:8:11: error: Arithmetic returns number but Boolean expected
934         .tmp.code:9:20: error: expected number found label
935         .tmp.code:9:8: info: variable 'd' was set as number here.
936         .tmp.code:10:8: error: cannot assign to an rval
937         oceani: type error in program - not running.
938
939 ###### test: type_err2
940
941         func main()
942                 a := 1
943                 a := 2
944                 a ::= 3
945                 a:number = 4
946                 a ::number = 5
947                 c:
948                 d:number = 02
949
950 ###### output: type_err2
951         .tmp.code:4:8: error: variable 'a' redeclared
952         .tmp.code:3:8: info: this is where 'a' was first declared
953         .tmp.code:5:8: error: variable 'a' redeclared
954         .tmp.code:3:8: info: this is where 'a' was first declared
955         .tmp.code:6:8: error: variable 'a' redeclared
956         .tmp.code:3:8: info: this is where 'a' was first declared
957         .tmp.code:7:8: error: variable 'a' redeclared
958         .tmp.code:3:8: info: this is where 'a' was first declared
959         .tmp.code:8:8: Variable declared with no type or value: c
960         .tmp.code:9:19: error: unsupported number format: 02
961
962 ###### test: type_err3
963
964         struct foo
965                 a: number
966                 b:string = "hello"; d:Boolean
967
968         func main()
969                 c := "hello"
970                 c = c + 1
971                 c = "hello" ++ (True and False)
972                 c = 4 < 5
973                 print 45 + ( "Hello" ++ "there")
974                 c[5] = 1
975
976                 while
977                         use 1
978                         use True
979                         use "Hello"
980                 do
981                         print
982                 case 1: print "one"
983                 case "Hello": print "Hello"
984
985                 a1:[5]number; a2:[5]number; a3:[10]number; a4:[5]string
986                 a1 = a2
987                 a1 = a3
988                 a1 = a4
989                 a1[2] = "hello"
990                 a4[1] = True
991                 c = a2[3]
992
993                 bar:foo
994                 foo.c = 43
995                 print c.foo
996                 print bar.c
997                 print bar.b + 42
998
999                 // trigger 'labels not permitted' error message
1000                 while 1 if True else False:
1001                         print
1002                 case 2: print "two"
1003                 print "one" ++ a4[], c[]
1004
1005                 x:Boolean = $"42"
1006
1007                 five ::= 5
1008                 four ::= 4
1009                 x1:[five]number
1010                 x2:[four]number
1011                 x1 = x2
1012
1013 ###### output: type_err3
1014         .tmp.code:8:12: error: expected number but variable 'c' is string
1015         .tmp.code:7:8: info: this is where 'c' was set to string
1016         .tmp.code:8:12: error: Arithmetic returns number but string expected
1017         .tmp.code:7:8: info: variable 'c' was set as string here.
1018         .tmp.code:9:24: error: Boolean operation found where string expected
1019         .tmp.code:7:8: info: variable 'c' was set as string here.
1020         .tmp.code:10:12: error: Comparison returns Boolean but string expected
1021         .tmp.code:7:8: info: variable 'c' was set as string here.
1022         .tmp.code:11:21: error: Concat returns string but number expected
1023         .tmp.code:12:8: error: string cannot be indexed
1024         .tmp.code:12:8: error: string cannot be indexed
1025         .tmp.code:21:13: error: expected number found string
1026         .tmp.code:17:16: error: expected number, found string
1027         .tmp.code:24:8: error: cannot assign value of type [5]number
1028         .tmp.code:25:13: error: expected [5]number but variable 'a3' is [10]number
1029         .tmp.code:23:36: info: this is where 'a3' was set to [10]number
1030         .tmp.code:25:8: error: cannot assign value of type [5]number
1031         .tmp.code:23:8: info: variable 'a1' was set as [5]number here.
1032         .tmp.code:26:13: error: expected [5]number but variable 'a4' is [5]string
1033         .tmp.code:23:51: info: this is where 'a4' was set to [5]string
1034         .tmp.code:26:8: error: cannot assign value of type [5]number
1035         .tmp.code:23:8: info: variable 'a1' was set as [5]number here.
1036         .tmp.code:27:16: error: expected number found string
1037         .tmp.code:28:16: error: expected string found Boolean
1038         .tmp.code:29:12: error: have number but need string
1039         .tmp.code:7:8: info: variable 'c' was set as string here.
1040         .tmp.code:32:8: error: variable used but not declared: foo
1041         .tmp.code:32:8: error: field reference on none is not supported
1042         .tmp.code:32:16: error: expected none found number
1043         .tmp.code:33:14: error: field reference on string is not supported
1044         .tmp.code:34:14: error: cannot find requested field in foo
1045         .tmp.code:35:17: error: have string but need number
1046         .tmp.code:38:29: error: expected number found Boolean
1047         .tmp.code:41:23: error: have number but need string
1048         .tmp.code:41:29: error: string cannot provide length
1049         .tmp.code:43:21: error: Can only convert string to number, not Boolean
1050         .tmp.code:43:8: info: variable 'x' was set as Boolean here.
1051         .tmp.code:49:13: error: expected [five]number but variable 'x2' is [four]number
1052         .tmp.code:48:8: info: this is where 'x2' was set to [four]number
1053         .tmp.code:49:8: error: cannot assign value of type [five]number
1054         .tmp.code:47:8: info: variable 'x1' was set as [five]number here.
1055         oceani: type error in program - not running.
1056
1057 ###### test: type_err4
1058         func main()
1059                 a:=1; b=2; c::=3
1060                 print a, b, c
1061
1062 ###### output: type_err4
1063         .tmp.code:3:14: error: variable used but not declared: b
1064         .tmp.code:3:16: error: expected none found number
1065         .tmp.code:3:14: info: variable 'b' was set as none here.
1066         oceani: type error in program - not running.
1067
1068 ###### test: type_err5
1069         struct foo
1070                 bar:baz
1071                 a:number
1072         struct baz
1073                 bat:foo
1074                 b:string
1075         struct foo
1076                 c:number
1077                 x:[5]:string
1078
1079 ###### output: type_err5
1080         .tmp.code:8:7: error: type already declared: foo
1081         .tmp.code:2:7: info: this is location of declartion: foo
1082         .tmp.code:10:13: Syntax error in struct field: :
1083         .tmp.code:5:7: error: type has recursive definition: baz
1084         .tmp.code:2:7: error: type has recursive definition: foo
1085
1086 ###### test: type_err6
1087
1088         func main()
1089                 a:= "hello"
1090                 if ?a:
1091                         print "no"
1092                 print a ?? "there"
1093
1094 ###### output: type_err6
1095         .tmp.code:4:12: error: '?' requires a testable value, not string
1096         .tmp.code:6:14: error: "??" requires a testable value, not string
1097         oceani: type error in program - not running.
1098
1099
1100 ###### test list
1101         oceani_failing_tests += type_err_const type_err_const1 type_err_const2 missing_program bad_main
1102
1103 ###### test: type_err_const
1104         const
1105                 foo :: number = 45
1106                 bar ::= "string" + 56
1107         const
1108                 bar ::= "baz"
1109         func main()
1110                 foo := 4
1111                 print foo, bar
1112
1113         // trigger duplicate-main error
1114         func main()
1115                 foo := 6
1116                 print bar, foo
1117
1118 ###### output: type_err_const
1119         .tmp.code:6:8: error: name already declared: bar
1120         .tmp.code:4:8: info: this is where 'bar' was first declared
1121         .tmp.code:8:8: error: variable 'foo' redeclared
1122         .tmp.code:3:8: info: this is where 'foo' was first declared
1123         .tmp.code:12:5: error: function 'main' redeclared
1124         .tmp.code:7:5: info: this is where 'main' was first declared
1125         .tmp.code:13:8: error: variable 'foo' redeclared
1126         .tmp.code:3:8: info: this is where 'foo' was first declared
1127         .tmp.code:4:16: error: expected number found string
1128
1129 ###### test: type_err_const1
1130         const
1131                 foo : number = 45
1132                 bar := "string"
1133         func main()
1134                 foo := 4
1135                 print foo, bar
1136
1137 ###### output: type_err_const1
1138         .tmp.code:3:12: Syntax error in constant: :
1139         .tmp.code:4:12: Syntax error in constant: :
1140
1141 ###### test: type_err_const2
1142         const
1143                 four ::= two + two
1144                 two ::= four / 2
1145         
1146 ###### output: type_err_const2
1147         .tmp.code:3:8: error: const four cannot be resolved.
1148         .tmp.code:4:8: error: const two cannot be resolved.
1149
1150 ###### test: missing_program
1151         const
1152                 foo::="bar"
1153
1154 ###### output: missing_program
1155         oceani: no main function found.
1156
1157 ###### test: bad_main
1158         func main(foo:string)
1159                 print foo
1160
1161 ###### output: bad_main
1162         .tmp.code:2:10: error: expected []string but variable 'foo' is string
1163         .tmp.code:2:10: info: this is where 'foo' was set to string
1164         oceani: main has wrong type.
1165
1166 Test for type errors with functions
1167
1168 ###### test list
1169         oceani_failing_tests += func_err_args func_err_redeclare
1170
1171 ###### test: func_err_args
1172
1173         func test1(a:number; b:string; c:[3]Boolean)
1174                 print a, b, c[1]
1175
1176         func test2(a:number; b:string; c:[3]Boolean)
1177                 print a, b, c[1]
1178
1179         func test3()
1180                 # use undefined names
1181                 print a, z
1182
1183         func main()
1184                 truth:[3]Boolean
1185                 truth[1] = True
1186                 test1(1,"hello")
1187                 test1("hello",1)
1188                 test1(1, "two", truth)
1189                 test1(1, 2, truth)
1190                 test1(1, "lo", truth, 4)
1191                 print test(), test1(1,2,3)
1192                 if test1 == test2:
1193                         pass
1194
1195         func test4(a:number):string
1196                 use a * a
1197
1198         func test5(a:number):string
1199                 print a
1200
1201         struct foo
1202                 a: number
1203                 b:string = "hello"
1204
1205         func test6(a:number):foo
1206                 b:foo
1207                 b.a = a
1208                 use b
1209
1210         func test7(a:@number)
1211                 test7(45)
1212                 test7("45")
1213
1214 ###### output: func_err_args
1215         .tmp.code:40:14: error: cannot pass rval when reference expected
1216         .tmp.code:41:14: error: expected @number found string
1217         .tmp.code:34:5: error: function cannot return value of type foo
1218         .tmp.code:28:8: error: expected string, found none
1219         .tmp.code:25:8: error: expected string, found number
1220         .tmp.code:15:14: error: insufficient arguments to function.
1221         .tmp.code:16:14: error: expected number found string
1222         .tmp.code:16:22: error: expected string found number
1223         .tmp.code:16:14: error: insufficient arguments to function.
1224         .tmp.code:18:17: error: expected string found number
1225         .tmp.code:19:14: error: too many arguments to function.
1226         .tmp.code:20:14: error: attempt to call a non-function.
1227         .tmp.code:20:32: error: expected string found number
1228         .tmp.code:20:28: error: insufficient arguments to function.
1229         .tmp.code:21:20: error: expected "func test1" but variable 'test2' is "func test2"
1230         .tmp.code:5:5: info: this is where 'test2' was set to "func test2"
1231         .tmp.code:10:14: error: variable used but not declared: a
1232         .tmp.code:10:17: error: variable used but not declared: z
1233         oceani: type error in program - not running.
1234
1235 ###### test: func_err_redeclare
1236
1237         func test1(a:number; b:string; c:[3]Boolean)
1238                 print a, b, c[1]
1239
1240         func test1
1241         do
1242                 pass
1243
1244         func test1
1245                 b:Boolean
1246         do
1247                 pass
1248
1249 ###### output: func_err_redeclare
1250         .tmp.code:5:5: error: function 'test1' redeclared
1251         .tmp.code:2:5: info: this is where 'test1' was first declared
1252         .tmp.code:9:5: error: function 'test1' redeclared
1253         .tmp.code:2:5: info: this is where 'test1' was first declared
1254
1255 Test for errors with references
1256
1257 ###### test list
1258         oceani_failing_tests += ref_err1 ref_err2
1259
1260 ###### test: ref_err1
1261         func main()
1262                 ref:@number
1263                 @foo = ref
1264                 ref = @old()
1265                 if ref == @null:
1266                         print "null"
1267
1268 ###### output: ref_err1
1269         .tmp.code:4:9: error: only "@free" makes sense here: foo
1270         .tmp.code:5:15: error: Only reference function is "@new()": old
1271         .tmp.code:6:19: error: Only reference value is "@nil": null
1272
1273 ###### test: ref_err2
1274         func main()
1275                 ref:@number
1276                 ref2:@string
1277                 num:number = @new()
1278                 print num@
1279                 if num == @nil or ref == ref2 or ref == 2 or ref.foo:
1280                         @free = num
1281                 ref = 1
1282
1283 ###### output: ref_err2
1284         .tmp.code:5:22: error: @new() can only be used with references, not number
1285         .tmp.code:5:8: info: variable 'num' was set as number here.
1286         .tmp.code:6:14: error: Cannot dereference number
1287         .tmp.code:7:19: error: @nil can only be used with reference, not number
1288         .tmp.code:7:33: error: expected @number but variable 'ref2' is @string
1289         .tmp.code:4:8: info: this is where 'ref2' was set to @string
1290         .tmp.code:7:48: error: expected @number found number
1291         .tmp.code:7:53: error: field reference on number is not supported
1292         .tmp.code:7:56: error: have none but need Boolean
1293         .tmp.code:8:17: error: @free can only be assigned a reference, not number
1294         .tmp.code:8:17: error: @free can only be assigned a reference, not number
1295         .tmp.code:9:8: error: Cannot assign an rval to a reference.
1296         oceani: type error in program - not running.
1297
1298 ## Test erroneous command line args
1299
1300 To improve coverage, we want to test correct handling of strange command
1301 line arguments.  These tests won't use code, so the exiting test types
1302 won't work.  So we need to be able to explicitly give the command line,
1303 and the expected output, and have that tested and the coverage assessed.
1304 Rather than having to spell out the whole command name, just give "cmd",
1305 and discard that.  Requiring but discarding the command make an empty
1306 command list possible.
1307
1308 ###### test code
1309         @for t in $(oceani_special_tests); do \
1310             echo -n "Test $$t ... ";\
1311             i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
1312             ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
1313             ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
1314             if ! cmp -s .tmp.want .tmp.have; then \
1315                echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
1316             echo  "passed"; \
1317             ./coverage_oceani $${1+"$$@"} > /dev/null 2>&1 ;\
1318         done || true
1319 ###### valgrind test code
1320         @[ -n "$$SKIP_VALGRIND" ] || for t in $(oceani_special_tests); do\
1321             echo -n "Valgrind $$t.. "; \
1322             i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
1323             if valgrind --error-exitcode=42 --log-file=.tmp.valg ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
1324                [ $$? -eq 42 ]; then \
1325                        echo "FAILED"; cat .tmp.valg; exit 1; fi ; \
1326             if grep 'LEAK SUMMARY' .tmp.valg > /dev/null; then \
1327                echo "valgrind found LEAKS"; cat .tmp.valg ; exit 1 ; fi; \
1328             if grep 'in use at exit [1-9]' .tmp.valg > /dev/null; then \
1329                echo "valgrind found memory in use at exit"; cat .tmp.valg ; exit 1 ; fi; \
1330             echo " passed"; \
1331         done
1332
1333 ###### test list
1334         oceani_special_tests += "cmd"
1335         oceani_special_tests += "cmd,-zyx"
1336         oceani_special_tests += "cmd,nofile"
1337         oceani_special_tests += "cmd,/dev/null"
1338         oceani_special_tests += "cmd,--section,toast:nothing,oceani-tests.mdc"
1339
1340 ###### output: cmd
1341         oceani: no input file given
1342
1343 ###### output: cmd,-zyx
1344         ./oceani: invalid option -- 'z'
1345         Usage: oceani --trace --print --noexec --brackets --section=SectionName prog.ocn
1346
1347 ###### output: cmd,nofile
1348         oceani: cannot open nofile
1349
1350 ###### output: cmd,/dev/null
1351         oceani: could not find any code in /dev/null
1352
1353 ###### output: cmd,--section,toast:nothing,oceani-tests.mdc
1354         oceani: cannot find section toast:nothing