diff --git a/.src/Tap/Tap.module b/.src/Tap/Tap.module new file mode 100644 index 000000000..cb14d4c59 --- /dev/null +++ b/.src/Tap/Tap.module @@ -0,0 +1,3 @@ +' Gambas module file + +Public Enum NONE = 0, TODO = 1, SKIP diff --git a/.src/Tap/TapParser.class b/.src/Tap/TapParser.class new file mode 100644 index 000000000..966af977e --- /dev/null +++ b/.src/Tap/TapParser.class @@ -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 diff --git a/.src/Tap/TapPrinter.class b/.src/Tap/TapPrinter.class new file mode 100644 index 000000000..839f6c67c --- /dev/null +++ b/.src/Tap/TapPrinter.class @@ -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 diff --git a/.src/Tap/TapTester.module b/.src/Tap/TapTester.module new file mode 100644 index 000000000..2bb62d6dd --- /dev/null +++ b/.src/Tap/TapTester.module @@ -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