2021-01-30 10:38:47 +01:00
|
|
|
# Copyright 2020 Mattia Giambirtone
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
2021-02-09 18:10:16 +01:00
|
|
|
## Just Another Test Suite for running JAPL tests
|
2021-01-29 18:14:57 +01:00
|
|
|
|
2021-01-29 20:11:06 +01:00
|
|
|
import nim/nimtests
|
2021-01-31 10:51:29 +01:00
|
|
|
import testobject
|
|
|
|
import logutils
|
2021-01-31 15:34:41 +01:00
|
|
|
import testconfig
|
2021-02-08 16:44:48 +01:00
|
|
|
import testbuilder
|
|
|
|
import testrun
|
|
|
|
import testeval
|
2021-02-08 18:18:34 +01:00
|
|
|
import localization
|
2021-01-29 20:11:06 +01:00
|
|
|
|
2021-01-31 10:51:29 +01:00
|
|
|
import os
|
|
|
|
import strformat
|
|
|
|
import parseopt
|
|
|
|
import strutils
|
2021-01-31 15:34:41 +01:00
|
|
|
import terminal
|
|
|
|
import re
|
2021-01-29 19:56:23 +01:00
|
|
|
|
2021-01-31 10:51:29 +01:00
|
|
|
type
|
|
|
|
Action {.pure.} = enum
|
2021-01-30 11:29:43 +01:00
|
|
|
Run, Help, Version
|
2021-02-09 18:10:16 +01:00
|
|
|
## The action JATS takes.
|
|
|
|
|
2021-01-31 10:51:29 +01:00
|
|
|
DebugAction {.pure.} = enum
|
2021-01-30 11:29:43 +01:00
|
|
|
Interactive, Stdout
|
2021-02-09 18:10:16 +01:00
|
|
|
## The action JATS takes with the Debug Log output.
|
|
|
|
|
2021-01-31 10:51:29 +01:00
|
|
|
QuitValue {.pure.} = enum
|
2021-02-09 18:10:16 +01:00
|
|
|
Success, Failure, ArgParseErr, Unreachable, Interrupt, JatrNotFound,
|
|
|
|
UncaughtException
|
|
|
|
## The enum that specifies what each exit code means
|
2021-01-31 10:51:29 +01:00
|
|
|
|
|
|
|
when isMainModule:
|
2021-02-09 18:10:16 +01:00
|
|
|
# command line option parser
|
2021-01-31 10:51:29 +01:00
|
|
|
var optparser = initOptParser(commandLineParams())
|
2021-02-09 18:10:16 +01:00
|
|
|
|
|
|
|
# variables that define what JATS does
|
2021-01-31 10:51:29 +01:00
|
|
|
var action: Action = Action.Run
|
2021-01-30 11:29:43 +01:00
|
|
|
var debugActions: seq[DebugAction]
|
|
|
|
var targetFiles: seq[string]
|
|
|
|
var verbose = true
|
|
|
|
var quitVal = QuitValue.Success
|
2021-02-20 22:23:57 +01:00
|
|
|
var testDir = "japl"
|
2021-02-09 18:10:16 +01:00
|
|
|
|
2021-01-30 11:29:43 +01:00
|
|
|
proc evalKey(key: string) =
|
2021-02-09 18:10:16 +01:00
|
|
|
## Modifies the globals that define what JATS does based on the
|
|
|
|
## provided key/flag
|
2021-01-30 11:29:43 +01:00
|
|
|
let key = key.toLower()
|
|
|
|
if key == "h" or key == "help":
|
|
|
|
action = Action.Help
|
|
|
|
elif key == "v" or key == "version":
|
|
|
|
action = Action.Version
|
|
|
|
elif key == "i" or key == "interactive":
|
|
|
|
debugActions.add(DebugAction.Interactive)
|
|
|
|
elif key == "s" or key == "silent":
|
|
|
|
verbose = false
|
|
|
|
elif key == "stdout":
|
|
|
|
debugActions.add(DebugAction.Stdout)
|
2021-02-20 22:23:57 +01:00
|
|
|
elif key == "f" or key == "force":
|
|
|
|
force = true
|
|
|
|
elif key == "e" or key == "enumerate":
|
|
|
|
enumerate = true
|
2021-01-30 11:29:43 +01:00
|
|
|
else:
|
|
|
|
echo &"Unknown flag: {key}"
|
|
|
|
action = Action.Help
|
|
|
|
quitVal = QuitValue.ArgParseErr
|
|
|
|
|
2021-01-31 10:51:29 +01:00
|
|
|
|
2021-01-30 11:29:43 +01:00
|
|
|
proc evalKeyVal(key: string, val: string) =
|
2021-02-09 18:10:16 +01:00
|
|
|
## Modifies the globals that specify what JATS does based on
|
|
|
|
## the provided key/value pair
|
2021-01-30 11:29:43 +01:00
|
|
|
let key = key.toLower()
|
|
|
|
if key == "o" or key == "output":
|
|
|
|
targetFiles.add(val)
|
2021-01-31 15:34:41 +01:00
|
|
|
elif key == "j" or key == "jobs":
|
|
|
|
if val.match(re"^[0-9]*$"):
|
|
|
|
maxAliveTests = parseInt(val)
|
|
|
|
else:
|
|
|
|
echo "Can't parse non-integer option passed to -j/--jobs."
|
|
|
|
action = Action.Help
|
|
|
|
quitVal = QuitValue.ArgParseErr
|
2021-02-20 22:23:57 +01:00
|
|
|
elif key == "t" or key == "test" or key == "tests":
|
|
|
|
testDir = val
|
|
|
|
elif key == "timeout":
|
|
|
|
try:
|
|
|
|
var timeoutSeconds = parseFloat(val)
|
|
|
|
# a round is 100 ms, so let's not get close to that
|
|
|
|
if timeoutSeconds < 0.3:
|
|
|
|
timeoutSeconds = 0.3
|
|
|
|
# I don't want anything not nicely convertible to int,
|
|
|
|
# so how about cut it off at 10 hours. Open an issue
|
|
|
|
# if that's not enough... or just tweak it you lunatic
|
|
|
|
if timeoutSeconds > 36000.0:
|
|
|
|
timeoutSeconds = 36000.0
|
|
|
|
timeout = (timeoutSeconds * 10).int
|
|
|
|
except ValueError:
|
|
|
|
echo "Can't parse invalid timeout value " & val
|
|
|
|
action = Action.Help
|
|
|
|
quitVal = QuitValue.ArgParseErr
|
2021-01-30 11:29:43 +01:00
|
|
|
else:
|
|
|
|
echo &"Unknown option: {key}"
|
|
|
|
action = Action.Help
|
|
|
|
quitVal = QuitValue.ArgParseErr
|
|
|
|
|
2021-01-31 10:51:29 +01:00
|
|
|
|
2021-01-30 11:29:43 +01:00
|
|
|
proc evalArg(key: string) =
|
2021-02-09 18:10:16 +01:00
|
|
|
## Modifies what JATS does based on a provided argument
|
2021-01-30 11:29:43 +01:00
|
|
|
echo &"Unexpected argument"
|
|
|
|
action = Action.Help
|
|
|
|
quitVal = QuitValue.ArgParseErr
|
|
|
|
|
2021-02-09 18:10:16 +01:00
|
|
|
# parse arguments
|
2021-01-30 11:29:43 +01:00
|
|
|
while true:
|
|
|
|
optparser.next()
|
|
|
|
case optparser.kind:
|
|
|
|
of cmdEnd: break
|
|
|
|
of cmdShortOption, cmdLongOption:
|
|
|
|
if optparser.val == "":
|
|
|
|
evalKey(optparser.key)
|
|
|
|
else:
|
|
|
|
evalKeyVal(optparser.key, optparser.val)
|
|
|
|
of cmdArgument:
|
|
|
|
evalArg(optparser.key)
|
|
|
|
|
2021-01-31 10:51:29 +01:00
|
|
|
|
2021-01-30 11:29:43 +01:00
|
|
|
proc printUsage =
|
2021-02-09 18:10:16 +01:00
|
|
|
## Prints JATS usage/help information to the terminal
|
2021-01-30 11:29:43 +01:00
|
|
|
echo """
|
|
|
|
JATS - Just Another Test Suite
|
|
|
|
|
2021-02-20 22:23:57 +01:00
|
|
|
Usage: ./jats <flags>
|
|
|
|
Debug output flags:
|
2021-01-30 11:29:43 +01:00
|
|
|
-i (or --interactive) displays all debug info
|
|
|
|
-o:<filename> (or --output:<filename>) saves debug info to a file
|
|
|
|
-s (or --silent) will disable all output (except --stdout)
|
|
|
|
--stdout will put all debug info to stdout
|
2021-02-20 22:23:57 +01:00
|
|
|
-e (or --enumerate) will list all tests that fail, crash or get killed
|
|
|
|
Test behavior flags:
|
2021-01-31 15:34:41 +01:00
|
|
|
-j:<parallel test count> (or --jobs:<parallel test count>) to specify number of tests to run parallel
|
2021-02-20 22:23:57 +01:00
|
|
|
-t:<test file or dir> (or --test:<path> or --tests:<path>) to specify where tests are
|
2021-02-28 18:54:04 +01:00
|
|
|
--timeout <timeout in seconds> to specify when to kill tests
|
2021-02-20 22:23:57 +01:00
|
|
|
-f (or --force) will run skipped tests
|
|
|
|
Miscellaneous flags:
|
2021-01-30 11:29:43 +01:00
|
|
|
-h (or --help) displays this help message
|
|
|
|
-v (or --version) displays the version number of JATS
|
|
|
|
"""
|
2021-01-31 10:51:29 +01:00
|
|
|
|
2021-01-30 11:29:43 +01:00
|
|
|
proc printVersion =
|
2021-02-09 18:10:16 +01:00
|
|
|
## Prints JATS version information to the terminal
|
2021-01-30 11:29:43 +01:00
|
|
|
echo &"JATS - Just Another Test Suite version {jatsVersion}"
|
2021-01-31 10:51:29 +01:00
|
|
|
|
2021-02-09 18:10:16 +01:00
|
|
|
# execute the action defined. Run is executed below, so not quitting
|
|
|
|
# runs it.
|
2021-01-30 11:29:43 +01:00
|
|
|
if action == Action.Help:
|
|
|
|
printUsage()
|
|
|
|
quit int(quitVal)
|
|
|
|
elif action == Action.Version:
|
|
|
|
printVersion()
|
|
|
|
quit int(quitVal)
|
|
|
|
elif action == Action.Run:
|
|
|
|
discard
|
|
|
|
else:
|
|
|
|
echo &"Unknown action {action}, please contact the devs to fix this."
|
2021-02-09 00:35:40 +01:00
|
|
|
quit int(QuitValue.Unreachable)
|
2021-02-09 18:10:16 +01:00
|
|
|
|
|
|
|
|
|
|
|
# action Run
|
|
|
|
|
|
|
|
# define globals in logutils
|
2021-01-30 11:29:43 +01:00
|
|
|
setVerbosity(verbose)
|
2021-01-30 18:42:04 +01:00
|
|
|
setLogfiles(targetFiles)
|
2021-02-09 18:10:16 +01:00
|
|
|
|
|
|
|
# run the test suite
|
2021-01-30 18:42:04 +01:00
|
|
|
try:
|
|
|
|
log(LogLevel.Debug, &"Welcome to JATS")
|
2021-02-09 18:10:16 +01:00
|
|
|
|
|
|
|
# the first half of the test suite defined in ~japl/tests/nim
|
2021-01-30 18:42:04 +01:00
|
|
|
runNimTests()
|
2021-02-09 18:10:16 +01:00
|
|
|
|
|
|
|
# the second half of the test suite defined in ~japl/tests/japl
|
|
|
|
# Find ~japl/tests/japl and the test runner JATR
|
2021-01-30 18:42:04 +01:00
|
|
|
var jatr = "jatr"
|
2021-02-09 00:35:40 +01:00
|
|
|
if not fileExists(jatr):
|
|
|
|
if fileExists("tests" / jatr):
|
2021-02-09 18:10:16 +01:00
|
|
|
log(LogLevel.Debug,
|
2021-02-20 22:23:57 +01:00
|
|
|
&"Must be in root: prepending \"tests\" to jatr path")
|
2021-02-09 00:35:40 +01:00
|
|
|
jatr = "tests" / jatr
|
|
|
|
else:
|
2021-02-09 18:10:16 +01:00
|
|
|
# only those two dirs are realistically useful for now,
|
2021-02-20 22:23:57 +01:00
|
|
|
echo "The test runner was not found."
|
2021-02-09 00:35:40 +01:00
|
|
|
quit int(QuitValue.JatrNotFound)
|
2021-02-09 18:10:16 +01:00
|
|
|
|
2021-02-20 22:23:57 +01:00
|
|
|
if not dirExists(testDir) and not fileExists(testDir):
|
|
|
|
if dirExists("tests" / testDir) or fileExists("tests" / testDir):
|
|
|
|
log(LogLevel.Debug, "Prepending \"tests\" to test path")
|
|
|
|
testDir = "tests" / testDir
|
|
|
|
else:
|
|
|
|
echo "The test dir/file was not found."
|
|
|
|
quit int(QuitValue.JatrNotFound)
|
|
|
|
|
2021-02-09 18:10:16 +01:00
|
|
|
# set the global var which specifies the path to the test runner
|
2021-02-09 00:35:40 +01:00
|
|
|
testRunner = jatr
|
2021-01-30 18:42:04 +01:00
|
|
|
log(LogLevel.Info, &"Running JAPL tests.")
|
|
|
|
log(LogLevel.Info, &"Building tests...")
|
2021-02-09 18:10:16 +01:00
|
|
|
# build tests (see testbuilder.nim)
|
2021-01-30 18:42:04 +01:00
|
|
|
let tests: seq[Test] = buildTests(testDir)
|
|
|
|
log(LogLevel.Debug, &"Tests built.")
|
2021-02-09 18:10:16 +01:00
|
|
|
# define interrupt (only here, because it's a closure over tests, so
|
|
|
|
# they can be killed)
|
2021-01-31 15:06:54 +01:00
|
|
|
proc ctrlc() {.noconv.} =
|
|
|
|
showCursor()
|
|
|
|
tests.killTests()
|
|
|
|
echo "Interrupted by ^C."
|
|
|
|
quit(int(QuitValue.Interrupt))
|
|
|
|
setControlCHook(ctrlc)
|
2021-01-30 18:42:04 +01:00
|
|
|
log(LogLevel.Info, &"Running tests...")
|
2021-02-09 18:10:16 +01:00
|
|
|
# run tests (see testrun.nim)
|
2021-02-17 23:41:53 +01:00
|
|
|
tests.runTests()
|
2021-01-30 18:42:04 +01:00
|
|
|
log(LogLevel.Debug, &"Tests ran.")
|
|
|
|
log(LogLevel.Debug, &"Evaluating tests...")
|
2021-02-09 18:10:16 +01:00
|
|
|
# evaluate tests (see testeval.nim)
|
2021-01-30 18:42:04 +01:00
|
|
|
tests.evalTests()
|
|
|
|
log(LogLevel.Debug, &"Tests evaluated.")
|
2021-02-09 18:10:16 +01:00
|
|
|
# print test results (see testeval.nim)
|
2021-01-30 18:42:04 +01:00
|
|
|
if not tests.printResults():
|
|
|
|
quitVal = QuitValue.Failure
|
|
|
|
log(LogLevel.Debug, &"Quitting JATS.")
|
|
|
|
# special options to view the entire debug log
|
2021-02-09 18:10:16 +01:00
|
|
|
except FatalError:
|
|
|
|
# a fatal raised by some code
|
|
|
|
writeLine stderr, getCurrentExceptionMsg()
|
|
|
|
quit(int(QuitValue.UncaughtException))
|
2021-02-08 18:18:34 +01:00
|
|
|
except:
|
2021-02-09 18:10:16 +01:00
|
|
|
# write the current exception message
|
|
|
|
writeLine stdout, getCurrentExceptionMessage()
|
2021-02-09 00:35:40 +01:00
|
|
|
writeLine stdout, getCurrentException().getStackTrace()
|
|
|
|
quit(int(QuitValue.UncaughtException))
|
2021-02-08 18:18:34 +01:00
|
|
|
|
2021-01-30 18:42:04 +01:00
|
|
|
finally:
|
2021-02-09 18:10:16 +01:00
|
|
|
# Always show logs, even if there's a crash
|
2021-01-30 18:42:04 +01:00
|
|
|
let logs = getTotalLog()
|
|
|
|
for action in debugActions:
|
|
|
|
case action:
|
|
|
|
of DebugAction.Interactive:
|
2021-02-09 18:10:16 +01:00
|
|
|
# try to find 'more' and 'less' as pagers
|
2021-01-30 18:42:04 +01:00
|
|
|
let lessExe = findExe("less", extensions = @[""])
|
|
|
|
let moreExe = findExe("more", extensions = @[""])
|
2021-02-09 18:10:16 +01:00
|
|
|
# prioritize 'less' if found, otherwise go for more
|
|
|
|
# or if both are "" = not found, then inform the lack
|
|
|
|
# of a recognized terminal pager
|
2021-01-30 18:42:04 +01:00
|
|
|
var viewer = if lessExe == "": moreExe else: lessExe
|
|
|
|
if viewer != "":
|
2021-02-09 18:10:16 +01:00
|
|
|
# more reliable than pipes
|
2021-01-30 18:42:04 +01:00
|
|
|
writeFile("testresults.txt", logs) # yes, testresults.txt is reserved
|
|
|
|
discard execShellCmd(viewer & " testresults.txt") # this way because of pipe buffer sizes
|
|
|
|
removeFile("testresults.txt")
|
|
|
|
else:
|
|
|
|
write stderr, "Interactive mode not supported on your platform, try --stdout and piping, or install/alias 'more' or 'less' to a terminal pager.\n"
|
|
|
|
of DebugAction.Stdout:
|
|
|
|
echo logs
|
2021-01-30 11:29:43 +01:00
|
|
|
quit int(quitVal)
|
2021-01-29 19:56:23 +01:00
|
|
|
|