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