From 2f639accf96ef65e94172ef7d22bf76d569292ac Mon Sep 17 00:00:00 2001 From: jmgomez Date: Thu, 3 Apr 2025 11:42:20 +0100 Subject: [PATCH 1/6] Implements initial version of the test parser --- testparser.nim | 93 +++++++++++++++++++ tests/all.nim | 3 +- tests/projects/nonimbleproject.nim | 1 + .../projects/testproject/src/testproject.nim | 7 ++ .../testproject/src/testproject/submodule.nim | 12 +++ tests/projects/testproject/testproject.nimble | 12 +++ tests/projects/testproject/tests/config.nims | 1 + tests/projects/testproject/tests/test1.nim | 12 +++ .../testproject/tests/testwithsuite.nim | 25 +++++ tests/ttestrunner.nim | 23 +++++ 10 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 testparser.nim create mode 100644 tests/projects/nonimbleproject.nim create mode 100644 tests/projects/testproject/src/testproject.nim create mode 100644 tests/projects/testproject/src/testproject/submodule.nim create mode 100644 tests/projects/testproject/testproject.nimble create mode 100644 tests/projects/testproject/tests/config.nims create mode 100644 tests/projects/testproject/tests/test1.nim create mode 100644 tests/projects/testproject/tests/testwithsuite.nim create mode 100644 tests/ttestrunner.nim diff --git a/testparser.nim b/testparser.nim new file mode 100644 index 0000000..13f0644 --- /dev/null +++ b/testparser.nim @@ -0,0 +1,93 @@ +import std/[tables, options] +import compiler/[ast, idents, msgs, syntaxes, options, pathutils, lineinfos] +# import compiler/[renderer, astalgo] + +type + TestInfo* = object + name*: string + line*: uint + + SuiteInfo* = object + name*: string #empty means global suite + tests*: seq[TestInfo] + line*: uint + + TestFileInfo* = object + testFile*: string + suites*: Table[string, SuiteInfo] + hasErrors*: bool + +proc extractTest(n: PNode, conf: ConfigRef): Option[TestInfo] = + if n.kind in nkCallKinds and + n[0].kind == nkIdent and + n[0].ident.s == "test": + if n.len >= 2 and n[1].kind in {nkStrLit .. nkTripleStrLit}: + return some(TestInfo( + name: n[1].strVal, + line: n.info.line + )) + else: + localError(conf, n.info, "'test' requires a string literal name") + return none(TestInfo) + +proc extract(n: PNode, conf: ConfigRef, result: var TestFileInfo) = + case n.kind + of nkStmtList, nkStmtListExpr: + for child in n: + extract(child, conf, result) + of nkCallKinds: + if n[0].kind == nkIdent: + case n[0].ident.s + of "suite": + if n.len >= 2 and n[1].kind in {nkStrLit .. nkTripleStrLit}: + var suite = SuiteInfo( + name: n[1].strVal, + tests: @[], + line: n.info.line + ) + # Extract tests within the suite's body + if n.len > 2 and n[2].kind == nkStmtList: + for testNode in n[2]: + let testInfo = extractTest(testNode, conf) + if testInfo.isSome: + suite.tests.add(testInfo.get) + + result.suites[suite.name] = suite + else: + localError(conf, n.info, "'suite' requires a string literal name") + result.hasErrors = true + of "test": + # Handle top-level tests (not in a suite) + let testInfo = extractTest(n, conf) + if testInfo.isSome: + result.suites.mgetOrPut("", SuiteInfo( + name: "", + tests: @[], + line: 0 + )).tests.add(testInfo.get) + else: + discard + else: + discard + +proc extractTestInfo*(testFile: string): TestFileInfo = + ## Extract test information from a test file. This parses the test file + ## and extracts suite and test names. + result.testFile = testFile + var conf = newConfigRef() + conf.foreignPackageNotes = {} + conf.notes = {} + conf.mainPackageNotes = {} + conf.errorMax = high(int) + conf.structuredErrorHook = proc( + config: ConfigRef, info: TLineInfo, msg: string, severity: Severity + ) {.gcsafe.} = + localError(config, info, warnUser, msg) + + let fileIdx = fileInfoIdx(conf, AbsoluteFile testFile) + var parser: Parser + if setupParser(parser, fileIdx, newIdentCache(), conf): + extract(parseAll(parser), conf, result) + closeParser(parser) + result.hasErrors = result.hasErrors or conf.errorCounter > 0 + diff --git a/tests/all.nim b/tests/all.nim index 1bc2cfe..a1f8d62 100644 --- a/tests/all.nim +++ b/tests/all.nim @@ -3,4 +3,5 @@ import tnimlangserver, tprojectsetup, textensions, - tmisc \ No newline at end of file + tmisc, + ttestrunner \ No newline at end of file diff --git a/tests/projects/nonimbleproject.nim b/tests/projects/nonimbleproject.nim new file mode 100644 index 0000000..41bb9d3 --- /dev/null +++ b/tests/projects/nonimbleproject.nim @@ -0,0 +1 @@ +echo "hello" \ No newline at end of file diff --git a/tests/projects/testproject/src/testproject.nim b/tests/projects/testproject/src/testproject.nim new file mode 100644 index 0000000..b7a2480 --- /dev/null +++ b/tests/projects/testproject/src/testproject.nim @@ -0,0 +1,7 @@ +# This is just an example to get you started. A typical library package +# exports the main API in this file. Note that you cannot rename this file +# but you can remove it if you wish. + +proc add*(x, y: int): int = + ## Adds two numbers together. + return x + y diff --git a/tests/projects/testproject/src/testproject/submodule.nim b/tests/projects/testproject/src/testproject/submodule.nim new file mode 100644 index 0000000..ad4f1b5 --- /dev/null +++ b/tests/projects/testproject/src/testproject/submodule.nim @@ -0,0 +1,12 @@ +# This is just an example to get you started. Users of your library will +# import this file by writing ``import testproject/submodule``. Feel free to rename or +# remove this file altogether. You may create additional modules alongside +# this file as required. + +type + Submodule* = object + name*: string + +proc initSubmodule*(): Submodule = + ## Initialises a new ``Submodule`` object. + Submodule(name: "Anonymous") diff --git a/tests/projects/testproject/testproject.nimble b/tests/projects/testproject/testproject.nimble new file mode 100644 index 0000000..cb62d12 --- /dev/null +++ b/tests/projects/testproject/testproject.nimble @@ -0,0 +1,12 @@ +# Package + +version = "0.1.0" +author = "jmgomez" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "src" + + +# Dependencies + +requires "nim >= 2.0.8" diff --git a/tests/projects/testproject/tests/config.nims b/tests/projects/testproject/tests/config.nims new file mode 100644 index 0000000..3bb69f8 --- /dev/null +++ b/tests/projects/testproject/tests/config.nims @@ -0,0 +1 @@ +switch("path", "$projectDir/../src") \ No newline at end of file diff --git a/tests/projects/testproject/tests/test1.nim b/tests/projects/testproject/tests/test1.nim new file mode 100644 index 0000000..17b6145 --- /dev/null +++ b/tests/projects/testproject/tests/test1.nim @@ -0,0 +1,12 @@ +# This is just an example to get you started. You may wish to put all of your +# tests into a single file, or separate them into multiple `test1`, `test2` +# etc. files (better names are recommended, just make sure the name starts with +# the letter 't'). +# +# To run these tests, simply execute `nimble test`. + +import unittest + +import testproject +test "can add": + check add(5, 5) == 10 diff --git a/tests/projects/testproject/tests/testwithsuite.nim b/tests/projects/testproject/tests/testwithsuite.nim new file mode 100644 index 0000000..7f2af1d --- /dev/null +++ b/tests/projects/testproject/tests/testwithsuite.nim @@ -0,0 +1,25 @@ +import unittest + +import testproject + + +suite "test suite": + test "can add": + check add(5, 5) == 10 + + test "can add 2": + check add(5, 5) == 10 + + test "can add 3": + check add(5, 5) == 10 + +suite "test suite 2": + test "can add 4": + check add(5, 5) == 10 + + test "can add 5": + check add(5, 5) == 10 + + +test "can add 6": + check add(5, 5) == 10 diff --git a/tests/ttestrunner.nim b/tests/ttestrunner.nim new file mode 100644 index 0000000..14f5f37 --- /dev/null +++ b/tests/ttestrunner.nim @@ -0,0 +1,23 @@ +import unittest +import std/[os, tables] +import testparser + +suite "Test Parser": + test "should be able to retrieve a test from a file": + let fileDir = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" / "test1.nim" + let testInfo = extractTestInfo(fileDir) + # echo testInfo + check testInfo.suites.len == 1 + check testInfo.suites[""].tests.len == 1 + check testInfo.suites[""].tests[0].name == "can add" + check testInfo.suites[""].tests[0].line == 11 + + test "should be able to retrieve suites and tests from a file": + let fileDir = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" / "testwithsuite.nim" + let testInfo = extractTestInfo(fileDir) + # echo testInfo + check testInfo.suites.len == 3 # 2 suites and 1 test global + check testInfo.suites["test suite"].tests.len == 3 + check testInfo.suites["test suite 2"].tests.len == 2 + check testInfo.suites[""].tests.len == 1 + From 9fc512d921350e1d7e6e4e4946b89ab6443395e3 Mon Sep 17 00:00:00 2001 From: jmgomez Date: Thu, 3 Apr 2025 12:12:49 +0100 Subject: [PATCH 2/6] Adds file check in test --- tests/ttestrunner.nim | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/ttestrunner.nim b/tests/ttestrunner.nim index 14f5f37..bc20e6a 100644 --- a/tests/ttestrunner.nim +++ b/tests/ttestrunner.nim @@ -4,8 +4,9 @@ import testparser suite "Test Parser": test "should be able to retrieve a test from a file": - let fileDir = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" / "test1.nim" - let testInfo = extractTestInfo(fileDir) + let file = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" / "test1.nim" + check fileExists(file), "File does not exist " & file + let testInfo = extractTestInfo(file) # echo testInfo check testInfo.suites.len == 1 check testInfo.suites[""].tests.len == 1 @@ -13,8 +14,9 @@ suite "Test Parser": check testInfo.suites[""].tests[0].line == 11 test "should be able to retrieve suites and tests from a file": - let fileDir = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" / "testwithsuite.nim" - let testInfo = extractTestInfo(fileDir) + let file = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" / "testwithsuite.nim" + check fileExists(file), "File does not exist " & file + let testInfo = extractTestInfo(file) # echo testInfo check testInfo.suites.len == 3 # 2 suites and 1 test global check testInfo.suites["test suite"].tests.len == 3 From cda72b5d65e3b22c7286303275be94c5693ea643 Mon Sep 17 00:00:00 2001 From: jmgomez Date: Thu, 3 Apr 2025 12:26:28 +0100 Subject: [PATCH 3/6] fix issue --- tests/ttestrunner.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ttestrunner.nim b/tests/ttestrunner.nim index bc20e6a..bf77f73 100644 --- a/tests/ttestrunner.nim +++ b/tests/ttestrunner.nim @@ -5,7 +5,7 @@ import testparser suite "Test Parser": test "should be able to retrieve a test from a file": let file = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" / "test1.nim" - check fileExists(file), "File does not exist " & file + check fileExists(file) let testInfo = extractTestInfo(file) # echo testInfo check testInfo.suites.len == 1 @@ -15,7 +15,7 @@ suite "Test Parser": test "should be able to retrieve suites and tests from a file": let file = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" / "testwithsuite.nim" - check fileExists(file), "File does not exist " & file + check fileExists(file) let testInfo = extractTestInfo(file) # echo testInfo check testInfo.suites.len == 3 # 2 suites and 1 test global From 5d36bfea4b1cd02c712c64e404050f4e68bb0e18 Mon Sep 17 00:00:00 2001 From: jmgomez Date: Thu, 3 Apr 2025 12:40:26 +0100 Subject: [PATCH 4/6] adds more hints to see why the file doesnt exists in the ci --- tests/projects/testproject/tests/testwithsuite.nim | 1 - tests/ttestrunner.nim | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/projects/testproject/tests/testwithsuite.nim b/tests/projects/testproject/tests/testwithsuite.nim index 7f2af1d..69b61e8 100644 --- a/tests/projects/testproject/tests/testwithsuite.nim +++ b/tests/projects/testproject/tests/testwithsuite.nim @@ -20,6 +20,5 @@ suite "test suite 2": test "can add 5": check add(5, 5) == 10 - test "can add 6": check add(5, 5) == 10 diff --git a/tests/ttestrunner.nim b/tests/ttestrunner.nim index bf77f73..6bd1514 100644 --- a/tests/ttestrunner.nim +++ b/tests/ttestrunner.nim @@ -15,6 +15,10 @@ suite "Test Parser": test "should be able to retrieve suites and tests from a file": let file = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" / "testwithsuite.nim" + if not fileExists(file): + echo "File does not exist " & file + echo getCurrentDir() + check fileExists(file) let testInfo = extractTestInfo(file) # echo testInfo From 62f2d2c460a3ebdc58a28943dd2a2b72892007c1 Mon Sep 17 00:00:00 2001 From: jmgomez Date: Thu, 3 Apr 2025 13:01:21 +0100 Subject: [PATCH 5/6] more checks --- tests/ttestrunner.nim | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/ttestrunner.nim b/tests/ttestrunner.nim index 6bd1514..2c9d8c4 100644 --- a/tests/ttestrunner.nim +++ b/tests/ttestrunner.nim @@ -18,7 +18,11 @@ suite "Test Parser": if not fileExists(file): echo "File does not exist " & file echo getCurrentDir() - + echo "Does exists as relative?", fileExists("./" & "tests" / "projects" / "testproject" / "tests" / "testwithsuite.nim") + let dir = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" + echo "Walking dir: ", dir, "dir exists? ", dirExists(dir) + for f in dir.walkDir(): + echo f.path check fileExists(file) let testInfo = extractTestInfo(file) # echo testInfo From a23b7593ae1d4cfbaa0f90dc993d30c820900656 Mon Sep 17 00:00:00 2001 From: jmgomez Date: Thu, 3 Apr 2025 13:17:31 +0100 Subject: [PATCH 6/6] Fixes test --- .../tests/testwithsuites.nim} | 0 tests/ttestrunner.nim | 12 +----------- 2 files changed, 1 insertion(+), 11 deletions(-) rename tests/projects/{testproject/tests/testwithsuite.nim => tasks/tests/testwithsuites.nim} (100%) diff --git a/tests/projects/testproject/tests/testwithsuite.nim b/tests/projects/tasks/tests/testwithsuites.nim similarity index 100% rename from tests/projects/testproject/tests/testwithsuite.nim rename to tests/projects/tasks/tests/testwithsuites.nim diff --git a/tests/ttestrunner.nim b/tests/ttestrunner.nim index 2c9d8c4..fa9dd8e 100644 --- a/tests/ttestrunner.nim +++ b/tests/ttestrunner.nim @@ -5,7 +5,6 @@ import testparser suite "Test Parser": test "should be able to retrieve a test from a file": let file = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" / "test1.nim" - check fileExists(file) let testInfo = extractTestInfo(file) # echo testInfo check testInfo.suites.len == 1 @@ -14,16 +13,7 @@ suite "Test Parser": check testInfo.suites[""].tests[0].line == 11 test "should be able to retrieve suites and tests from a file": - let file = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" / "testwithsuite.nim" - if not fileExists(file): - echo "File does not exist " & file - echo getCurrentDir() - echo "Does exists as relative?", fileExists("./" & "tests" / "projects" / "testproject" / "tests" / "testwithsuite.nim") - let dir = getCurrentDir() / "tests" / "projects" / "testproject" / "tests" - echo "Walking dir: ", dir, "dir exists? ", dirExists(dir) - for f in dir.walkDir(): - echo f.path - check fileExists(file) + let file = getCurrentDir() / "tests" / "projects" / "tasks" / "tests" / "testwithsuites.nim" let testInfo = extractTestInfo(file) # echo testInfo check testInfo.suites.len == 3 # 2 suites and 1 test global