Author Topic: Test runner  (Read 2738 times)

0 Members and 1 Guest are viewing this topic.

Offline luke

  • Administrator
  • Seasoned Forum Regular
  • Posts: 324
    • View Profile
Test runner
« on: March 06, 2021, 07:03:34 am »
About
--------
This is a tool for running a program many times with varying input, and checking the output against expected values. In this way a program's correctness can be tested, (hopefully) making the programmer more confident of the absence of bugs. It can also help with confirming no bugs have been introduced later on, and that fixed bugs stay fixed.

This was written as part of the L-BASIC project, but is fairly generic and should work with any program that takes a filename in COMMAND$ and prints output to the console (i.e. use $CONSOLE:ONLY). Thanks to @Ed Davis for providing Windows compatibility patches.

Usage
---------
Let's say we have a simple program, save it as sample.bas and compile:
Code: QB64: [Select]
  1.     Line Input #1, l$
  2.     If InStr(l$, "Q") Then
  3.         Print "Error, found a Q"
  4.         System 1 'Exit with an error if we read a Q
  5.     End If
  6.     Print UCase$(l$)
It reads a file and prints out the contents in upper case, but requires the input not contain the letter Q: that is considered invalid input and will give an error.

Now we can write some test cases! The format should be fairly clear; the file is one or more test cases, delimited with various $commands:
Code: [Select]
$title: Letters are uppercased
the rain in spain
$expect: stdout
THE RAIN IN SPAIN
$finish

$title: Uppercased letters stay uppercased
The rain in Spain
$expect: stdout
THE RAIN IN SPAIN
$finish

$title: Multi-line input
Now is the time for
all good men to
come to the aid
of their party
$expect: stdout
NOW IS THE TIME FOR
ALL GOOD MEN TO
COME TO THE AID
OF THEIR PARTY
$finish

$title: Bad input is caught
This has a Q in it
$expect: error

    The structure is:
    • Each test begins with $title to give the test an identifiying name.
    • The contents of the input file is provided
    • $expect: stdout indicates that we should check the standard (i.e. console) output of the program
    • The expected output is provided
    • $finish indicates the end of the expected output
    If instead an error is expected, we can $expect: error. No expected output or $finish is required then.

    $expect: stdout strips leading and trailing whitespace before comparing. Use $expect: stdout_exact if you don't want that.

    Now you can run it (on Mac/Linux, use './sample'):
Code: [Select]
test-runner.exe sample.exe our-tests.txt

and get an output similar to this:
Code: [Select]
test:Letters are uppercased: OK
test:Uppercased letters stay uppercased: OK
test:Multi-line input: OK
test:Bad input is caught: OK
Total 4/4 OK in 4 seconds

If any test failed, it would show us the expected and actual output to help fix the program.

Code: QB64: [Select]
  1. 'Copyright 2020 Luke Ceddia
  2. 'SPDX-License-Identifier: Apache-2.0
  3. 'test.bas - Run test suite
  4.  
  5. DefLng A-Z
  6.  
  7. Type test_unit_t
  8.     title As String
  9.     program As String
  10.     expect As String
  11.     expected_output As String
  12. ReDim tests(0) As test_unit_t
  13. Dim active_section '0 = none, 1 = program, 2 = output
  14.  
  15. On Error GoTo ehandler
  16.  
  17.     Print "Usage: "; Command$(0); " <test program> <test files>"
  18.     System 1
  19.  
  20. If InStr(_OS$, "WINDOWS") Then
  21.     tmpdir$ = Environ$("TEMP") + "\"
  22.     tmpdir$ = "/tmp/"
  23.  
  24. testbinary$ = Command$(1)
  25. For cmdline_index = 2 To _CommandCount
  26.     Open Command$(cmdline_index) For Binary As #1
  27.     While Not EOF(1)
  28.         Line Input #1, l$
  29.         lt$ = LTrim$(l$)
  30.         If Left$(lt$, 7) = "$title:" Then
  31.             active_test = UBound(tests) + 1
  32.             ReDim _Preserve tests(1 To active_test) As test_unit_t
  33.             tests(active_test).title = basename$(Command$(cmdline_index)) + ":" + LTrim$(Mid$(lt$, 8))
  34.             active_section = 1
  35.         ElseIf Left$(lt$, 8) = "$expect:" Then
  36.             tests(active_test).expect = LTrim$(Mid$(lt$, 9))
  37.             active_section = 2
  38.         ElseIf Left$(lt$, 7) = "$finish" Then
  39.             active_section = 0
  40.         ElseIf active_section = 1 Then
  41.             tests(active_test).program = tests(active_test).program + l$ + Chr$(10)
  42.         ElseIf active_section = 2 Then
  43.             tests(active_test).expected_output = tests(active_test).expected_output + l$ + Chr$(10)
  44.         ElseIf lt$ = "" Or Left$(lt$, 1) = "#" Or Left$(lt$, 1) = "'" Then
  45.             'Blank line or comment, do nothing
  46.         Else
  47.             Print "Must start with $title"
  48.             System 1
  49.         End If
  50.     Wend
  51.     Close #1
  52. Next cmdline_index
  53.  
  54. starttime! = Timer(0.001)
  55. For active_test = 1 To UBound(tests)
  56.     'print "TITLE: "; tests(active_test).title
  57.     'print "PROGRAM"
  58.     'print "-------"
  59.     'print tests(active_test).program;
  60.     'print "EXPECT: "; tests(active_test).expect
  61.     'print tests(active_test).expected_output;
  62.  
  63.     filename$ = tmpdir$ + "test-" + rndhex$(4)
  64.     Open filename$ + ".bas" For Output As #1
  65.     Print #1, tests(active_test).program
  66.     Close #1
  67.     retcode = Shell(testbinary$ + " " + filename$ + ".bas > " + filename$ + ".output")
  68.     Print tests(active_test).title; ": ";
  69.     Select Case tests(active_test).expect
  70.         Case "error"
  71.             If retcode > 0 Then
  72.                 Print "OK"
  73.                 successes = successes + 1
  74.             Else
  75.                 Print "Failed, expected error but ran successfully."
  76.             End If
  77.         Case "stdout", "stdout_exact"
  78.             Open filename$ + ".output" For Binary As #1
  79.             actual_output$ = Space$(LOF(1))
  80.             Get #1, , actual_output$
  81.             Close #1
  82.             actual_output$ = remove_char$(actual_output$, Chr$(13))
  83.             tests(active_test).expected_output = remove_char$(tests(active_test).expected_output, Chr$(13))
  84.  
  85.             If tests(active_test).expect <> "stdout_exact" Then
  86.                 actual_output$ = strip$(actual_output$)
  87.                 tests(active_test).expected_output = strip$(tests(active_test).expected_output)
  88.             End If
  89.  
  90.             If retcode > 0 Then
  91.                 Print "Failed with error, output was: "; actual_output$
  92.             ElseIf actual_output$ = tests(active_test).expected_output Then
  93.                 Print "OK"
  94.                 successes = successes + 1
  95.             Else
  96.                 Print "Failed!"
  97.                 Print "Expected: "; tests(active_test).expected_output;
  98.                 Print "  Actual: "; actual_output$;
  99.             End If
  100.         Case Else
  101.             Print "Unknown condition"
  102.     End Select
  103.     Kill filename$ + ".bas"
  104.     Kill filename$ + ".output"
  105. Next active_test
  106. endtime! = Timer(0.001)
  107.  
  108. Print "Total"; Str$(successes); "/"; LTrim$(Str$(UBound(tests))); " OK in"; Int((endtime! - starttime!) * 10) / 10; "seconds"
  109.  
  110.  
  111. ehandler:
  112. Print "Error"; Err; "on line"; _ErrorLine
  113.  
  114. Function basename$ (path$)
  115.     dot = _InStrRev(path$, ".")
  116.     slash = _InStrRev(path$, "/")
  117.     basename$ = Mid$(path$, slash + 1, dot - slash - 1)
  118.  
  119. Function rndhex$ (length)
  120.     For i = 1 To length
  121.         result$ = result$ + Hex$(Int(Rnd * 256))
  122.     Next i
  123.     rndhex$ = result$
  124.  
  125. 'Courtesy Ed Davis
  126. Function remove_char$ (s$, c$)
  127.     Dim s2$
  128.     Dim i As Integer
  129.  
  130.     s2$ = ""
  131.     For i = 1 To Len(s$)
  132.         If Mid$(s$, i, 1) <> c$ Then
  133.             s2$ = s2$ + Mid$(s$, i, 1)
  134.         End If
  135.     Next
  136.     remove_char$ = s2$
  137.  
  138. Function strip$ (s$)
  139.     start = 0
  140.     whitespace$ = Chr$(10) + Chr$(9) + Chr$(32)
  141.     Do
  142.         start = start + 1
  143.     Loop While InStr(whitespace$, Mid$(s$, start, 1))
  144.     finish = Len(s$) + 1
  145.     Do
  146.         finish = finish - 1
  147.     Loop While InStr(whitespace$, Mid$(s$, finish, 1))
  148.     strip$ = Mid$(s$, start, finish - start + 1)
  149.  
« Last Edit: March 06, 2021, 07:05:51 am by luke »