X-Git-Url: https://ocean-lang.org/code/?p=ocean;a=blobdiff_plain;f=csrc%2Foceani-tests.mdc;h=dccfb2a47bc2bdb50f7f3a39e7c3adf09ccf1b01;hp=d0b14190e7a31ff1fc59a0f7c1c0c6bdbcff7279;hb=84f4783c47a0fcb74b7f46d3ff96fe8a3ad1b278;hpb=4e5c62ce299f422fa51b96245f74f652e3511433 diff --git a/csrc/oceani-tests.mdc b/csrc/oceani-tests.mdc index d0b1419..dccfb2a 100644 --- a/csrc/oceani-tests.mdc +++ b/csrc/oceani-tests.mdc @@ -38,40 +38,65 @@ arguments separated from the name by commas. For each test, there is a section tests:: oceani_test_suite oceani_test_suite: oceani coverage_oceani + @echo -n Checking grammar ... + @./parsergen --report --LALR --tag Parser oceani.mdc | grep " - no conflicts" > /dev/null || \ + { echo "Grammar contains conflicts, please review" ; exit 1; } + @echo ok @rm -rf coverage; mkdir -p coverage @cp *.gcno coverage @for T in $(oceani_tests); do \ - echo -n "Test $$T ... "; \ + echo -n "Test $$T.. "; \ i="$$IFS"; IFS=,; set $$T; IFS="$$i"; t=$$1; shift; \ ./md2c oceani-tests.mdc "output: $$T" | grep -v '^#' > .tmp.want; \ ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > .tmp.have; \ if ! cmp -s .tmp.want .tmp.have; then \ echo "FAILED"; diff -u .tmp.want .tmp.have ; exit 1; fi ;\ - echo -n "passed ... "; \ - if ! valgrind ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} \ - > /dev/null 2> .tmp.valg; then \ - echo "valgrind FAILED"; cat .tmp.valg; exit 1; fi ; \ - echo -n "valgrind passed ... "; \ + echo -n "printing.. "; \ echo '``````' > .tmp.code1; echo '``````' > .tmp.code2 ;\ ./oceani --noexec --print --section "test: $$t" oceani-tests.mdc >> .tmp.code1; \ - ./oceani --noexec --print .tmp.code1 >> .tmp.code2 ;\ + ./oceani --noexec --print .tmp.code1 >> .tmp.code2 || exit 1;\ if ! cmp -s .tmp.code1 .tmp.code2; then \ - echo "Printing Failed"; diff -u .tmp.code1 .tmp.code2; exit1 ; fi ; \ - echo "Printing passed"; \ + echo " Failed"; diff -u .tmp.code1 .tmp.code2; exit 1 ; fi ; \ + echo -n "extra-newlines.. "; \ + sed -e 'i\ + ' .tmp.code1 > .tmp.code1a; \ + echo '``````' > .tmp.code2a ;\ + ./oceani --noexec --print .tmp.code1a >> .tmp.code2a || exit 1;\ + if ! cmp -s .tmp.code1 .tmp.code2a; then \ + echo " Failed"; diff -u .tmp.code1 .tmp.code2a; exit 1; fi ; \ + echo -n "exec-after-print.. "; \ + ./oceani .tmp.code1 $${1+"$$@"} > .tmp.have ; \ + if ! cmp -s .tmp.want .tmp.have; then \ + echo " FAILED"; diff -u .tmp.want .tmp.have; exit 1;fi; \ + echo " all passed"; \ ./coverage_oceani --print --section "test: $$t" oceani-tests.mdc $${1+"$$@"} > /dev/null ; \ ./coverage_oceani -tpbn --section "test: $$t" oceani-tests.mdc > /dev/null 2>&1; \ done ## test code + @for i in coverage/#*.gcda; do mv $$i coverage/$${i##*#}; done @gcov -o coverage oceani.mdc > /dev/null 2> /dev/null - @mv *.gcov coverage ; [ -f .gcov ] && mv .gcov coverage - @ awk '/^ *[1-9]/ {ran+=1} /^ *###/ {skip+=1} \ + @mv *.gcov coverage ; [ -f .gcov ] && mv .gcov coverage || true + @ awk '/NOTEST/ { next } /^ *[1-9]/ {ran+=1} /^ *###/ {skip+=1} \ END {printf "coverage: %6.2f%%\n", ran * 100 / (ran + skip); \ - if (ran < (ran + skip) *0.8) exit(1) }' \ + if (ran < (ran + skip) *0.959) exit(1) }' \ coverage/oceani.mdc.gcov @rm -f .tmp* + @[ -n "$$SKIP_VALGRIND" ] || for T in $(oceani_tests); do \ + echo -n "Valgrind $$T.. "; \ + i="$$IFS"; IFS=,; set $$T; IFS="$$i"; t=$$1; shift; \ + if ! valgrind --error-exitcode=1 --log-file=.tmp.valg ./oceani --section "test: $$t" oceani-tests.mdc $${1+"$$@"} \ + > /dev/null 2>&1 ; then \ + echo "FAILED"; cat .tmp.valg; exit 1; fi ; \ + if grep 'LEAK SUMMARY' .tmp.valg > /dev/null; then \ + echo "valgrind found LEAKS"; cat .tmp.valg ; exit 1 ; fi; \ + if grep 'in use at exit [1-9]' .tmp.valg > /dev/null; then \ + echo "valgrind found memory in use at exit"; cat .tmp.valg ; exit 1 ; fi; \ + echo " passed"; \ + done + coverage_oceani: oceani.c $(CC) $(CFLAGS) --coverage -fprofile-dir=coverage -o coverage_oceani oceani.c $(LDLIBS) @@ -83,23 +108,39 @@ calculations on them. ###### test list oceani_tests += "valvar" - ###### test: valvar - program: - a := 23; b:=12 + func main + argv:[argc::]string + do + a := 23; b:=12 ; b1 := -b print a, b, a+b, a-b, a*b, a/b, a%b print ab, a>=b, a 0.000000001 - do: + do guess = (guess + (target / guess) ) / 2 print count, guess count = count + 1 print "error is ", target - guess * guess + for j:=0; then j = j+3 ; while j < 10: + if j != 0 and then 20 / j > 3: + print "20 /", j," =", 20 / j + else + print "I won't calculate 20 /", j + pi ::= 3.1415926535897 + if 355/113 == pi or else +(pi - 355/113) < 0.001: + print "Close enough" + print "lower" if 355/113 < pi else "higher" + + if pi > 3 then print "pi exceeds three"; else print "need more pie" + if (pi < 3) { print "not enough pi" } else { print "pi sufficient" } + for { i := 0; sum := 0 } + then { i = i+1 } + while i <= 10: + sum = sum + i + else + pass + print "sum 1..10 is", sum + if + PI1 := 22/7 + use PI1 < pi + then + print "Smaller" + else + print 'larger' + ###### output: cond_loop Success 1 2 4 8 16 32 64 @@ -158,6 +283,16 @@ Now we need to test if/else and some different loops 2 1.41422 3 1.41421 error is -4.51095e-12 + I won't calculate 20 / 0 + 20 / 3 = 6.66667 + 20 / 6 = 3.33333 + I won't calculate 20 / 9 + Close enough + higher + pi exceeds three + pi sufficient + sum 1..10 is 55 + larger ## Say Hello @@ -165,19 +300,22 @@ The demonstration code presented in the interpreted is suitable for the test sui Here I break it into two parts, keeping the array code separate. ###### test list - oceani_tests += "sayhello,55,33" - oceani_tests += "sayhello,12,60" + oceani_tests += "sayhello,55,33,hello,True" + oceani_tests += "sayhello,12,60,there,False" ###### test: sayhello - program A B: + func main(av:[ac::number]string) + A := $av[1]; B := $av[2] + astr := av[3] + bbool := av[ac-1] == "True" print "Hello World, what lovely oceans you have!" /* When a variable is defined in both branches of an 'if', * and used afterwards, the variables are merged. */ if A > B: bigger := "yes" - else: + else bigger := "no" print "Is", A, "bigger than", B,"? ", bigger /* If a variable is not used after the 'if', no @@ -186,26 +324,27 @@ Here I break it into two parts, keeping the array code separate. if A > B * 2: double:string = "yes" print A, "is more than twice", B, "?", double - else: + else double := B*2 print "double", B, "is", double a : number a = A; b:number = B + print "still", bigger // check for regression in scoping if a > 0 and b > 0: while a != b: if a < b: b = b - a - else: + else a = a - b print "GCD of", A, "and", B,"is", a else if a <= 0: print a, "is not positive, cannot calculate GCD" - else: + else print b, "is not positive, cannot calculate GCD" - for: + for togo := 10 f1 := 1; f2 := 1 print "Fibonacci:", f1,f2, @@ -217,50 +356,60 @@ Here I break it into two parts, keeping the array code separate. f2 = f3 print "" + if bbool: + print astr ++ " was the str" + else + print "I found the str over " ++ astr + /* Binary search... */ - for: + for lo:= 0; hi := 100 target := 77 - while: + while mid := (lo + hi) / 2 if mid == target: use Found if mid < target: lo = mid - else: + else hi = mid if hi - lo < 1: + lo = mid use GiveUp use True - do: pass + do pass case Found: print "Yay, I found", target case GiveUp: - print "Closest I found was", mid + print "Closest I found was", lo -###### output: sayhello,55,33 +###### output: sayhello,55,33,hello,True Hello World, what lovely oceans you have! Is 55 bigger than 33 ? yes double 33 is 66 + still yes GCD of 55 and 33 is 11 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144 + hello was the str Closest I found was 77.3438 -###### output: sayhello,12,60 +###### output: sayhello,12,60,there,False Hello World, what lovely oceans you have! Is 12 bigger than 60 ? no double 60 is 120 + still no GCD of 12 and 60 is 12 Fibonacci: 1 1 2 3 5 8 13 21 34 55 89 144 + I found the str over there Closest I found was 77.3438 ###### test list oceani_tests += "insert_sort" ###### test: insert_sort - program: + func main() size::=55 list:[size]number - list[0] = 1234 + list[0] = 1_234 for i:=1; then i = i + 1; while i < size: n := list[i-1] * list[i-1] list[i] = (n / 100) % 10000 @@ -393,6 +542,77 @@ Here I break it into two parts, keeping the array code separate. list[ 53 ]= 9040 list[ 54 ]= 9768 +## Arrays + +We already have some array tests, but this is where we put other +ad-hoc things array related. + +###### test list + oceani_tests += arrays + +###### test: arrays + + func main() + bools:[5]Boolean + strings:[4]string + + bools[3] = strings[1] == "Hello" + bools[1] = strings[2] <= "there" + + for i:=0; then i=i+1; while i<5: + j ::= i + ra:[j]number + ra[i-1] = i*i + ra[6] = 42 // mustn't crash + print '', bools[i], ra[j-1], + print + +###### output: arrays + False 0 True 1 False 4 False 9 False 16 + +## Structures + +Time to test if structure declarations and accesses work correctly. + +###### test list + oceani_tests += structs + +###### test: structs + + const three ::= 3 + struct foo + size:[three]number + name:string + active:Boolean = True + + struct baz { a:number; b:Boolean; } + + func main + do + info:[4]foo + + for i:=0; then i=i+1; while i < 4: + switch i + case 2: nm:= "peter" + case 0: nm:= "bob" + case 1: nm:= "jane" + else nm:= "janine" + + info[i].name = nm + info[i].size[0] = i*i + if nm != "jane": + info[i].active = False + + for i:=0; then i=i+1; while i < 4: + print info[i].name, info[i].active, info[i].size[0] + +###### output: structs + + bob False 0 + jane True 1 + peter False 4 + janine False 9 + ## Test code with syntax errors Syntax errors aren't handled well yet - the result is almost always a @@ -429,14 +649,14 @@ separate file first, then run from there. ###### test: syn1 - program: + func main() if then else while do ###### output: syn1 - .tmp.code:3:11: error: unhandled parse error: then + .tmp.code:3:11: Syntax error in statement: then ###### test: tokerr - program: + func main() a := 1i // imaginary numbers aren't understood b:[2i]number // array sizes are handled separately c:[3.14159]Boolean // array sizes must be integers @@ -453,6 +673,9 @@ separate file first, then run from there. zzsize := 4 zz:[zzsize]string // size must be constant, use ::= + // These numbers should be bad in all contexts: FIXME + aa:[00123]number + ###### output: tokerr .tmp.code:3:13: error: unsupported number suffix: 1i .tmp.code:4:11: error: unsupported number suffix: 2i @@ -466,6 +689,195 @@ separate file first, then run from there. .tmp.code:14:11: error: undefined type: unknown .tmp.code:15:12: error: name undeclared: unknowable .tmp.code:17:12: error: array size must be a constant: zzsize + .tmp.code:20:12: error: unrecognised number: 00123 + +## Tests for type errors + +Type error don't cause parsing to abort, so we can fit many in the +one test program. Some type errors are found during the parse, others +during type analysis which doesn't run if parsing failed. So we cannot +fit everything in one. + +These programs were generated by looking for the +various places that `type_err()` are called. + +###### test list + oceani_failing_tests += type_err1 type_err2 type_err3 type_err4 + +###### test: type_err1 + + func main() + print "hello" ++ 5, 5 ++ "hello" + + b ::= 3 + b = b + 1 + + if 3 * 4 and not True: print "Weird" + +###### output: type_err1 + .tmp.code:3:25: error: expected string found number + .tmp.code:3:28: error: expected string found number + .tmp.code:6:8: error: Cannot assign to a constant: b + .tmp.code:5:8: info: name was defined as a constant here + .tmp.code:6:8: error: Cannot assign to a constant: b + .tmp.code:5:8: info: name was defined as a constant here + .tmp.code:8:11: error: Arithmetic returns number but Boolean expected + oceani: type error in program - not running. + +###### test: type_err2 + + func main() + a := 1 + a := 2 + a ::= 3 + a:number = 4 + a ::number = 5 + c: + +###### output: type_err2 + .tmp.code:4:8: error: variable 'a' redeclared + .tmp.code:3:8: info: this is where 'a' was first declared + .tmp.code:5:8: error: variable 'a' redeclared + .tmp.code:3:8: info: this is where 'a' was first declared + .tmp.code:6:8: error: variable 'a' redeclared + .tmp.code:3:8: info: this is where 'a' was first declared + .tmp.code:7:8: error: variable 'a' redeclared + .tmp.code:3:8: info: this is where 'a' was first declared + .tmp.code:8:8: Variable declared with no type or value: c + +###### test: type_err3 + + struct foo + a: number + b:string = "hello" + + func main() + c := "hello" + c = c + 1 + c = "hello" ++ (True and False) + c = 4 < 5 + print 45 + ( "Hello" ++ "there") + c[5] = 1 + + while + use 1 + use True + use "Hello" + do + print + case 1: print "one" + case "Hello": print "Hello" + + a1:[5]number; a2:[5]number; a3:[10]number; a4:[5]string + a1 = a2 + a1 = a3 + a1 = a4 + a1[2] = "hello" + a4[1] = True + c = a2[3] + + bar:foo + foo.c = 43 + print c.foo + print bar.c + print bar.b + 42 + + + // trigger 'labels not permitted' error message + while 1 if True else False: + print + case 2: print "two" + +###### output: type_err3 + .tmp.code:8:12: error: expected number but variable 'c' is string + .tmp.code:7:8: info: this is where 'c' was set to string + .tmp.code:8:12: error: Arithmetic returns number but string expected + .tmp.code:7:8: info: variable 'c' was set as string here. + .tmp.code:9:24: error: Boolean operation found where string expected + .tmp.code:10:12: error: Comparison returns Boolean but string expected + .tmp.code:7:8: info: variable 'c' was set as string here. + .tmp.code:11:21: error: Concat returns string but number expected + .tmp.code:12:8: error: string cannot be indexed + .tmp.code:12:8: error: string cannot be indexed + .tmp.code:21:13: error: expected number found string + .tmp.code:17:16: error: expected number, found string + .tmp.code:24:8: error: cannot assign value of type [5]number + .tmp.code:25:13: error: expected [5]number but variable 'a3' is [10]number + .tmp.code:23:36: info: this is where 'a3' was set to [10]number + .tmp.code:25:8: error: cannot assign value of type [5]number + .tmp.code:26:13: error: expected [5]number but variable 'a4' is [5]string + .tmp.code:23:51: info: this is where 'a4' was set to [5]string + .tmp.code:26:8: error: cannot assign value of type [5]number + .tmp.code:27:16: error: expected number found string + .tmp.code:28:16: error: expected string found Boolean + .tmp.code:29:12: error: have number but need string + .tmp.code:7:8: info: variable 'c' was set as string here. + .tmp.code:32:8: error: variable used but not declared: foo + .tmp.code:32:8: error: field reference attempted on none, not a struct + .tmp.code:32:16: error: expected none found number + .tmp.code:33:14: error: field reference attempted on string, not a struct + .tmp.code:34:14: error: cannot find requested field in foo + .tmp.code:35:17: error: have string but need number + .tmp.code:39:29: error: expected number (labels not permitted) found Boolean + oceani: type error in program - not running. + +###### test: type_err4 + func main() + a:=1; b=2; c::=3 + print a, b, c + +###### output: type_err4 + .tmp.code:3:14: error: variable used but not declared: b + .tmp.code:3:16: error: expected none found number + .tmp.code:3:14: info: variable 'b' was set as none here. + oceani: type error in program - not running. + +###### test list + oceani_failing_tests += type_err_const type_err_const1 missing_program + +###### test: type_err_const + const + foo :: number = 45 + bar ::= "string" + 56 + const + bar ::= "baz" + func main() + foo := 4 + print foo, bar + + // trigger duplicate-main error + func main() + foo := 6 + print bar, foo + +###### output: type_err_const + .tmp.code:4:16: error: expected number found string + .tmp.code:6:8: error: name already declared: bar + .tmp.code:4:8: info: this is where 'bar' was first declared + .tmp.code:8:8: error: variable 'foo' redeclared + .tmp.code:3:8: info: this is where 'foo' was first declared + .tmp.code:13:8: error: variable 'foo' redeclared + .tmp.code:3:8: info: this is where 'foo' was first declared + .tmp.code:13:8: "main" defined a second time + +###### test: type_err_const1 + const + foo : number = 45 + bar := "string" + func main() + foo := 4 + print foo, bar + +###### output: type_err_const1 + .tmp.code:3:12: Syntax error in constant: : + .tmp.code:4:12: Syntax error in constant: : + +###### test: missing_program + const + foo::="bar" + +###### output: missing_program + oceani: no main function found. ## Test erroneous command line args @@ -489,7 +901,6 @@ command list possible. ./coverage_oceani $${1+"$$@"} > /dev/null 2>&1 ;\ done || true - ###### test list oceani_special_tests += "cmd" oceani_special_tests += "cmd,-zyx" @@ -502,7 +913,7 @@ command list possible. ###### output: cmd,-zyx ./oceani: invalid option -- 'z' - Usage: oceani --trace --print --noexec --brackets--section=SectionName prog.ocn + Usage: oceani --trace --print --noexec --brackets --section=SectionName prog.ocn ###### output: cmd,nofile oceani: cannot open nofile @@ -512,4 +923,3 @@ command list possible. ###### output: cmd,--section,toast:nothing,oceani-tests.mdc oceani: cannot find section toast:nothing -