-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathtestrunner.nim
153 lines (141 loc) · 5.55 KB
/
testrunner.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import std/[os, strscans, tables, enumerate, strutils, xmlparser, xmltree, options, strformat]
import chronos, chronos/asyncproc
import protocol/types
import ls
import chronicles
import stew/byteutils
import utils
proc extractTestInfo*(rawOutput: string): TestProjectInfo =
result.suites = initTable[string, TestSuiteInfo]()
let lines = rawOutput.split("\n")
var currentSuite = ""
for i, line in enumerate(lines):
var name, file, ignore: string
var lineNumber: int
if scanf(line, "Suite: $*", name):
currentSuite = name.strip()
result.suites[currentSuite] = TestSuiteInfo(name: currentSuite)
# echo "Found suite: ", currentSuite
elif scanf(line, "$*Test: $*", ignore, name):
let insideSuite = line.startsWith("\t")
# Use currentSuite if inside a suite, empty string if not
let suiteName = if insideSuite: currentSuite else: ""
#File is always next line of a test
if scanf(lines[i+1], "$*File:$*:$i", ignore, file, lineNumber):
var testInfo = TestInfo(name: name.strip(), file: file.strip(), line: lineNumber)
# echo "Adding test: ", testInfo.name, " to suite: ", suiteName
result.suites[suiteName].tests.add(testInfo)
proc getFullPath*(entryPoint: string, workspaceRoot: string): string =
if not fileExists(entryPoint):
let absolutePath = joinPath(workspaceRoot, entryPoint)
if fileExists(absolutePath):
return absolutePath
return entryPoint
proc parseObject(obj: var object, node: XmlNode) =
for field, value in obj.fieldPairs:
when value is string:
getField(obj, field) = node.attr(field)
elif value is int:
getField(obj, field) = parseInt(node.attr(field))
elif value is float:
getField(obj, field) = parseFloat(node.attr(field))
proc parseTestResult*(node: XmlNode): RunTestResult =
parseObject(result, node)
# Add handling for failure node
let failureNode = node.child("failure")
if not failureNode.isNil:
result.failure = some failureNode.attr("message")
proc parseTestSuite*(node: XmlNode): RunTestSuiteResult =
parseObject(result, node)
for testCase in node.findAll("testcase"):
result.testResults.add(parseTestResult(testCase))
proc parseTestResults*(xmlContent: string): RunTestProjectResult =
let xml = parseXml(xmlContent)
for suiteNode in xml.findAll("testsuite"):
let suite = parseTestSuite(suiteNode)
# echo suite.name, " ", suite.testResults.len
if suite.testResults.len > 0:
result.suites.add(suite)
proc listTests*(
entryPoint: string,
nimPath: string,
workspaceRoot: string
): Future[TestProjectInfo] {.async.} =
var entryPoint = getFullPath(entryPoint, workspaceRoot)
debug "Listing tests", entryPoint = entryPoint, exists = fileExists(entryPoint)
let args = @["c", "-d:unittest2ListTests", "-r", entryPoint]
let process = await startProcess(
nimPath,
arguments = args,
options = {UsePath},
stderrHandle = AsyncProcess.Pipe,
stdoutHandle = AsyncProcess.Pipe,
)
try:
let (error, res) = await readErrorOutputUntilExit(process, 15.seconds)
if res != 0:
error "Failed to list tests", nimPath = nimPath, entryPoint = entryPoint, res = res
error "An error occurred while listing tests"
for line in error.splitLines:
error "Error line: ", line = line
error "Command args: ", args = args
result = TestProjectInfo(error: some error)
else:
let rawOutput = await readAllOutput(process.stdoutStream)
debug "list test raw output", rawOutput = rawOutput
result = extractTestInfo(rawOutput)
finally:
await shutdownChildProcess(process)
proc runTests*(
entryPoint: string,
nimPath: string,
suiteName: Option[string],
testNames: seq[string],
workspaceRoot: string,
ls: LanguageServer
): Future[RunTestProjectResult] {.async.} =
var entryPoint = getFullPath(entryPoint, workspaceRoot)
if not fileExists(entryPoint):
error "Entry point does not exist", entryPoint = entryPoint
return RunTestProjectResult()
let resultFile = (getTempDir() / "result.xml").absolutePath
var args = @["c", "-r", entryPoint , fmt"--xml:{resultFile}"]
if suiteName.isSome:
args.add(fmt"{suiteName.get()}::")
else:
for testName in testNames:
args.add(testName)
let process = await startProcess(
nimPath,
arguments = args,
options = {UsePath},
stderrHandle = AsyncProcess.Pipe,
stdoutHandle = AsyncProcess.Pipe,
)
ls.testRunProcess = some(process)
try:
removeFile(resultFile)
let (error, res) = await readErrorOutputUntilExit(process, 15.seconds)
if res != 0: #When a test fails, the process will exit with a non-zero code
if fileExists(resultFile):
result = parseTestResults(readFile(resultFile))
result.fullOutput = error
return result
error "Failed to run tests", nimPath = nimPath, entryPoint = entryPoint, res = res
error "An error occurred while running tests"
error "Error from process", error = error
result = RunTestProjectResult(fullOutput: error)
else:
let xmlContent = readFile(resultFile)
# echo "XML CONTENT: ", xmlContent
result = parseTestResults(xmlContent)
result.fullOutput = error
removeFile(resultFile)
except Exception as e:
let processOutput = string.fromBytes(process.stdoutStream.read().await)
error "An error occurred while running tests", error = e.msg
error "Output from process", output = processOutput
finally:
await shutdownChildProcess(process)
if ls.testRunProcess.isSome:
ls.testRunProcess = none(AsyncProcessRef)