' Gambas class file ''' Low-level parser for Test Anything Protocol ([TAP]) output. ''' It can only be used for one test process. ''' ''' [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 $iLine As Integer = 0 Private $iTestsRun As Integer = 0 Public Sub Parse(TapStream As Stream) While Not Eof(TapStream) ParseLine(TapStream.ReadLine()) Wend End Public Sub ParseLine({Line} As String) Dim sLine As String = {Line} Dim iVersion As Integer Dim bResult As Boolean Dim iTestNr As Integer Dim sDescription As String Dim iDirective As Integer Dim sComment As String 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 End 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