]> ocean-lang.org Git - ocean/blob - csrc/oceani-tests.mdc
oceani-tests: assorted more tests.
[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
39         tests:: oceani_test_suite
40         oceani_test_suite: oceani coverage_oceani
41                 @rm -rf coverage; mkdir -p coverage
42                 @cp *.gcno coverage
43                 @for T in $(oceani_tests); do \
44                     echo -n "Test $$T ... "; \
45                     i="$$IFS"; IFS=,; set $$T; IFS="$$i"; t=$$1; shift; \
46                     ./md2c oceani-tests.mdc "output: $$T" | grep -v '^#' > .tmp.want; \
47                     ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > .tmp.have; \
48                     if ! cmp -s .tmp.want .tmp.have; then \
49                        echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
50                     echo -n "passed ... "; \
51                     if ! valgrind ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} \
52                          > /dev/null 2> .tmp.valg; then \
53                        echo "valgrind FAILED"; cat .tmp.valg; exit 1; fi ; \
54                     echo -n "valgrind passed ... "; \
55                     echo '``````' > .tmp.code1; echo '``````' > .tmp.code2 ;\
56                     ./oceani --noexec --print --section "test: $$t" oceani-tests.mdc >> .tmp.code1; \
57                     ./oceani --noexec --print .tmp.code1 >> .tmp.code2 ;\
58                     if ! cmp -s .tmp.code1 .tmp.code2; then \
59                        echo "Printing Failed"; diff -u .tmp.code1 .tmp.code2; exit1 ; fi ; \
60                     echo "Printing passed"; \
61                     ./coverage_oceani --print --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > /dev/null ; \
62                     ./coverage_oceani -tpbn --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1; \
63                 done
64
65                 ## test code
66
67                 @gcov -o coverage oceani.mdc > /dev/null 2> /dev/null
68                 @mv *.gcov coverage ; [ -f .gcov ] && mv .gcov coverage
69                 @ awk '/^ *[1-9]/ {ran+=1} /^ *###/ {skip+=1} \
70                     END {printf "coverage: %6.2f%%\n", ran * 100 / (ran + skip); \
71                          if (ran < (ran + skip) *0.90) exit(1) }' \
72                         coverage/oceani.mdc.gcov
73                 @rm -f .tmp*
74
75         coverage_oceani: oceani.c
76                 $(CC) $(CFLAGS) --coverage -fprofile-dir=coverage -o coverage_oceani oceani.c $(LDLIBS)
77
78 ## Values and variables
79
80 The first test stores values in variables and performs various
81 calculations on them.
82
83 ###### test list
84        oceani_tests += "valvar"
85
86
87 ###### test: valvar
88
89         program:
90                 a := 23; b:=12 ; b1 := -b
91                 print a, b, a+b, a-b, a*b, a/b, a%b
92                 print a<b, a<=b, a>b, a>=b, a<a, a==b, a==a
93                 print +a, +b, +b1, -a, -b, -b1
94                 x := True; y := False
95                 print x and y, x or y, x and x, y or y, x and not x, x < y
96
97                 c ::= "This is a string"
98                 d ::= " field theory"
99                 print c, d, c++d
100
101                 aconst :: string = "unchanging"
102
103 ###### output: valvar
104
105         23 12 35 11 276 1.91667 11
106         False False True True False False True
107         23 12 12 -23 -12 12
108         False True True False False False
109         This is a string  field theory This is a string field theory
110
111 Next we change the value of variables
112
113 ###### test list
114        oceani_tests += "setvar"
115
116 ###### test: setvar
117
118         program:
119                 a := 4
120                 a = a * a
121                 a = (a + a) * (a + a)
122                 a = a * a * a
123                 print a, a/a
124
125 ###### output: setvar
126         1.07374e+09 1
127
128 ## Conditions and Loops
129
130 Now we need to test if/else and some different loops
131
132 ###### test list
133        oceani_tests += cond_loop
134
135 ###### test: cond_loop
136
137         program:
138                 a := 4
139                 if a < 5:
140                         print "Success"
141                 else:
142                         print "Failure"
143                 for b:=1; then b=b+b; while b < 100:
144                         print '', b,
145                 print
146                 // Newtons method for square root of 2
147                 target ::= 2
148                 guess := target
149                 for:
150                         count: number = 0
151                 while:
152                         current := guess * guess
153                         use +(current - target) > 0.000000001
154                 do:
155                         guess = (guess + (target / guess) ) / 2
156                         print count, guess
157                         count = count + 1
158                 print "error is ", target - guess * guess
159
160 ###### output: cond_loop
161         Success
162          1 2 4 8 16 32 64
163         0 1.5
164         1 1.41667
165         2 1.41422
166         3 1.41421
167         error is  -4.51095e-12
168
169 ## Say Hello
170
171 The demonstration code presented in the interpreted is suitable for the test suite.
172 Here I break it into two parts, keeping the array code separate.
173
174 ###### test list
175         oceani_tests += "sayhello,55,33"
176         oceani_tests += "sayhello,12,60"
177
178 ###### test: sayhello
179
180         program A B:
181                 print "Hello World, what lovely oceans you have!"
182                 /* When a variable is defined in both branches of an 'if',
183                  * and used afterwards, the variables are merged.
184                  */
185                 if A > B:
186                         bigger := "yes"
187                 else:
188                         bigger := "no"
189                 print "Is", A, "bigger than", B,"? ", bigger
190                 /* If a variable is not used after the 'if', no
191                  * merge happens, so types can be different
192                  */
193                 if A > B * 2:
194                         double:string = "yes"
195                         print A, "is more than twice", B, "?", double
196                 else:
197                         double := B*2
198                         print "double", B, "is", double
199
200                 a : number
201                 a = A;
202                 b:number = B
203                 if a > 0 and b > 0:
204                         while a != b:
205                                 if a < b:
206                                         b = b - a
207                                 else:
208                                         a = a - b
209                         print "GCD of", A, "and", B,"is", a
210                 else if a <= 0:
211                         print a, "is not positive, cannot calculate GCD"
212                 else:
213                         print b, "is not positive, cannot calculate GCD"
214
215                 for:
216                         togo := 10
217                         f1 := 1; f2 := 1
218                         print "Fibonacci:", f1,f2,
219                 then togo = togo - 1
220                 while togo > 0:
221                         f3 := f1 + f2
222                         print "", f3,
223                         f1 = f2
224                         f2 = f3
225                 print ""
226
227                 /* Binary search... */
228                 for:
229                         lo:= 0; hi := 100
230                         target := 77
231                 while:
232                         mid := (lo + hi) / 2
233                         if mid == target:
234                                 use Found
235                         if mid < target:
236                                 lo = mid
237                         else:
238                                 hi = mid
239                         if hi - lo < 1:
240                                 use GiveUp
241                         use True
242                 do: pass
243                 case Found:
244                         print "Yay, I found", target
245                 case GiveUp:
246                         print "Closest I found was", mid
247
248 ###### output: sayhello,55,33
249         Hello World, what lovely oceans you have!
250         Is 55 bigger than 33 ?  yes
251         double 33 is 66
252         GCD of 55 and 33 is 11
253         Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
254         Closest I found was 77.3438
255
256 ###### output: sayhello,12,60
257         Hello World, what lovely oceans you have!
258         Is 12 bigger than 60 ?  no
259         double 60 is 120
260         GCD of 12 and 60 is 12
261         Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144
262         Closest I found was 77.3438
263
264 ###### test list
265         oceani_tests += "insert_sort"
266 ###### test: insert_sort
267         program:
268                 size::=55
269                 list:[size]number
270                 list[0] = 1234
271                 for i:=1; then i = i + 1; while i < size:
272                         n := list[i-1] * list[i-1]
273                         list[i] = (n / 100) % 10000
274
275                 print "Before sort:"
276                 for i:=0; then i = i + 1; while i < size:
277                         print "list[",i,"]=",list[i]
278
279                 for i := 1; then i=i+1; while i < size:
280                         for j:=i-1; then j=j-1; while j >= 0:
281                                 if list[j] > list[j+1]:
282                                         t:= list[j]
283                                         list[j] = list[j+1]
284                                         list[j+1] = t
285                 print "After sort:"
286                 for i:=0; then i = i + 1; while i < size:
287                         print "list[",i,"]=",list[i]
288
289 ###### output: insert_sort
290         Before sort:
291         list[ 0 ]= 1234
292         list[ 1 ]= 5227
293         list[ 2 ]= 3215
294         list[ 3 ]= 3362
295         list[ 4 ]= 3030
296         list[ 5 ]= 1809
297         list[ 6 ]= 2724
298         list[ 7 ]= 4201
299         list[ 8 ]= 6484
300         list[ 9 ]= 422
301         list[ 10 ]= 1780
302         list[ 11 ]= 1684
303         list[ 12 ]= 8358
304         list[ 13 ]= 8561
305         list[ 14 ]= 2907
306         list[ 15 ]= 4506
307         list[ 16 ]= 3040
308         list[ 17 ]= 2416
309         list[ 18 ]= 8370
310         list[ 19 ]= 569
311         list[ 20 ]= 3237
312         list[ 21 ]= 4781
313         list[ 22 ]= 8579
314         list[ 23 ]= 5992
315         list[ 24 ]= 9040
316         list[ 25 ]= 7216
317         list[ 26 ]= 706
318         list[ 27 ]= 4984
319         list[ 28 ]= 8402
320         list[ 29 ]= 5936
321         list[ 30 ]= 2360
322         list[ 31 ]= 5696
323         list[ 32 ]= 4444
324         list[ 33 ]= 7491
325         list[ 34 ]= 1150
326         list[ 35 ]= 3225
327         list[ 36 ]= 4006
328         list[ 37 ]= 480
329         list[ 38 ]= 2304
330         list[ 39 ]= 3084
331         list[ 40 ]= 5110
332         list[ 41 ]= 1121
333         list[ 42 ]= 2566
334         list[ 43 ]= 5843
335         list[ 44 ]= 1406
336         list[ 45 ]= 9768
337         list[ 46 ]= 4138
338         list[ 47 ]= 1230
339         list[ 48 ]= 5129
340         list[ 49 ]= 3066
341         list[ 50 ]= 4003
342         list[ 51 ]= 240
343         list[ 52 ]= 576
344         list[ 53 ]= 3317
345         list[ 54 ]= 24
346         After sort:
347         list[ 0 ]= 24
348         list[ 1 ]= 240
349         list[ 2 ]= 422
350         list[ 3 ]= 480
351         list[ 4 ]= 569
352         list[ 5 ]= 576
353         list[ 6 ]= 706
354         list[ 7 ]= 1121
355         list[ 8 ]= 1150
356         list[ 9 ]= 1230
357         list[ 10 ]= 1234
358         list[ 11 ]= 1406
359         list[ 12 ]= 1684
360         list[ 13 ]= 1780
361         list[ 14 ]= 1809
362         list[ 15 ]= 2304
363         list[ 16 ]= 2360
364         list[ 17 ]= 2416
365         list[ 18 ]= 2566
366         list[ 19 ]= 2724
367         list[ 20 ]= 2907
368         list[ 21 ]= 3030
369         list[ 22 ]= 3040
370         list[ 23 ]= 3066
371         list[ 24 ]= 3084
372         list[ 25 ]= 3215
373         list[ 26 ]= 3225
374         list[ 27 ]= 3237
375         list[ 28 ]= 3317
376         list[ 29 ]= 3362
377         list[ 30 ]= 4003
378         list[ 31 ]= 4006
379         list[ 32 ]= 4138
380         list[ 33 ]= 4201
381         list[ 34 ]= 4444
382         list[ 35 ]= 4506
383         list[ 36 ]= 4781
384         list[ 37 ]= 4984
385         list[ 38 ]= 5110
386         list[ 39 ]= 5129
387         list[ 40 ]= 5227
388         list[ 41 ]= 5696
389         list[ 42 ]= 5843
390         list[ 43 ]= 5936
391         list[ 44 ]= 5992
392         list[ 45 ]= 6484
393         list[ 46 ]= 7216
394         list[ 47 ]= 7491
395         list[ 48 ]= 8358
396         list[ 49 ]= 8370
397         list[ 50 ]= 8402
398         list[ 51 ]= 8561
399         list[ 52 ]= 8579
400         list[ 53 ]= 9040
401         list[ 54 ]= 9768
402
403 ## Arrays
404
405 We already have some array tests, but this is where we put other
406 ad-hoc things array related.
407
408 ###### test list
409         oceani_tests += arrays
410
411 ###### test: arrays
412
413         program:
414                 bools:[5]Boolean
415                 strings:[4]string
416
417                 bools[3] = strings[1] == "Hello"
418                 bools[1] = strings[2] <= "there"
419
420                 for i:=0; then i=i+1; while i<5:
421                         print '', bools[i],
422                 print
423
424 ###### output: arrays
425          False True False False False
426
427 ## Test code with syntax errors
428
429 Syntax errors aren't handled well yet - the result is almost always a
430 single message about the first error.  So this section will be fairly
431 thin until we add proper parsing recovery in the face of common errors.
432
433 A special case of syntax errors is token errors, when a token is only
434 accepted because the parser doesn't know quite enough to reject it.
435 There are handled better as they are quite local, so a single test
436 program can trigger most of the possible errors.
437
438 To handle erronous code, we need a different set of tests, as we need to
439 capture `stderr`. The same test code will be used for type errors too.
440 As error messages contain the line number, and we don't want changes to
441 this file to change the reported numbers, we copy the code into a
442 separate file first, then run from there.
443
444 ###### test code
445         @for t in $(oceani_failing_tests); do \
446             echo -n "Test $$t ... "; \
447             ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
448             echo '``````' > .tmp.code; \
449             ./md2c oceani-tests.mdc "test: $$t" | grep -v '^#' >> .tmp.code; \
450             ./oceani .tmp.code > .tmp.have 2>&1; \
451             if ! cmp -s .tmp.want .tmp.have; then \
452                echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
453             echo  "passed"; \
454             ./coverage_oceani --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1 ;\
455         done || true
456
457 ###### test list
458         oceani_failing_tests := syn1
459         oceani_failing_tests += tokerr
460
461 ###### test: syn1
462
463         program:
464                 if then else while do
465
466 ###### output: syn1
467         .tmp.code:3:11: error: unhandled parse error: then
468
469 ###### test: tokerr
470         program:
471                 a := 1i  // imaginary numbers aren't understood
472                 b:[2i]number // array sizes are handled separately
473                 c:[3.14159]Boolean // array sizes must be integers
474                 d:[1_000_000_000_000]number // they mustn't be huge
475                 patn: string = "foo[ ,_]*bar"re // regexp strings are just a dream
476
477                 multi := """
478                 This is a multiline string
479                 With an unsupportable suffix
480                 """Aa
481
482                 xx:unknown = 24
483                 yy:[unknowable]number
484                 zzsize := 4
485                 zz:[zzsize]string // size must be constant, use ::=
486
487                 // These numbers should be bad in all contexts: FIXME
488                 aa:[00123]number
489
490 ###### output: tokerr
491         .tmp.code:3:13: error: unsupported number suffix: 1i
492         .tmp.code:4:11: error: unsupported number suffix: 2i
493         .tmp.code:5:11: error: array size must be an integer: 3.14159
494         .tmp.code:6:11: error: array size is too large: 1_000_000_000_000
495         .tmp.code:7:23: error: unsupported string suffix: "foo[ ,_]*bar"re
496         .tmp.code:9:17: error: unsupported string suffix: """
497                 This is a multiline string
498                 With an unsupportable suffix
499                 """Aa
500         .tmp.code:14:11: error: undefined type: unknown
501         .tmp.code:15:12: error: name undeclared: unknowable
502         .tmp.code:17:12: error: array size must be a constant: zzsize
503         .tmp.code:20:12: error: unrecognised number: 00123
504
505 ## Tests for type errors
506
507 Type error don't cause parsing to abort, so we can fit many in the
508 one test program.  Some type errors are found during the parse, others
509 during type analysis which doesn't run if parsing failed.  So we cannot
510 fit everything in one.
511
512 These programs were generated by looking for the
513 various places that `type_err()` are called.
514
515 ###### test list
516         oceani_failing_tests += type_err1 type_err2 type_err3 type_err4
517
518 ###### test: type_err1
519
520         program:
521                 print "hello" ++ 5, 5 ++ "hello"
522
523                 b ::= 3
524                 b = b + 1
525
526                 if 3 * 4 and not True: print "Weird"
527
528 ###### output: type_err1
529         .tmp.code:3:25: error: expected string found number
530         .tmp.code:3:28: error: expected string found number
531         .tmp.code:6:8: error: Cannot assign to a constant: b
532         .tmp.code:5:8: info: name was defined as a constant here
533         .tmp.code:6:8: error: Cannot assign to a constant: b
534         .tmp.code:5:8: info: name was defined as a constant here
535         .tmp.code:8:11: error: Arithmetic returns number but Boolean expected
536         oceani: type error in program - not running.
537
538 ###### test: type_err2
539
540         program:
541                 a := 1
542                 a := 2
543                 a ::= 3
544                 a:number = 4
545                 a ::number = 5
546                 c:
547
548 ###### output: type_err2
549         .tmp.code:4:8: error: variable 'a' redeclared
550         .tmp.code:3:8: info: this is where 'a' was first declared
551         .tmp.code:5:8: error: variable 'a' redeclared
552         .tmp.code:3:8: info: this is where 'a' was first declared
553         .tmp.code:6:8: error: variable 'a' redeclared
554         .tmp.code:3:8: info: this is where 'a' was first declared
555         .tmp.code:7:8: error: variable 'a' redeclared
556         .tmp.code:3:8: info: this is where 'a' was first declared
557         .tmp.code:8:8: Variable declared with no type or value: c
558
559 ###### test: type_err3
560
561         program:
562                 c := "hello"
563                 c = c + 1
564                 c = "hello" ++ (True and False)
565                 c = 4 < 5
566                 print 45 + ( "Hello" ++ "there")
567                 c[5] = 1
568
569                 while:
570                         use 1
571                         use True
572                         use "Hello"
573                 do:
574                         print
575                 case 1: print "one"
576                 case "Hello": print "Hello"
577
578                 a1:[5]number; a2:[5]number; a3:[10]number; a4:[5]string
579                 a1 = a2
580                 a1 = a3
581                 a1 = a4
582                 a1[2] = "hello"
583                 a4[1] = True
584                 c = a2[3]
585
586 ###### output: type_err3
587         .tmp.code:4:12: error: expected number but variable 'c' is string
588         .tmp.code:3:8: info: this is where 'c' was set to string
589         .tmp.code:4:12: error: Arithmetic returns number but string expected
590         .tmp.code:3:8: info: variable 'c' was set as string here.
591         .tmp.code:5:24: error: Boolean operation found where string expected
592         .tmp.code:6:12: error: Comparison returns Boolean but string expected
593         .tmp.code:3:8: info: variable 'c' was set as string here.
594         .tmp.code:7:21: error: Concat returns string but number expected
595         .tmp.code:8:8: error: string cannot be indexed
596         .tmp.code:8:8: error: string cannot be indexed
597         .tmp.code:17:13: error: expected number found string
598         .tmp.code:13:16: error: expected number, found string
599         .tmp.code:20:8: error: cannot assign value of type [5]number
600         .tmp.code:21:13: error: expected [5]number but variable 'a3' is [10]number
601         .tmp.code:19:36: info: this is where 'a3' was set to [10]number
602         .tmp.code:21:8: error: cannot assign value of type [5]number
603         .tmp.code:22:13: error: expected [5]number but variable 'a4' is [5]string
604         .tmp.code:19:51: info: this is where 'a4' was set to [5]string
605         .tmp.code:22:8: error: cannot assign value of type [5]number
606         .tmp.code:23:16: error: expected number found string
607         .tmp.code:24:16: error: expected string found Boolean
608         .tmp.code:25:12: error: have number but need string
609         .tmp.code:3:8: info: variable 'c' was set as string here.
610         oceani: type error in program - not running.
611
612 ###### test: type_err4
613         program:
614                 a:=1; b=2; c::=3
615                 print a, b, c
616
617 ###### output: type_err4
618         .tmp.code:3:14: error: expected *unknown*type* (labels not permitted) but variable 'b' is label
619         .tmp.code:3:14: info: this is where 'b' was set to label
620         .tmp.code:3:16: error: expected label found number
621         .tmp.code:3:14: info: variable 'b' was set as label here.
622         .tmp.code:4:17: error: expected *unknown*type* (labels not permitted) but variable 'b' is label
623         .tmp.code:3:14: info: this is where 'b' was set to label
624         oceani: type error in program - not running.
625
626 ## Test erroneous command line args
627
628 To improve coverage, we want to test correct handling of strange command
629 line arguments.  These tests won't use code, so the exiting test types
630 won't work.  So we need to be able to explicitly give the command line,
631 and the expected output, and have that tested and the coverage assessed.
632 Rather than having to spell out the whole command name, just give "cmd",
633 and discard that.  Requiring but discarding the command make an empty
634 command list possible.
635
636 ###### test code
637         @for t in $(oceani_special_tests); do \
638             echo -n "Test $$t ... ";\
639             i="$$IFS"; IFS=,; set $$t; IFS="$$i"; shift ;\
640             ./md2c oceani-tests.mdc "output: $$t" | grep -v '^#' > .tmp.want; \
641             ./oceani $${1+"$$@"} > .tmp.have 2>&1 ;\
642             if ! cmp -s .tmp.want .tmp.have; then \
643                echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\
644             echo  "passed"; \
645             ./coverage_oceani $${1+"$$@"} > /dev/null 2>&1 ;\
646         done || true
647
648
649 ###### test list
650         oceani_special_tests += "cmd"
651         oceani_special_tests += "cmd,-zyx"
652         oceani_special_tests += "cmd,nofile"
653         oceani_special_tests += "cmd,/dev/null"
654         oceani_special_tests += "cmd,--section,toast:nothing,oceani-tests.mdc"
655
656 ###### output: cmd
657         oceani: no input file given
658
659 ###### output: cmd,-zyx
660         ./oceani: invalid option -- 'z'
661         Usage: oceani --trace --print --noexec --brackets--section=SectionName prog.ocn
662
663 ###### output: cmd,nofile
664         oceani: cannot open nofile
665
666 ###### output: cmd,/dev/null
667         oceani: could not find any code in /dev/null
668
669 ###### output: cmd,--section,toast:nothing,oceani-tests.mdc
670         oceani: cannot find section toast:nothing
671