Minor style fixes to jats and fixed debug logs clashing when debug_vm and debug_alloc are both enabled

This commit is contained in:
nocturn9x 2021-01-31 10:51:29 +01:00
parent 476a6b6c6b
commit 95522cd3c7
12 changed files with 113 additions and 70 deletions

View File

@ -62,8 +62,6 @@ of what's done in JAPL:
If you want to contribute, feel free to open a PR! If you want to contribute, feel free to open a PR!
Right now there are some major issues with the virtual machine which need to be addressed before the development can proceed, and some help is ~~desperately needed~~ greatly appreciated!
To get started, you might want to have a look at the currently open issues and start from there To get started, you might want to have a look at the currently open issues and start from there
### Community ### Community
@ -72,7 +70,7 @@ Our first goal is to create a welcoming and helpful community, so if you are so
### A special thanks ### A special thanks
JAPL is born thanks to the amazing work of Bob Nystrom that wrote a book available completely for free at [this](https://craftinginterpreters.com) link, where he describes the implementation of a simple language called Lox. JAPL is born thanks to the amazing work of Bob Nystrom that wrote a book available completely for free at [this](https://craftinginterpreters.com) link, where he describes the implementation of a simple language called Lox
## JAPL - Installing ## JAPL - Installing
@ -97,7 +95,7 @@ git clone https://github.com/japl-lang/japl
### Running the build script ### Running the build script
As a next step, you need to run the build script. This will generate the required configuration files, compile the JAPL runtime and run tests (unless `--skip-tests` is passed). There are some options that can be tweaked with command-line options, for more information, run `python3 build.py --help`. As a next step, you need to run the build script. This will generate the required configuration files, compile the JAPL runtime and run tests (unless `--skip-tests` is used). There are some settings that can be tweaked with command-line options (or environment variables), for more information, run `python3 build.py --help`.
To compile the JAPL runtime, you'll first need to move into the project's directory you cloned before, so run `cd japl`, then `python3 build.py ./src` and wait for it to complete. You should now find an executable named `japl` (or `japl.exe` on windows) inside the `src` folder. To compile the JAPL runtime, you'll first need to move into the project's directory you cloned before, so run `cd japl`, then `python3 build.py ./src` and wait for it to complete. You should now find an executable named `japl` (or `japl.exe` on windows) inside the `src` folder.
@ -111,14 +109,13 @@ If you need more customizability or want to enable debugging for JAPL, there's a
### Nim compiler options ### Nim compiler options
The build tool calls the system's nim compiler to build JAPL. If you want to customize the options passed to the compiler, you can pass a comma separated list of key:value options (spaces are not allowed). For example, doing `python3 build.py src --flags d:release,threads:on` will call `nim compile src/japl -d:release --threads:on`. The build tool calls the system's nim compiler to build JAPL. If you want to customize the options passed to the compiler, you can pass a comma separated list of key:value pairs (spaces not allowed) via the `--flags` option. For example, doing `python3 build.py src --flags d:release,threads:on` will call `nim compile src/japl -d:release --threads:on`.
#### Known issues #### Known issues
Right now JAPL is in its very early stages and we've encountered a series of issues related to nim's garbage collection implementations. Some of them Right now JAPL is in its very early stages and we've encountered a series of issues related to nim's garbage collection implementations. Some of them
seem to clash with JAPL's own memory management and cause random `NilAccessDefects` because the GC frees stuff that JAPL needs. If the test suite shows seem to clash with JAPL's own memory management and cause random `NilAccessDefect`s because the GC frees stuff that JAPL needs. If the test suite shows
weird crashes try changing the `gc` option to `boehm` (particularly recommended since it seems to cause very little interference with JAPL), or `regions` weird crashes try changing (via `--flags`) the `gc` option to `boehm` (particularly recommended since it seems to cause very little interference with JAPL), or `regions` to see if this mitigates the problem; this is a temporary solution until the JAPL VM becomes fully independent from nim's runtime memory management.
to see if this mitigates the problem; this is a temporary solution until JAPL becomes fully independent from nim's runtime memory management.
### JAPL Debugging options ### JAPL Debugging options
@ -129,12 +126,16 @@ There are also some compile-time constants (such as the heap grow factor for the
- `debug_gc` -> Debugs the garbage collector (once we have one) - `debug_gc` -> Debugs the garbage collector (once we have one)
- `debug_alloc` -> Debugs memory allocation/deallocation - `debug_alloc` -> Debugs memory allocation/deallocation
- `debug_compiler` -> Debugs the compiler, showing each byte that is spit into the bytecode - `debug_compiler` -> Debugs the compiler, showing each byte that is spit into the bytecode
- `skip_stdlib_init` -> Skips the initialization of the standard library (useful to reduce the amount of unneeded output in debug logs)
- `array_grow_factor` -> Sets the multiplicative factor by which JAPL's dynamic array implementation will increase its size when it becomes full
- `map_load_factor` -> A real value between 0 and 1 that indicates the max. % of full buckets in JAPL's hashmap implementation that are needed to trigger a resize
- `frames_max` - The max. number of call frames allowed, used to limit recursion depth
Each of these options is independent of the others and can be enabled/disabled at will. To enable an option, pass `option_name:true` to `--options` while to disable it, replace `true` with `false`. Each of these options is independent of the others and can be enabled/disabled at will. Except for `array_grow_factor`, `map_load_factor` and `frames_max` (which take integers and a real values respectively), all other options require boolean parameters; to enable an option, pass `option_name:true` to `--options` while to disable it, replace `true` with `false`.
Note that the build tool will generate a file named `config.nim` inside the `src` directory and will use that for subsequent builds, so if you want to override it you'll have to pass `--override-config` as a command-line options. Passing it without any option will fallback to (somewhat) sensible defaults Note that the build tool will generate a file named `config.nim` inside the `src` directory and will use that for subsequent builds, so if you want to override it you'll have to pass `--override-config` as a command-line options. Passing it without any option will fallback to (somewhat) sensible defaults
**P.S.**: The test suite assumes that all debugging options are turned off, so for development/debug builds we recommend skipping the test suite by passing `--skip-tests` to the build script **P.S.**: For now the test suite assumes that all debugging options are turned off, so for development/debug builds we recommend skipping the test suite by passing `--skip-tests` to the build script. This will be fixed soon (the test suite will ignore debugging output)
### Installing on Linux ### Installing on Linux
@ -148,5 +149,5 @@ the already existing data unless `--ignore-binary` is passed!
### Environment variables ### Environment variables
On both Windows and Linux, the build script supports reading parameters from environment variables if they are not specified via the command line. On both Windows and Linux, the build script supports reading parameters from environment variables if they are not specified via the command line.
All options follow the same naming scheme: `JAPL_OPTION_NAME=value` and will only be applied only if no explicit override for them is passed All options follow the same naming scheme: `JAPL_OPTION_NAME=value` and will only be applied if no explicit override for them is passed
when running the script when running the script

View File

@ -23,7 +23,7 @@ import shutil
import logging import logging
import argparse import argparse
from time import time from time import time
from typing import Dict from typing import Dict, Optional
from subprocess import Popen, PIPE, DEVNULL, run from subprocess import Popen, PIPE, DEVNULL, run
@ -46,14 +46,14 @@ import strformat
const MAP_LOAD_FACTOR* = {map_load_factor} # Load factor for builtin hashmaps const MAP_LOAD_FACTOR* = {map_load_factor} # Load factor for builtin hashmaps
const ARRAY_GROW_FACTOR* = {array_grow_factor} # How much extra memory to allocate for dynamic arrays const ARRAY_GROW_FACTOR* = {array_grow_factor} # How much extra memory to allocate for dynamic arrays when resizing
const FRAMES_MAX* = {frames_max} # The maximum recursion limit const FRAMES_MAX* = {frames_max} # The maximum recursion limit
const JAPL_VERSION* = "0.3.0" const JAPL_VERSION* = "0.3.0"
const JAPL_RELEASE* = "alpha" const JAPL_RELEASE* = "alpha"
const DEBUG_TRACE_VM* = {debug_vm} # Traces VM execution const DEBUG_TRACE_VM* = {debug_vm} # Traces VM execution
const SKIP_STDLIB_INIT* = {skip_stdlib_init} # Skips stdlib initialization in debug mode const SKIP_STDLIB_INIT* = {skip_stdlib_init} # Skips stdlib initialization in debug mode
const DEBUG_TRACE_GC* = {debug_gc} # Traces the garbage collector (TODO) const DEBUG_TRACE_GC* = {debug_gc} # Traces the garbage collector (TODO)
const DEBUG_TRACE_ALLOCATION* = {debug_alloc} # Traces memory allocation/deallocation (WIP) const DEBUG_TRACE_ALLOCATION* = {debug_alloc} # Traces memory allocation/deallocation
const DEBUG_TRACE_COMPILER* = {debug_compiler} # Traces the compiler const DEBUG_TRACE_COMPILER* = {debug_compiler} # Traces the compiler
const JAPL_VERSION_STRING* = &"JAPL {{JAPL_VERSION}} ({{JAPL_RELEASE}}, {{CompileDate}} {{CompileTime}})" const JAPL_VERSION_STRING* = &"JAPL {{JAPL_VERSION}} ({{JAPL_RELEASE}}, {{CompileDate}} {{CompileTime}})"
const HELP_MESSAGE* = """The JAPL runtime interface, Copyright (C) 2020 Mattia Giambirtone const HELP_MESSAGE* = """The JAPL runtime interface, Copyright (C) 2020 Mattia Giambirtone
@ -87,23 +87,23 @@ def run_command(command: str, mode: str = "Popen", **kwargs):
if mode == "Popen": if mode == "Popen":
process = Popen(shlex.split(command, posix=os.name != "nt"), **kwargs) process = Popen(shlex.split(command, posix=os.name != "nt"), **kwargs)
else:
process = run(command, **kwargs)
if mode == "Popen":
stdout, stderr = process.communicate() stdout, stderr = process.communicate()
else: else:
process = run(command, **kwargs)
stdout, stderr = None, None stdout, stderr = None, None
return stdout, stderr, process.returncode return stdout, stderr, process.returncode
def build(path: str, flags: Dict[str, str] = {}, options: Dict[str, bool] = {}, override: bool = False, def build(path: str, flags: Optional[Dict[str, str]] = {}, options: Optional[Dict[str, bool]] = {},
skip_tests: bool = False, install: bool = False, ignore_binary: bool = False): override: Optional[bool] = False, skip_tests: Optional[bool] = False,
install: Optional[bool] = False, ignore_binary: Optional[bool] = False,
verbose: Optional[bool] = False):
""" """
Compiles the JAPL runtime, generating the appropriate Builds the JAPL runtime.
configuration needed for compilation to succeed,
runs tests and performs installation This function generates the required configuration
when possible. according to the user's choice, runs tests and
Nim 1.2 or above is required to build JAPL performs installation when possible.
:param path: The path to JAPL's main source directory :param path: The path to JAPL's main source directory
:type path: string, optional :type path: string, optional
@ -134,6 +134,10 @@ def build(path: str, flags: Dict[str, str] = {}, options: Dict[str, bool] = {},
or folder already named "jpl" in ANY entry in PATH so this option allows or folder already named "jpl" in ANY entry in PATH so this option allows
to overwrite whatever data is there. Note that JAPL right now isn't aware to overwrite whatever data is there. Note that JAPL right now isn't aware
of what it is replacing so make sure you don't lose any sensitive data! of what it is replacing so make sure you don't lose any sensitive data!
:type ignore_binary: bool, optional
:param verbose: This parameter tells the test suite to use verbose logs,
defaults to False
:type verbose: bool, optional
""" """
@ -272,13 +276,20 @@ if __name__ == "__main__":
for value in args.options.split(","): for value in args.options.split(","):
k, v = value.split(":", maxsplit=2) k, v = value.split(":", maxsplit=2)
if k not in options: if k not in options:
logging.error("Invalid compile-time option '{key}'") logging.error(f"Invalid compile-time option '{k}'")
exit() exit()
options[k] = v options[k] = v
except Exception: except Exception:
logging.error("Invalid parameter for --options") logging.error("Invalid parameter for --options")
exit() exit()
build(args.path, flags, options, args.override_config, args.skip_tests, args.install, args.ignore_binary) build(args.path,
flags,
options,
args.override_config,
args.skip_tests,
args.install,
args.ignore_binary,
args.verbose)
logging.debug("Build tool exited") logging.debug("Build tool exited")
except KeyboardInterrupt: except KeyboardInterrupt:
logging.info("Interrupted by the user") logging.info("Interrupted by the user")

View File

@ -33,13 +33,22 @@ proc reallocate*(pointr: pointer, oldSize: int, newSize: int): pointer =
try: try:
if newSize == 0 and pointr != nil: # pointr is awful, but clashing with builtins is even more awful if newSize == 0 and pointr != nil: # pointr is awful, but clashing with builtins is even more awful
when DEBUG_TRACE_ALLOCATION: when DEBUG_TRACE_ALLOCATION:
echo &"DEBUG - Memory manager: Deallocating {oldSize} bytes" if oldSize > 1:
echo &"DEBUG - Memory manager: Deallocating {oldSize} bytes"
else:
echo "DEBUG - Memory manager: Deallocating 1 byte"
dealloc(pointr) dealloc(pointr)
return nil return nil
when DEBUG_TRACE_ALLOCATION:
if pointr == nil and newSize == 0:
echo &"DEBUG - Memory manager: Warning, asked to dealloc() nil pointer from {oldSize} to {newSize} bytes, ignoring request"
if oldSize > 0 and pointr != nil or oldSize == 0: if oldSize > 0 and pointr != nil or oldSize == 0:
when DEBUG_TRACE_ALLOCATION: when DEBUG_TRACE_ALLOCATION:
if oldSize == 0: if oldSize == 0:
echo &"DEBUG - Memory manager: Allocating {newSize} bytes of memory" if newSize > 1:
echo &"DEBUG - Memory manager: Allocating {newSize} bytes of memory"
else:
echo "DEBUG - Memory manager: Allocating 1 byte of memory"
else: else:
echo &"DEBUG - Memory manager: Resizing {oldSize} bytes of memory to {newSize} bytes" echo &"DEBUG - Memory manager: Resizing {oldSize} bytes of memory to {newSize} bytes"
result = realloc(pointr, newSize) result = realloc(pointr, newSize)

View File

@ -213,6 +213,10 @@ proc `$`*[K, V](self: ptr HashMap[K, V]): string =
result &= "}" result &= "}"
proc typeName*[K, V](self: ptr HashMap[K, V]): string =
result = "dict"
var d = newHashMap[int, int]() var d = newHashMap[int, int]()
d[1] = 55 d[1] = 55
d[2] = 876 d[2] = 876

View File

@ -58,6 +58,8 @@ proc typeName*(self: ptr Obj): string =
result = cast[ptr Nil](self).typeName() result = cast[ptr Nil](self).typeName()
of ObjectType.Native: of ObjectType.Native:
result = cast[ptr Native](self).typeName() result = cast[ptr Native](self).typeName()
of ObjectType.List:
result = "list" # We can't do casting here since it's not a concrete type!
else: else:
discard discard

View File

@ -21,8 +21,9 @@ import strformat
import tables import tables
import std/enumerate import std/enumerate
## Our modules ## Our modules
import stdlib
import config import config
when not SKIP_STDLIB_INIT:
import stdlib
import compiler import compiler
import meta/opcode import meta/opcode
import meta/frame import meta/frame
@ -294,6 +295,7 @@ proc readConstant(self: CallFrame): ptr Obj =
proc showRuntime*(self: VM, frame: CallFrame, iteration: uint64) = proc showRuntime*(self: VM, frame: CallFrame, iteration: uint64) =
## Shows debug information about the current ## Shows debug information about the current
## state of the virtual machine ## state of the virtual machine
let view = frame.getView()
stdout.write("DEBUG - VM: General information\n") stdout.write("DEBUG - VM: General information\n")
stdout.write(&"DEBUG - VM:\tIteration -> {iteration}\nDEBUG - VM:\tStack -> [") stdout.write(&"DEBUG - VM:\tIteration -> {iteration}\nDEBUG - VM:\tStack -> [")
for i, v in self.stack: for i, v in self.stack:
@ -312,18 +314,18 @@ proc showRuntime*(self: VM, frame: CallFrame, iteration: uint64) =
else: else:
stdout.write(&"function, '{frame.function.name.stringify()}'\n") stdout.write(&"function, '{frame.function.name.stringify()}'\n")
echo &"DEBUG - VM:\tCount -> {self.frames.len()}" echo &"DEBUG - VM:\tCount -> {self.frames.len()}"
echo &"DEBUG - VM:\tLength -> {frame.len}" echo &"DEBUG - VM:\tLength -> {view.len}"
stdout.write("DEBUG - VM:\tTable -> ") stdout.write("DEBUG - VM:\tTable -> ")
stdout.write("[") stdout.write("[")
for i, e in frame.function.chunk.consts: for i, e in frame.function.chunk.consts:
stdout.write(stringify(e)) stdout.write(stringify(e))
if i < frame.function.chunk.consts.high(): if i < len(frame.function.chunk.consts) - 1:
stdout.write(", ") stdout.write(", ")
stdout.write("]\nDEBUG - VM:\tStack view -> ") stdout.write("]\nDEBUG - VM:\tStack view -> ")
stdout.write("[") stdout.write("[")
for i, e in frame.getView(): for i, e in view:
stdout.write(stringify(e)) stdout.write(stringify(e))
if i < len(frame) - 1: if i < len(view) - 1:
stdout.write(", ") stdout.write(", ")
stdout.write("]\n") stdout.write("]\n")
echo "DEBUG - VM: Current instruction" echo "DEBUG - VM: Current instruction"

View File

@ -17,7 +17,8 @@
# a testrunner process # a testrunner process
import ../src/vm import ../src/vm
import os, strformat import os
var btvm = initVM() var btvm = initVM()
try: try:

View File

@ -15,28 +15,34 @@
# Just Another Test Suite for running JAPL tests # Just Another Test Suite for running JAPL tests
import nim/nimtests import nim/nimtests
import testobject
import testutils
import logutils
import testobject, testutils, logutils
import os, strformat, parseopt, strutils import os
import strformat
import parseopt
import strutils
const jatsVersion = "(dev)"
type
Action {.pure.} = enum
Run, Help, Version
DebugAction {.pure.} = enum
Interactive, Stdout
QuitValue {.pure.} = enum
Success, Failure, ArgParseErr, InternalErr
when isMainModule: when isMainModule:
const jatsVersion = "(dev)"
var optparser = initOptParser(commandLineParams()) var optparser = initOptParser(commandLineParams())
type Action {.pure.} = enum
Run, Help, Version
var action: Action = Action.Run var action: Action = Action.Run
type DebugAction {.pure.} = enum
Interactive, Stdout
var debugActions: seq[DebugAction] var debugActions: seq[DebugAction]
var targetFiles: seq[string] var targetFiles: seq[string]
var verbose = true var verbose = true
type QuitValue {.pure.} = enum
Success, Failure, ArgParseErr, InternalErr
var quitVal = QuitValue.Success var quitVal = QuitValue.Success
proc evalKey(key: string) = proc evalKey(key: string) =
let key = key.toLower() let key = key.toLower()
if key == "h" or key == "help": if key == "h" or key == "help":
@ -54,6 +60,7 @@ when isMainModule:
action = Action.Help action = Action.Help
quitVal = QuitValue.ArgParseErr quitVal = QuitValue.ArgParseErr
proc evalKeyVal(key: string, val: string) = proc evalKeyVal(key: string, val: string) =
let key = key.toLower() let key = key.toLower()
if key == "o" or key == "output": if key == "o" or key == "output":
@ -63,6 +70,7 @@ when isMainModule:
action = Action.Help action = Action.Help
quitVal = QuitValue.ArgParseErr quitVal = QuitValue.ArgParseErr
proc evalArg(key: string) = proc evalArg(key: string) =
echo &"Unexpected argument" echo &"Unexpected argument"
action = Action.Help action = Action.Help
@ -80,6 +88,7 @@ when isMainModule:
of cmdArgument: of cmdArgument:
evalArg(optparser.key) evalArg(optparser.key)
proc printUsage = proc printUsage =
echo """ echo """
JATS - Just Another Test Suite JATS - Just Another Test Suite
@ -95,9 +104,12 @@ Flags:
-h (or --help) displays this help message -h (or --help) displays this help message
-v (or --version) displays the version number of JATS -v (or --version) displays the version number of JATS
""" """
proc printVersion = proc printVersion =
echo &"JATS - Just Another Test Suite version {jatsVersion}" echo &"JATS - Just Another Test Suite version {jatsVersion}"
if action == Action.Help: if action == Action.Help:
printUsage() printUsage()
quit int(quitVal) quit int(quitVal)
@ -109,15 +121,11 @@ Flags:
else: else:
echo &"Unknown action {action}, please contact the devs to fix this." echo &"Unknown action {action}, please contact the devs to fix this."
quit int(QuitValue.InternalErr) quit int(QuitValue.InternalErr)
setVerbosity(verbose) setVerbosity(verbose)
setLogfiles(targetFiles) setLogfiles(targetFiles)
# start of JATS # start of JATS
try: try:
log(LogLevel.Debug, &"Welcome to JATS") log(LogLevel.Debug, &"Welcome to JATS")
runNimTests() runNimTests()
var jatr = "jatr" var jatr = "jatr"
var testDir = "japl" var testDir = "japl"
@ -125,7 +133,6 @@ Flags:
log(LogLevel.Debug, &"Must be in root: prepending \"tests\" to paths") log(LogLevel.Debug, &"Must be in root: prepending \"tests\" to paths")
jatr = "tests" / jatr jatr = "tests" / jatr
testDir = "tests" / testDir testDir = "tests" / testDir
log(LogLevel.Info, &"Running JAPL tests.") log(LogLevel.Info, &"Running JAPL tests.")
log(LogLevel.Info, &"Building tests...") log(LogLevel.Info, &"Building tests...")
let tests: seq[Test] = buildTests(testDir) let tests: seq[Test] = buildTests(testDir)
@ -138,9 +145,7 @@ Flags:
log(LogLevel.Debug, &"Tests evaluated.") log(LogLevel.Debug, &"Tests evaluated.")
if not tests.printResults(): if not tests.printResults():
quitVal = QuitValue.Failure quitVal = QuitValue.Failure
log(LogLevel.Debug, &"Quitting JATS.") log(LogLevel.Debug, &"Quitting JATS.")
# special options to view the entire debug log # special options to view the entire debug log
finally: finally:
let logs = getTotalLog() let logs = getTotalLog()
@ -158,6 +163,5 @@ Flags:
write stderr, "Interactive mode not supported on your platform, try --stdout and piping, or install/alias 'more' or 'less' to a terminal pager.\n" 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: of DebugAction.Stdout:
echo logs echo logs
quit int(quitVal) quit int(quitVal)

View File

@ -14,19 +14,22 @@
# Test creation tool, use mainly for exceptions # Test creation tool, use mainly for exceptions
#
import os
import strformat
import re
import strutils
# Imports nim tests as well
import multibyte, os, strformat, times, re, terminal, strutils
const tempCodeFile = ".tempcode_drEHdZuwNYLqsQaMDMqeNRtmqoqXBXfnCfeqEcmcUYJToBVQkF.jpl" const tempCodeFile = ".tempcode_drEHdZuwNYLqsQaMDMqeNRtmqoqXBXfnCfeqEcmcUYJToBVQkF.jpl"
const tempOutputFile = ".tempoutput.txt" const tempOutputFile = ".tempoutput.txt"
proc autoremove(path: string) =
proc autoRemove(path: string) =
if fileExists(path): if fileExists(path):
removeFile(path) removeFile(path)
when isMainModule: when isMainModule:
var testsDir = "tests" / "japl" var testsDir = "tests" / "japl"
var japlExec = "src" / "japl" var japlExec = "src" / "japl"
@ -41,16 +44,13 @@ when isMainModule:
if not dirExists(testsDir): if not dirExists(testsDir):
echo "Tests dir not found" echo "Tests dir not found"
quit(1) quit(1)
echo "Please enter the JAPL code or specify a file containing it with file:<path>" echo "Please enter the JAPL code or specify a file containing it with file:<path>"
let response = stdin.readLine() let response = stdin.readLine()
if response =~ re"^file:(.*)$": if response =~ re"^file:(.*)$":
let codepath = matches[0] let codepath = matches[0]
writeFile(tempCodeFile, readFile(codepath)) writeFile(tempCodeFile, readFile(codepath))
else: else:
writeFile(tempCodeFile, response) writeFile(tempCodeFile, response)
let japlCode = readFile(tempCodeFile) let japlCode = readFile(tempCodeFile)
discard execShellCmd(&"{japlExec} {tempCodeFile} > {tempOutputFile} 2>&1") discard execShellCmd(&"{japlExec} {tempCodeFile} > {tempOutputFile} 2>&1")
var output: string var output: string
@ -59,9 +59,8 @@ when isMainModule:
else: else:
echo "Temporary output file not detected, aborting" echo "Temporary output file not detected, aborting"
quit(1) quit(1)
autoremove(tempCodeFile) autoRemove(tempCodeFile)
autoremove(tempOutputFile) autoRemove(tempOutputFile)
echo "Got the following output:" echo "Got the following output:"
echo output echo output
echo "Do you want to keep it as a test? [y/N]" echo "Do you want to keep it as a test? [y/N]"

View File

@ -1,10 +1,12 @@
import multibyte import multibyte
import ../logutils import ../logutils
proc runNimTests* = proc runNimTests* =
log(LogLevel.Info, "Running nim tests.") log(LogLevel.Info, "Running nim tests.")
testMultibyte() testMultibyte()
log(LogLevel.Debug, "Nim tests finished") log(LogLevel.Debug, "Nim tests finished")
when isMainModule: when isMainModule:
runNimTests() runNimTests()

View File

@ -40,6 +40,7 @@ proc compileExpectedOutput*(source: string): string =
if line =~ re"^.*//stdout:[ ]?(.*)$": if line =~ re"^.*//stdout:[ ]?(.*)$":
result &= matches[0] & "\n" result &= matches[0] & "\n"
proc compileExpectedError*(source: string): string = proc compileExpectedError*(source: string): string =
for line in source.split('\n'): for line in source.split('\n'):
if line =~ re"^.*//stderr:[ ]?(.*)$": if line =~ re"^.*//stderr:[ ]?(.*)$":

View File

@ -17,8 +17,9 @@
import testobject, logutils, os, osproc, streams, strformat import testobject, logutils, os, osproc, streams, strformat
# Tests that represent not-yet implemented behaviour # Tests that represent not-yet implemented behaviour
const exceptions = ["all.jpl", "for_with_function.jpl", "runtime_interning.jpl", "problem4.jpl"] const exceptions = ["all.jpl", "for_with_function.jpl", "runtime_interning.jpl"]
# TODO: for_with_function.jpl and problem4.jpl should already be implemented, check on them # TODO: for_with_function.jpl should already be implemented, check on it
proc buildTest(path: string): Test = proc buildTest(path: string): Test =
log(LogLevel.Debug, &"Building test {path}") log(LogLevel.Debug, &"Building test {path}")
@ -32,6 +33,7 @@ proc buildTest(path: string): Test =
input: compileInput(source) input: compileInput(source)
) )
proc buildTests*(testDir: string): seq[Test] = proc buildTests*(testDir: string): seq[Test] =
for candidateObj in walkDir(testDir): for candidateObj in walkDir(testDir):
let candidate = candidateObj.path let candidate = candidateObj.path
@ -41,6 +43,7 @@ proc buildTests*(testDir: string): seq[Test] =
else: else:
result.add buildTest(candidate) result.add buildTest(candidate)
proc runTest(test: Test, runner: string) = proc runTest(test: Test, runner: string) =
log(LogLevel.Debug, &"Starting test {test.path}.") log(LogLevel.Debug, &"Starting test {test.path}.")
let process = startProcess(runner, args = @[test.path]) let process = startProcess(runner, args = @[test.path])
@ -56,6 +59,7 @@ proc runTest(test: Test, runner: string) =
test.result = TestResult.Running test.result = TestResult.Running
proc tryFinishTest(test: Test): bool = proc tryFinishTest(test: Test): bool =
if test.process.running(): if test.process.running():
return false return false
@ -69,6 +73,7 @@ proc tryFinishTest(test: Test): bool =
log(LogLevel.Debug, &"Test {test.path} finished.") log(LogLevel.Debug, &"Test {test.path} finished.")
return true return true
proc killTest(test: Test) = proc killTest(test: Test) =
if test.process.running(): if test.process.running():
test.process.kill() test.process.kill()
@ -76,10 +81,12 @@ proc killTest(test: Test) =
log(LogLevel.Error, &"Test {test.path} was killed for taking too long.") log(LogLevel.Error, &"Test {test.path} was killed for taking too long.")
discard test.tryFinishTest() discard test.tryFinishTest()
const maxAliveTests = 16 const maxAliveTests = 16
const testWait = 100 const testWait = 100
const timeout = 100 # number of cycles after which a test is killed for timeout const timeout = 100 # number of cycles after which a test is killed for timeout
proc runTests*(tests: seq[Test], runner: string) = proc runTests*(tests: seq[Test], runner: string) =
var var
aliveTests = 0 aliveTests = 0
@ -87,7 +94,6 @@ proc runTests*(tests: seq[Test], runner: string) =
finishedTests = 0 finishedTests = 0
buffer = newBuffer() buffer = newBuffer()
let totalTests = tests.len() let totalTests = tests.len()
buffer.updateProgressBar(&"", totalTests, finishedTests) buffer.updateProgressBar(&"", totalTests, finishedTests)
buffer.render() buffer.render()
while aliveTests > 0 or currentTest < tests.len(): while aliveTests > 0 or currentTest < tests.len():
@ -117,6 +123,7 @@ proc runTests*(tests: seq[Test], runner: string) =
buffer.render() buffer.render()
buffer.endBuffer() buffer.endBuffer()
proc evalTest(test: Test) = proc evalTest(test: Test) =
test.output = test.output.tuStrip() test.output = test.output.tuStrip()
test.error = test.error.tuStrip() test.error = test.error.tuStrip()
@ -127,18 +134,19 @@ proc evalTest(test: Test) =
else: else:
test.result = TestResult.Success test.result = TestResult.Success
proc evalTests*(tests: seq[Test]) = proc evalTests*(tests: seq[Test]) =
for test in tests: for test in tests:
if test.result == TestResult.ToEval: if test.result == TestResult.ToEval:
evalTest(test) evalTest(test)
proc printResults*(tests: seq[Test]): bool = proc printResults*(tests: seq[Test]): bool =
var var
skipped = 0 skipped = 0
success = 0 success = 0
fail = 0 fail = 0
crash = 0 crash = 0
for test in tests: for test in tests:
log(LogLevel.Debug, &"Test {test.path} result: {test.result}") log(LogLevel.Debug, &"Test {test.path} result: {test.result}")
case test.result: case test.result:
@ -156,7 +164,6 @@ proc printResults*(tests: seq[Test]): bool =
log(LogLevel.Error, &"Probably a testing suite bug: test {test.path} has result {test.result}") log(LogLevel.Error, &"Probably a testing suite bug: test {test.path} has result {test.result}")
let finalLevel = if fail == 0 and crash == 0: LogLevel.Info else: LogLevel.Error let finalLevel = if fail == 0 and crash == 0: LogLevel.Info else: LogLevel.Error
log(finalLevel, &"{tests.len()} tests: {success} succeeded, {skipped} skipped, {fail} failed, {crash} crashed.") log(finalLevel, &"{tests.len()} tests: {success} succeeded, {skipped} skipped, {fail} failed, {crash} crashed.")
result = fail == 0 and crash == 0
fail == 0 and crash == 0