#
#
# Nim Tester
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
import sequtils, parseutils, strutils, os, streams, parsecfg
var compilerPrefix* = findExe("nim")
let isTravis* = existsEnv("TRAVIS")
let isAppVeyor* = existsEnv("APPVEYOR")
let isAzure* = existsEnv("TF_BUILD")
var skips*: seq[string]
type
TTestAction* = enum
actionRun = "run"
actionCompile = "compile"
actionReject = "reject"
TOutputCheck* = enum
ocIgnore = "ignore"
ocEqual = "equal"
ocSubstr = "substr"
TResultEnum* = enum
reNimcCrash, # nim compiler seems to have crashed
reMsgsDiffer, # error messages differ
reFilesDiffer, # expected and given filenames differ
reLinesDiffer, # expected and given line numbers differ
reOutputsDiffer,
reExitcodesDiffer,
reTimeout,
reInvalidPeg,
reCodegenFailure,
reCodeNotFound,
reExeNotFound,
reInstallFailed # package installation failed
reBuildFailed # package building failed
reDisabled, # test is disabled
reJoined, # test is disabled because it was joined into the megatest
reSuccess # test was successful
reInvalidSpec # test had problems to parse the spec
TTarget* = enum
targetC = "C"
targetCpp = "C++"
targetObjC = "ObjC"
targetJS = "JS"
TSpec* = object
action*: TTestAction
file*, cmd*: string
input*: string
outputCheck*: TOutputCheck
sortoutput*: bool
output*: string
line*, column*: int
tfile*: string
tline*, tcolumn*: int
exitCode*: int
msg*: string
ccodeCheck*: string
maxCodeSize*: int
err*: TResultEnum
targets*: set[TTarget]
nimout*: string
parseErrors*: string # when the spec definition is invalid, this is not empty.
unjoinable*: bool
timeout*: float # in seconds, fractions possible,
# but don't rely on much precision
proc getCmd*(s: TSpec): string =
if s.cmd.len == 0:
result = compilerPrefix & " $target --hints:on -d:testing --nimblePath:tests/deps $options $file"
else:
result = s.cmd
const
targetToExt*: array[TTarget, string] = ["nim.c", "nim.cpp", "nim.m", "js"]
targetToCmd*: array[TTarget, string] = ["c", "cpp", "objc", "js"]
when not declared(parseCfgBool):
# candidate for the stdlib:
proc parseCfgBool(s: string): bool =
case normalize(s)
of "y", "yes", "true", "1", "on": result = true
of "n", "no", "false", "0", "off": result = false
else: raise newException(ValueError, "cannot interpret as a bool: " & s)
proc extractSpec(filename: string): string =
const tripleQuote = "\"\"\""
var x = readFile(filename).string
var a = x.find(tripleQuote)
var b = x.find(tripleQuote, a+3)
# look for """ only in the first section
if a >= 0 and b > a and a < 40:
result = x.substr(a+3, b-1).replace("'''", tripleQuote)
else:
#echo "warning: file does not contain spec: " & filename
result = ""
when not defined(nimhygiene):
{.pragma: inject.}
proc parseTargets*(value: string): set[TTarget] =
for v in value.normalize.splitWhitespace:
case v
of "c": result.incl(targetC)
of "cpp", "c++": result.incl(targetCpp)
of "objc": result.incl(targetObjC)
of "js": result.incl(targetJS)
else: echo "target ignored: " & v
proc addLine*(self: var string; a: string) =
self.add a
self.add "\n"
proc addLine*(self: var string; a,b: string) =
self.add a
self.add b
self.add "\n"
proc initSpec*(filename: string): TSpec =
result.file = filename
proc parseSpec*(filename: string): TSpec =
result.file = filename
let specStr = extractSpec(filename)
var ss = newStringStream(specStr)
var p: CfgParser
open(p, ss, filename, 1)
while true:
var e = next(p)
case e.kind
of cfgKeyValuePair:
case normalize(e.key)
of "action":
case e.value.normalize
of "compile":
result.action = actionCompile
of "run":
result.action = actionRun
of "reject":
result.action = actionReject
else:
result.parseErrors.addLine "cannot interpret as action: ", e.value
of "file":
if result.msg.len == 0 and result.nimout.len == 0:
result.parseErrors.addLine "errormsg or msg needs to be specified before file"
result.file = e.value
of "line":
if result.msg.len == 0 and result.nimout.len == 0:
result.parseErrors.addLine "errormsg, msg or nimout needs to be specified before line"
discard parseInt(e.value, result.line)
of "column":
if result.msg.len == 0 and result.nimout.len == 0:
result.parseErrors.addLine "errormsg or msg needs to be specified before column"
discard parseInt(e.value, result.column)
of "tfile":
result.tfile = e.value
of "tline":
discard parseInt(e.value, result.tline)
of "tcolumn":
discard parseInt(e.value, result.tcolumn)
of "output":
result.outputCheck = ocEqual
result.output = strip(e.value)
of "input":
result.input = e.value
of "outputsub":
result.outputCheck = ocSubstr
result.output = strip(e.value)
of "sortoutput":
try:
result.sortoutput = parseCfgBool(e.value)
except:
result.parseErrors.addLine getCurrentExceptionMsg()
of "exitcode":
discard parseInt(e.value, result.exitCode)
result.action = actionRun
of "msg":
result.msg = e.value
if result.action != actionRun:
result.action = actionCompile
of "errormsg", "errmsg":
result.msg = e.value
result.action = actionReject
of "nimout":
result.nimout = e.value
of "joinable":
result.unjoinable = not parseCfgBool(e.value)
of "disabled":
case e.value.normalize
of "y", "yes", "true", "1", "on": result.err = reDisabled
of "n", "no", "false", "0", "off": discard
of "win", "windows":
when defined(windows): result.err = reDisabled
of "linux":
when defined(linux): result.err = reDisabled
of "bsd":
when defined(bsd): result.err = reDisabled
of "macosx":
when defined(macosx): result.err = reDisabled
of "unix":
when defined(unix): result.err = reDisabled
of "posix":
when defined(posix): result.err = reDisabled
of "travis":
if isTravis: result.err = reDisabled
of "appveyor":
if isAppVeyor: result.err = reDisabled
of "azure":
if isAzure: result.err = reDisabled
of "32bit":
if sizeof(int) == 4:
result.err = reDisabled
else:
result.parseErrors.addLine "cannot interpret as a bool: ", e.value
of "cmd":
if e.value.startsWith("nim "):
result.cmd = compilerPrefix & e.value[3..^1]
else:
result.cmd = e.value
of "ccodecheck":
result.ccodeCheck = e.value
of "maxcodesize":
discard parseInt(e.value, result.maxCodeSize)
of "timeout":
try:
result.timeout = parseFloat(e.value)
except ValueError:
result.parseErrors.addLine "cannot interpret as a float: ", e.value
of "target", "targets":
for v in e.value.normalize.splitWhitespace:
case v
of "c":
result.targets.incl(targetC)
of "cpp", "c++":
result.targets.incl(targetCpp)
of "objc":
result.targets.incl(targetObjC)
of "js":
result.targets.incl(targetJS)
else:
result.parseErrors.addLine "cannot interpret as a target: ", e.value
else:
result.parseErrors.addLine "invalid key for test spec: ", e.key
of cfgSectionStart:
result.parseErrors.addLine "section ignored: ", e.section
of cfgOption:
result.parseErrors.addLine "command ignored: ", e.key & ": " & e.value
of cfgError:
result.parseErrors.addLine e.msg
of cfgEof:
break
close(p)
if skips.anyIt(it in result.file):
result.err = reDisabled