Low-level printer and parser for TAP format
The Test Anything Protocol (TAP) is a text-based format for communicating unit test results. It originated from perl and has a specification here: http://testanything.org/
This commit is contained in:
parent
2d591f341b
commit
0929707aee
4 changed files with 368 additions and 0 deletions
3
.src/Tap/Tap.module
Normal file
3
.src/Tap/Tap.module
Normal file
|
@ -0,0 +1,3 @@
|
|||
' Gambas module file
|
||||
|
||||
Public Enum NONE = 0, TODO = 1, SKIP
|
175
.src/Tap/TapParser.class
Normal file
175
.src/Tap/TapParser.class
Normal file
|
@ -0,0 +1,175 @@
|
|||
' Gambas class file
|
||||
|
||||
''' Low-level parser for Test Anything Protocol ([TAP]) output.
|
||||
'''
|
||||
''' [TAP]: http://testanything.org/tap-specification.html
|
||||
|
||||
Export
|
||||
|
||||
Event Version(Version As Integer)
|
||||
|
||||
Event Plan(Start As Integer, {End} As Integer)
|
||||
|
||||
Event Ok(TestNr As Integer, Description As String)
|
||||
Event NotOk(TestNr As Integer, Description As String)
|
||||
Event Todo(TestNr As Integer, Comment As String)
|
||||
Event Skip(TestNr As Integer, Comment As String)
|
||||
|
||||
Event BailOut(Comment As String)
|
||||
Event Diagnostic(Comment As String)
|
||||
Event Else({Line} As String)
|
||||
|
||||
Private $hTapStream As Stream
|
||||
|
||||
Private $iTestsRun As Integer
|
||||
|
||||
Public Sub _new(TapStream As Stream)
|
||||
|
||||
$hTapStream = TapStream
|
||||
|
||||
End
|
||||
|
||||
Public Sub Parse()
|
||||
|
||||
Dim sLine As String
|
||||
Dim iLine As Integer = 0
|
||||
Dim iVersion As Integer
|
||||
|
||||
Dim bResult As Boolean
|
||||
Dim iTestNr As Integer
|
||||
Dim sDescription As String
|
||||
Dim iDirective As Integer
|
||||
Dim sComment As String
|
||||
|
||||
$iTestsRun = 0
|
||||
|
||||
For Each sLine In $hTapStream.Lines
|
||||
If iLine = 0 And If sLine Begins "TAP version " Then
|
||||
Try iVersion = CInt(Trim$(Mid$(sLine, 13)))
|
||||
' At present a "TAP version" line is always an error:
|
||||
' 1. It might not be an integer, which is an error
|
||||
' 2. A version lower than 13 is an error by the specification
|
||||
' 3. We don't support version 13 or above.
|
||||
If Error Then Error.Raise(Subst$(("Unrecognised TAP version '&1'"), Trim$(Mid$(sLine, 13))))
|
||||
If iVersion < 13 Then Error.Raise(Subst$(("Illegal TAP version '&1'"), iVersion))
|
||||
If iVersion > 12 Then Error.Raise(Subst$(("Unsupported TAP version &1"), iVersion))
|
||||
Endif
|
||||
If iLine = 0 Then Raise Version(12)
|
||||
|
||||
If sLine Match "\\d+..\\d+" Then
|
||||
With Scan(sLine, "*..*")
|
||||
If Not IsInteger(.[0]) Or If Not IsInteger(.[1]) Then Error.Raise(Subst$(("Couldn't extract test plan from '&1'"), sLine))
|
||||
Raise Plan(CInt(.[0]), CInt(.[1]))
|
||||
End With
|
||||
|
||||
Else If sLine Begins "ok" Or If sLine Begins "not ok" Then
|
||||
bResult = ParseTest(sLine, ByRef iTestNr, ByRef sDescription, ByRef iDirective, ByRef sComment)
|
||||
' A single line may raise two events: Ok or NotOk, depending on the result
|
||||
' and Todo or Skip or none of the two depending on the directive.
|
||||
' Use the TestNr argument to link the two when counting tests for statistics.
|
||||
If bResult Then
|
||||
Raise Ok(iTestNr, sDescription)
|
||||
Else
|
||||
Raise NotOk(iTestNr, sDescription)
|
||||
Endif
|
||||
|
||||
If iDirective = Tap.TODO Then
|
||||
Raise Todo(iTestNr, sComment)
|
||||
Else If iDirective = Tap.SKIP Then
|
||||
Raise Skip(iTestNr, sComment)
|
||||
Endif
|
||||
|
||||
Else If sLine Begins "Bail out!" Then
|
||||
Raise BailOut(Trim$(Mid$(sLine, 10)))
|
||||
|
||||
Else If sLine Begins "#" Then
|
||||
Raise Diagnostic(Trim$(Mid$(sLine, 2)))
|
||||
|
||||
Else
|
||||
Raise Else(sLine)
|
||||
Endif
|
||||
|
||||
Inc iLine
|
||||
Next
|
||||
|
||||
End
|
||||
|
||||
' This pattern works in perl but not in gb.pcre... So doing it by hand below.
|
||||
' Dim rTest As RegExp
|
||||
' Try rTest = New RegExp(sLine, Replace$("(ok|not ok)WS(\\d+WS)?([^#]+WS)?(?:#\\s*((?i)TODO|SKIP(?-i))WS(.*)?)?", "WS", "(?:\\s+|$)"))
|
||||
' Inc $iTestsRun
|
||||
' Try TestNr = CInt(rTest[2].Text)
|
||||
' If Error Then TestNr = $iTestsRun
|
||||
' Description = rTest[3].Text
|
||||
' Directive = IIf(rTest[4].Text, IIf(Upper$(rTest[4].Text) = "TODO", Tap.TODO, Tap.SKIP), Tap.NONE)
|
||||
' Comment = rTest[5].Text
|
||||
'
|
||||
' Return rTest[1].Text = "ok"
|
||||
|
||||
Private Function ParseTest(sLine As String, ByRef TestNr As Integer, ByRef Description As String, ByRef Directive As Integer, ByRef Comment As String) As Boolean
|
||||
|
||||
Dim bResult As Boolean
|
||||
Dim aWords As String[]
|
||||
Dim sWord As String
|
||||
|
||||
' Tidy up caller's local variables
|
||||
TestNr = 0
|
||||
Description = ""
|
||||
Directive = Tap.NONE
|
||||
Comment = ""
|
||||
|
||||
Inc $iTestsRun
|
||||
|
||||
' "ok" or "not ok"
|
||||
If sLine Begins "ok" Then
|
||||
bResult = True
|
||||
sLine = Trim$(Mid$(sLine, 3))
|
||||
Else If sLine Begins "not ok"
|
||||
bResult = False
|
||||
sLine = Trim$(Mid$(sLine, 7))
|
||||
Else
|
||||
Error.Raise(Subst$(("Not a test line '&1'"), sLine))
|
||||
Endif
|
||||
|
||||
' Make sure that if a "#" occurs, it will be after a space
|
||||
sLine = Replace$(sLine, "#", " #")
|
||||
aWords = Split(sLine, " ", "", True).Reverse()
|
||||
|
||||
' TestNr
|
||||
Try sWord = aWords.Pop()
|
||||
Try TestNr = CInt(sWord)
|
||||
If Error Then
|
||||
aWords.Push(sWord)
|
||||
TestNr = $iTestsRun
|
||||
Endif
|
||||
|
||||
' Description
|
||||
While aWords.Count
|
||||
sWord = aWords.Pop()
|
||||
If sWord Begins "#" Then Break
|
||||
Description &= sWord & " "
|
||||
Wend
|
||||
Description = Trim$(Description)
|
||||
|
||||
' Directive
|
||||
If sWord Begins "#" Then
|
||||
If sWord = "#" Then
|
||||
Try sWord = aWords.Pop()
|
||||
If Error Then Error.Raise(("Premature end of directive"))
|
||||
Endif
|
||||
Select Case Lower$(sWord)
|
||||
Case "todo"
|
||||
Directive = Tap.TODO
|
||||
Case "skip"
|
||||
Directive = Tap.SKIP
|
||||
Default
|
||||
Error.Raise(Subst$(("Invalid directive '&1'"), sWord))
|
||||
End Select
|
||||
Endif
|
||||
|
||||
' Comment
|
||||
Comment = Trim$(aWords.Reverse().Join(" "))
|
||||
|
||||
Return bResult
|
||||
|
||||
End
|
79
.src/Tap/TapPrinter.class
Normal file
79
.src/Tap/TapPrinter.class
Normal file
|
@ -0,0 +1,79 @@
|
|||
' Gambas class file
|
||||
|
||||
''' Low-level class for planning and printing test results in Test Anything Protocol ([TAP]) format.
|
||||
'''
|
||||
''' [TAP]: http://testanything.org/tap-specification.html
|
||||
|
||||
Export
|
||||
|
||||
Private Const NO_PLAN As Integer = -1
|
||||
|
||||
Private $hOutput As Stream
|
||||
Private $iPlan As Integer = NO_PLAN
|
||||
Private $iTestsRun As Integer = 0
|
||||
|
||||
Public Sub _new(Optional Tests As Integer, Optional Comment As String, Optional {Output} As Stream = File.Out)
|
||||
|
||||
$hOutput = {Output}
|
||||
If Not IsMissing(Tests) Then Plan(Tests, Comment)
|
||||
|
||||
End
|
||||
|
||||
Public Sub Plan(Tests As Integer, Optional Comment As String)
|
||||
|
||||
If $iTestsRun Then Error.Raise(Subst$(("Tests already started, at test #&1"), $iTestsRun))
|
||||
' TAP specification lists '1..0 # Skipped: WWW::Mechanize not installed'
|
||||
' as a valid example.
|
||||
If Tests <= NO_PLAN Then Error.Raise(("Number of tests must be non-negative"))
|
||||
$iPlan = Tests
|
||||
Print #$hOutput, "1.."; $iPlan;
|
||||
Print #$hOutput, IIf(Comment, " # " & Comment, "")
|
||||
|
||||
End
|
||||
|
||||
Public Sub Finish()
|
||||
|
||||
If $iPlan > NO_PLAN Then Return ' already printed the "plan" line
|
||||
Print #$hOutput, "1.."; $iTestsRun
|
||||
|
||||
End
|
||||
|
||||
Public Sub Test(Result As Boolean, Optional TestNr As Integer, Optional Description As String, Optional Directive As Integer, Optional Comment As String)
|
||||
|
||||
Dim sDirective As String
|
||||
|
||||
' It is not advised to start a description with a number token because
|
||||
' it will be interpreted as the (optional) test number. We issue a warning
|
||||
' about this but fix it anyway by always printing the TestNr before *and*
|
||||
' prefixing the Description with a dash, as is common.
|
||||
If Description Match "^[0-9]+(\\s|$)" Then
|
||||
Error Subst$(("Warning: Description '&1' should not start with a number"), Description)
|
||||
Endif
|
||||
If Description Like "*#*" Then
|
||||
Error.Raise(Subst$(("Description '&1' may not contain a '#' character"), Description))
|
||||
Endif
|
||||
|
||||
Inc $iTestsRun
|
||||
If Not TestNr Then TestNr = $iTestsRun
|
||||
If Not IsMissing(Directive) Then
|
||||
' Matches the values of the Enum Tap.Todo, Tap.Skip
|
||||
sDirective = Choose(Directive, "TODO", "SKIP")
|
||||
If Not sDirective Then Error.Raise(Subst$(("Unsupported directive '&1'"), Directive))
|
||||
Endif
|
||||
|
||||
Print #$hOutput, Subst$("&1 &2 - &3", IIf(Result, "ok", "not ok"), TestNr, Description);
|
||||
Print #$hOutput, IIf(sDirective, Subst$(" # &1&2", sDirective, IIf(Comment, " " & Comment, "")), "")
|
||||
|
||||
End
|
||||
|
||||
Public Sub BailOut(Optional Comment As String)
|
||||
|
||||
Print #$hOutput, "Bail out!";; Comment
|
||||
|
||||
End
|
||||
|
||||
Public Sub Diagnostic(Comment As String)
|
||||
|
||||
Print #$hOutput, "#";; Comment
|
||||
|
||||
End
|
111
.src/Tap/TapTester.module
Normal file
111
.src/Tap/TapTester.module
Normal file
|
@ -0,0 +1,111 @@
|
|||
' Gambas module file
|
||||
|
||||
Public Sub Main()
|
||||
Dim hPrinter As TapPrinter
|
||||
|
||||
Dim hStream As Stream
|
||||
Dim hParser As TapParser
|
||||
Dim sTap As String = ""
|
||||
"ok - created Board\n"
|
||||
"ok\n"
|
||||
"ok\n"
|
||||
"ok\n"
|
||||
"ok\n"
|
||||
"ok\n"
|
||||
"ok\n"
|
||||
"ok\n"
|
||||
"not ok - gladly it's todo # TODO test\n"
|
||||
"# +------+------+------+------+\n"
|
||||
"# | |16G | |05C |\n"
|
||||
"# | |G N C | |C C G |\n"
|
||||
"# | | G | | C +|\n"
|
||||
"# +------+------+------+------+\n"
|
||||
"# |10C |01G | |03C |\n"
|
||||
"# |R N G |G A G | |C C C |\n"
|
||||
"# | R | G | | C +|\n"
|
||||
"# +------+------+------+------+\n"
|
||||
"# | |01G |17C |00C |\n"
|
||||
"# | |G A G |G N R |R N R |\n"
|
||||
"# | | G | R | G |\n"
|
||||
"# +------+------+------+------+\n"
|
||||
"ok - board has 7 tiles + starter tile\n"
|
||||
"1..9"
|
||||
|
||||
hStream = Open String sTap For Read
|
||||
hParser = New TapParser(hStream) As "Parser"
|
||||
hParser.Parse()
|
||||
Close #hStream
|
||||
|
||||
Print String$(50, "-")
|
||||
|
||||
hStream = Open String For Write
|
||||
hPrinter = New TapPrinter(4, "", hStream)
|
||||
hPrinter.Test(12 = 12)
|
||||
hPrinter.Test(True,, "True is true")
|
||||
hPrinter.Test(False,, "False is true", Tap.SKIP, "This is impossible")
|
||||
hPrinter.Diagnostic("Omitting a test intentionally")
|
||||
hPrinter.Finish()
|
||||
sTap = Close #hStream
|
||||
Print sTap
|
||||
|
||||
Print String$(50, "-")
|
||||
|
||||
hStream = Open String sTap For Read
|
||||
hParser = New TapParser(hStream) As "Parser"
|
||||
hParser.Parse()
|
||||
Close #hStream
|
||||
End
|
||||
|
||||
Public Sub Parser_Ok(TestNr As Integer, Description As String)
|
||||
|
||||
Debug "OK("; TestNr; ")";; Description
|
||||
|
||||
End
|
||||
|
||||
Public Sub Parser_NotOk(TestNr As Integer, Description As String)
|
||||
|
||||
Debug "NOTOK("; TestNr; ")";; Description
|
||||
|
||||
End
|
||||
|
||||
Public Sub Parser_Todo(TestNr As Integer, Comment As String)
|
||||
|
||||
Debug "TODO("; TestNr; ")";; Comment
|
||||
|
||||
End
|
||||
|
||||
Public Sub Parser_Skip(TestNr As Integer, Comment As String)
|
||||
|
||||
Debug "SKIP("; TestNr; ")";; Comment
|
||||
|
||||
End
|
||||
|
||||
Public Sub Parser_Version(Version As Integer)
|
||||
|
||||
Debug "TAP version";; Version
|
||||
|
||||
End
|
||||
|
||||
Public Sub Parser_Plan(Start As Integer, {End} As Integer)
|
||||
|
||||
Debug "PLAN";; Start; ".."; {End}
|
||||
|
||||
End
|
||||
|
||||
Public Sub Parser_BailOut(Comment As String)
|
||||
|
||||
Debug "BAIL";; Comment
|
||||
|
||||
End
|
||||
|
||||
Public Sub Parser_Diagnostic(Comment As String)
|
||||
|
||||
Debug "DIAG";; Comment
|
||||
|
||||
End
|
||||
|
||||
Public Sub Parser_Else({Line} As String)
|
||||
|
||||
Debug "ELSE";; {Line}
|
||||
|
||||
End
|
Loading…
Reference in a new issue