Merge pull request #37 from Productive2/master

TUI improvements for JATS
This commit is contained in:
Mattia 2021-01-31 09:28:28 +01:00 committed by GitHub
commit 476a6b6c6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 5134 additions and 4892 deletions

View File

@ -31,7 +31,8 @@ proc repl(bytecodeVM: VM) =
if bytecodeVM == nil:
bytecodeVM = initVM()
echo JAPL_VERSION_STRING
echo &"[Nim {NimVersion} on {hostOs} ({hostCPU})]"
let nimDetails = &"[Nim {NimVersion} on {hostOs} ({hostCPU})]"
echo nimDetails
var source = ""
while true:
try:
@ -46,10 +47,14 @@ proc repl(bytecodeVM: VM) =
bytecodeVM.freeVM()
break
if source == "//clear" or source == "// clear":
echo "\x1Bc"
echo JAPL_VERSION_STRING
echo &"[Nim {NimVersion} on {hostOs} ({hostCPU})]"
echo "\x1Bc" & JAPL_VERSION_STRING
echo nimDetails
continue
elif source == "//exit" or source == "// exit":
echo "Goodbye!"
echo JAPL_VERSION_STRING
echo nimDetails
break
elif source != "":
discard bytecodeVM.interpret(source, "stdin")
if not bytecodeVM.lastPop.isNil():

View File

@ -47,6 +47,12 @@ proc natPrint*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
# to nil
return (kind: retNative.Nil, result: nil)
proc natReadline*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Native function readline
## Reads a line from stdin and returns
## it as a string.
return (kind: retNative.Object, result: stdin.readLine().asStr())
proc natClock*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
## Native function clock

View File

@ -718,6 +718,7 @@ proc initStdlib*(vm: VM) =
vm.defineGlobal("toInt", newNative("toInt", natToInt, 1))
vm.defineGlobal("toString", newNative("toString", natToString, 1))
vm.defineGlobal("type", newNative("type", natType, 1))
vm.defineGlobal("readline", newNative("readline", natReadline, 0))
when DEBUG_TRACE_VM and SKIP_STDLIB_INIT:
echo "DEBUG - VM: Skipping stdlib initialization"

View File

@ -1,34 +1,34 @@
//int arithmetic
print(7+5); //output:12
print(-8); //output:-8
print(5-8); //output:-3
print(1+1+1+1+1); //output:5
print(1-1+1-1+1-1); //output:0
print(2*3+2);//output:8
print(2+3*2);//output:8
print(3+2*7);//output:17
print(2-9*5);//output:-43
print(2*9-5);//output:13
print(2**5);//output:32
print(3**3);//output:27
print(3**3*2);//output:54
print(8+2**4);//output:24
print(2+7*2+4);//output:20
print(1-2**2*5);//output:-19
print(7*-2**3+4*7);//output:-28
print(-2**2);//output:-4
print((-2)**2);//output:4
print(-2**3);//output:-8
print((-2)**3);//output:-8
print((2+3)*4);//output:20
print(2*(2+2)*2);//output:16
print(2*(2*2)*2);//output:16
print(8%5);//output:3
print(4%3);//output:1
print(8/4);//output:2.0
print(28/7/4);//output:1.0
print(64/-64);//output:-1.0
print(8/0);//output:inf
print(8/-0);//output:inf
print(-8/0);//output:-inf
print(7+5); //stdout:12
print(-8); //stdout:-8
print(5-8); //stdout:-3
print(1+1+1+1+1); //stdout:5
print(1-1+1-1+1-1); //stdout:0
print(2*3+2);//stdout:8
print(2+3*2);//stdout:8
print(3+2*7);//stdout:17
print(2-9*5);//stdout:-43
print(2*9-5);//stdout:13
print(2**5);//stdout:32
print(3**3);//stdout:27
print(3**3*2);//stdout:54
print(8+2**4);//stdout:24
print(2+7*2+4);//stdout:20
print(1-2**2*5);//stdout:-19
print(7*-2**3+4*7);//stdout:-28
print(-2**2);//stdout:-4
print((-2)**2);//stdout:4
print(-2**3);//stdout:-8
print((-2)**3);//stdout:-8
print((2+3)*4);//stdout:20
print(2*(2+2)*2);//stdout:16
print(2*(2*2)*2);//stdout:16
print(8%5);//stdout:3
print(4%3);//stdout:1
print(8/4);//stdout:2.0
print(28/7/4);//stdout:1.0
print(64/-64);//stdout:-1.0
print(8/0);//stdout:inf
print(8/-0);//stdout:inf
print(-8/0);//stdout:-inf

View File

@ -1,9 +1,9 @@
print(~5 | 5);//output:-1
print(1 | 2);//output:3
print(1 & 2);//output:0
print(~124 & 124);//output:0
print(1 | 2 | 4 | 8);//output:15
print(32 | 64);//output:96
print(96 & 32);//output:32
print(~0);//output:-1
print(~356);//output:-357
print(~5 | 5);//stdout:-1
print(1 | 2);//stdout:3
print(1 & 2);//stdout:0
print(~124 & 124);//stdout:0
print(1 | 2 | 4 | 8);//stdout:15
print(32 | 64);//stdout:96
print(96 & 32);//stdout:32
print(~0);//stdout:-1
print(~356);//stdout:-357

View File

@ -1,21 +1,21 @@
print(2 or 3);//output:2
print(2 and 3);//output:3
print(false or true);//output:true
print(true or false);//output:true
print(true and false);//output:false
print(false and 3 or 4);//output:4
print(true and 3 or 4);//output:3
print(true and 2);//output:2
print(false or 5);//output:5
print(0 or 4);//output:4
print(0 and true);//output:0
print("" and true);//output:''
print("" or true);//output:true
print(1 or 2 or 3 or 4);//output:1
print(1 and 2 and 3 and 4);//output:4
print(1 and 2 or 3 and 4);//output:2
print(1 and false or 3 and 4);//output:4
print(not 0);//output:true
print(not 1);//output:false
print(not 1 and not 2);//output:false
print(not (1 and 0));//output:true
print(2 or 3);//stdout:2
print(2 and 3);//stdout:3
print(false or true);//stdout:true
print(true or false);//stdout:true
print(true and false);//stdout:false
print(false and 3 or 4);//stdout:4
print(true and 3 or 4);//stdout:3
print(true and 2);//stdout:2
print(false or 5);//stdout:5
print(0 or 4);//stdout:4
print(0 and true);//stdout:0
print("" and true);//stdout:''
print("" or true);//stdout:true
print(1 or 2 or 3 or 4);//stdout:1
print(1 and 2 and 3 and 4);//stdout:4
print(1 and 2 or 3 and 4);//stdout:2
print(1 and false or 3 and 4);//stdout:4
print(not 0);//stdout:true
print(not 1);//stdout:false
print(not 1 and not 2);//stdout:false
print(not (1 and 0));//stdout:true

View File

@ -15,4 +15,4 @@ fun mul2(x)
print(add2(sub2(mul2(sub2(5)))));
//5-2=3
//3*2=6
//output:6
//stdout:6

View File

@ -2,41 +2,41 @@ var x = 4;
var y = 5;
var z = 6;
if (x < y)
print("1");//output:1
print("1");//stdout:1
else
print("2");
if (x == y)
print("3");//output:4
print("3");//stdout:4
else
print("4");
if (x > y)
print("5");//output:6
print("5");//stdout:6
else if (x < y)
print("6");
if (y >= 5)
print("7");//output:7
print("7");//stdout:7
else
print("8");
if (z >= 5)
print("9");//output:9
print("9");//stdout:9
else
print("10");
if (x <= 4)
print("11");//output:11
print("11");//stdout:11
else
print("12");
if (2 <= y)
print("13");//output:13
print("13");//stdout:13
else
print("14");
if (8 <= z)
print("15");
else
print("16");//output:16
print("16");//stdout:16

View File

@ -2,12 +2,12 @@
var a = "hello";
var b = "hello";
print(a is b);//output:true
print(a is b);//stdout:true
//different strings
var x = "ex";
var y = "ey";
print(x is y);//output:false
print(x is y);//stdout:false

View File

@ -1,4 +1,4 @@
var a = 1; { var a = a; }
//output:A fatal error occurred while compiling '''', line 1, at ';' -> cannot read local variable in its own initializer
//stdout:A fatal error occurred while compiling '''', line 1, at ';' -> cannot read local variable in its own initializer
//output:
//stdout:

View File

@ -1,8 +1,8 @@
var a = b;
//output:An unhandled exception occurred, traceback below:
//stdout:An unhandled exception occurred, traceback below:
//output: File '''', line 1, in <module>:
//stdout: File '''', line 1, in <module>:
//output:ReferenceError: undefined name 'b'
//stdout:ReferenceError: undefined name 'b'
//output:
//stdout:

View File

@ -1,8 +1,8 @@
var a = 2 + "hey";
//output:An unhandled exception occurred, traceback below:
//stdout:An unhandled exception occurred, traceback below:
//output: File '''', line 1, in <module>:
//stdout: File '''', line 1, in <module>:
//output:TypeError: unsupported binary operator '+' for objects of type 'integer' and 'string'
//stdout:TypeError: unsupported binary operator '+' for objects of type 'integer' and 'string'
//output:
//stdout:

View File

@ -8,4 +8,4 @@ for (var x = 3; x < 1001; x = x + 1)
sum = sum + x;
}
}
print(sum);//output:234168
print(sum);//stdout:234168

View File

@ -13,5 +13,5 @@ while (a < 4000000)
a = b;
b = c;
}
print(sum);//output:4613732
print(sum);//stdout:4613732

View File

@ -3,12 +3,12 @@ fun fib(n) {
return n;
return fib(n-2) + fib(n-1);
}
print(fib(1));//output:1
print(fib(2));//output:1
print(fib(3));//output:2
print(fib(4));//output:3
print(fib(5));//output:5
print(fib(6));//output:8
print(fib(7));//output:13
print(fib(8));//output:21
print(fib(9));//output:34
print(fib(1));//stdout:1
print(fib(2));//stdout:1
print(fib(3));//stdout:2
print(fib(4));//stdout:3
print(fib(5));//stdout:5
print(fib(6));//stdout:8
print(fib(7));//stdout:13
print(fib(8));//stdout:21
print(fib(9));//stdout:34

View File

@ -1,6 +1,6 @@
for (var x = 0; x < 2; x = x + 1)
{
print(x);
//output:0
//output:1
//stdout:0
//stdout:1
}

View File

@ -17,70 +17,70 @@ var i = 0;
for (; i != -1; i = next(i))
print(i);
// before using next
//output:0
//stdout:0
// y = 0
//output:1
//output:2
//output:3
//output:4
//output:5
//output:6
//output:7
//output:8
//output:9
//output:10
//stdout:1
//stdout:2
//stdout:3
//stdout:4
//stdout:5
//stdout:6
//stdout:7
//stdout:8
//stdout:9
//stdout:10
// y = 1
//output:2
//output:3
//output:4
//output:5
//output:6
//output:7
//output:8
//output:9
//output:10
//stdout:2
//stdout:3
//stdout:4
//stdout:5
//stdout:6
//stdout:7
//stdout:8
//stdout:9
//stdout:10
// y = 2
//output:3
//output:4
//output:5
//output:6
//output:7
//output:8
//output:9
//output:10
//stdout:3
//stdout:4
//stdout:5
//stdout:6
//stdout:7
//stdout:8
//stdout:9
//stdout:10
// y = 3
//output:4
//output:5
//output:6
//output:7
//output:8
//output:9
//output:10
//stdout:4
//stdout:5
//stdout:6
//stdout:7
//stdout:8
//stdout:9
//stdout:10
// y = 4
//output:5
//output:6
//output:7
//output:8
//output:9
//output:10
//stdout:5
//stdout:6
//stdout:7
//stdout:8
//stdout:9
//stdout:10
// y = 5
//output:6
//output:7
//output:8
//output:9
//output:10
//stdout:6
//stdout:7
//stdout:8
//stdout:9
//stdout:10
// y = 6
//output:7
//output:8
//output:9
//output:10
//stdout:7
//stdout:8
//stdout:9
//stdout:10
// y = 7
//output:8
//output:9
//output:10
//stdout:8
//stdout:9
//stdout:10
// y = 8
//output:9
//output:10
//stdout:9
//stdout:10
// y = 9
//output:10
//stdout:10
// y = 10

View File

@ -1,4 +1,4 @@
print("Hello, world.");
//output:Hello, world.
//stdout:Hello, world.
//output:
//stdout:

View File

@ -1,4 +1,4 @@
print("Hello, JAPL.");
//output:Hello, JAPL.
//stdout:Hello, JAPL.
//output:
//stdout:

View File

@ -1,7 +1,7 @@
var x = 5;
if (x > 2)
{
print("large");//output:large
print("large");//stdout:large
}
else
{
@ -12,17 +12,17 @@ if (x < 4)
{
print("large");//won't run
}
print("beep");//output:beep
print("beep");//stdout:beep
if (x == 5)
print("equal to 5");//output:equal to 5
print("equal to 5");//stdout:equal to 5
if (2 != x)
print("not 2");//output:not 2
print("not 2");//stdout:not 2
else
print("2");
if (2 == x)
print("2");
else
print("not 2");//output:not 2
print("not 2");//stdout:not 2

View File

@ -13,10 +13,10 @@ fun printInt(x) {
print("six");
}
var x = 3;
printInt(x);//output:three
printInt(x);//stdout:three
x = 5;
printInt(x);//output:five
printInt(x);//stdout:five
x = 7;
printInt(x);
x = 1;
printInt(x);//output:one
printInt(x);//stdout:one

3
tests/japl/inputtest.jpl Normal file
View File

@ -0,0 +1,3 @@
//stdin:Hello world!
print(readline());
//stdout:Hello world!

View File

@ -1,22 +1,22 @@
var x = 4;
var y = x;
print(x is y);//output:true
print(x is x);//output:true
print(x is 4);//output:false
print(x is y);//stdout:true
print(x is x);//stdout:true
print(x is 4);//stdout:false
var z = true;
var u = true;
print(z is u);//output:true
print(z is x);//output:false
print(z is z);//output:true
print(z is u);//stdout:true
print(z is x);//stdout:false
print(z is z);//stdout:true
var l = false;
print((not l) is z);//output:true
print(l is z);//output:false
print((l is z) is l);//output:true
print((not l) is z);//stdout:true
print(l is z);//stdout:false
print((l is z) is l);//stdout:true
var k;
print(k is nil);//output:true
print(k is nil);//stdout:true

View File

@ -15,4 +15,4 @@ var mul2 = lambda(x)
print(add2(sub2(mul2(sub2(5)))));
//5-2=3
//3*2=6
//output:6
//stdout:6

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
print((5/0)*0);
//output:nan
//stdout:nan
//output:
//stdout:

View File

@ -2,7 +2,7 @@
var x = 5;
var y = x;
y = 6;
print(x);//output:5
print(x);//stdout:5
}
var g = 7;
@ -11,9 +11,9 @@ var p = g;
var k = g;
p = 3;
k = 9;
print(g);//output:7
print(g);//stdout:7
}
print(g);//output:7
print(g);//stdout:7
fun resetter(x) {
x = 7;
@ -21,5 +21,5 @@ fun resetter(x) {
}
var q = 5;
resetter(q);//output:7
print(q);//output:5
resetter(q);//stdout:7
print(q);//stdout:5

View File

@ -4,12 +4,12 @@ var f = "leafy";
var g = "ishere";
var h = f + g;
var j = "leafyishere";
print(h is j);//output:true
print(h is j);//stdout:true
//different strings
var x = "ex";
var y = "ey";
print(x is y);//output:false
print(x is y);//stdout:false

View File

@ -4,73 +4,73 @@
var x = 4;
{
var x = 5;
print(x);//output:5
print(x);//stdout:5
}
print(x);//output:4
print(x);//stdout:4
// type changing shadowing
var y = true;
{
var y = 2;
print(y);//output:2
print(y);//stdout:2
}
print(y);//output:true
print(y);//stdout:true
// no shadowing here
var z = 3;
{
z = true;
print(z);//output:true
print(z);//stdout:true
}
print(z);//output:true
print(z);//stdout:true
//in-function shadowing
fun shadow(x) {
//will be called once with the input 3
print(x);//output:3
print(x);//stdout:3
{
var x = 4;
print(x);//output:4
print(x);//stdout:4
}
print(x);//output:3
print(x);//stdout:3
x = nil;
print(x);//output:nil
print(x);//stdout:nil
return x;
}
print(shadow(3));//output:nil
print(shadow(3));//stdout:nil
//shadowing functions
fun hello() {
print("hello");
}
hello();//output:hello
hello();//stdout:hello
{
fun hello() {
print("hello in");
}
hello();//output:hello in
hello();//stdout:hello in
{
fun hello() {
print("hello inmost");
}
hello();//output:hello inmost
hello();//stdout:hello inmost
}
hello();//output:hello in
hello();//stdout:hello in
}
hello();//output:hello
hello();//stdout:hello
//functions shadowing with type change
fun eat() {
print("nom nom nom");
}
eat();//output:nom nom nom
eat();//stdout:nom nom nom
{
var eat = 4;
print(eat);//output:4
print(eat);//stdout:4
{{{{{
eat = 5;
}}}}} //multiple scopes haha
print(eat);//output:5
print(eat);//stdout:5
}
eat();//output:nom nom nom
eat();//stdout:nom nom nom

View File

@ -1,12 +1,12 @@
var left = "left";
var right = "right";
var directions = left + " " + right;
print(directions);//output:left right
print(directions);//stdout:left right
var longstring = directions * 5;
print(longstring);//output:left rightleft rightleft rightleft rightleft right
print(longstring);//stdout:left rightleft rightleft rightleft rightleft right
left = left + " side";
print(left);//output:left side
print(left);//stdout:left side
right = "side: " + right;
print(right);//output:side: right
print(right);//stdout:side: right

View File

@ -1,30 +1,30 @@
var x = 1;
var y = 2;
print(x);//output:1
print(y);//output:2
print(x);//stdout:1
print(y);//stdout:2
{
var x = 4;
var y = 5;
print(x);//output:4
print(y);//output:5
print(x);//stdout:4
print(y);//stdout:5
{
var z = 6;
var y = 2;
print(x);//output:4
print(y);//output:2
print(x);//stdout:4
print(y);//stdout:2
}
print(x);//output:4
print(y);//output:5
print(x);//stdout:4
print(y);//stdout:5
}
print(x);//output:1
print(y);//output:2
print(x);//stdout:1
print(y);//stdout:2
var longName;
print(longName); //output:nil
print(longName); //stdout:nil
longName = 5;
print(longName); //output:5
print(longName); //stdout:5
longName = "hello";
print(longName); //output:hello
print(longName); //stdout:hello
longName = longName + " world";
print(longName); //output:hello world
print(longName); //stdout:hello world

View File

@ -3,11 +3,11 @@ while (x > 0)
{
x = x - 1;
print(x);
//output:4
//output:3
//output:2
//output:1
//output:0
//stdout:4
//stdout:3
//stdout:2
//stdout:1
//stdout:0
}
var string = "h";
@ -17,4 +17,4 @@ while (x < 10)
x = x + 1;
string = string + "A";
}
print(string);//output:hAAAAAAAAAA
print(string);//stdout:hAAAAAAAAAA

View File

@ -1,3 +1,18 @@
# 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.
# Just Another Test Runner for running JAPL tests
# a testrunner process

View File

@ -1,172 +1,163 @@
# 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.
# Just Another Test Suite for running JAPL tests
import nim/nimtests
import ../src/vm
import testutils
import testobject, testutils, logutils
import os, osproc, strformat, streams
# Tests that represent not-yet implemented behaviour
const exceptions = ["all.jpl", "for_with_function.jpl", "runtime_interning.jpl", "problem4.jpl"]
# TODO: for_with_function.jpl and problem4.jpl should already be implemented, check on them
proc buildTest(path: string): Test =
log(LogLevel.Debug, &"Building test {path}")
let source = readFile(path)
result = Test(
path: path,
result: if path.extractFilename in exceptions: TestResult.Skip
else: TestResult.Unstarted,
expectedOutput: compileExpectedOutput(source),
expectedError: compileExpectedError(source)
)
proc buildTests(testDir: string): seq[Test] =
for candidateObj in walkDir(testDir):
let candidate = candidateObj.path
if dirExists(candidate):
log(LogLevel.Debug, &"Descending into dir {candidate}")
result &= buildTests(candidate)
else:
result.add buildTest(candidate)
proc runTest(test: Test, runner: string) =
log(LogLevel.Debug, &"Starting test {test.path}.")
let process = startProcess(runner, args = @[test.path])
test.process = process
test.result = TestResult.Running
proc tryFinishTest(test: Test): bool =
if test.process.running():
return false
test.output = test.process.outputStream.readAll()
test.error = test.process.errorStream.readAll()
if test.process.peekExitCode() == 0:
test.result = TestResult.ToEval
else:
test.result = TestResult.Crash
test.process.close()
log(LogLevel.Debug, &"Test {test.path} finished.")
return true
const maxAliveTests = 8
const testWait = 10
proc runTests(tests: seq[Test], runner: string) =
var
aliveTests = 0
currentTest = 0
finishedTests = 0
buffer = newBuffer()
let totalTests = tests.len()
buffer.updateProgressBar(&"", totalTests, finishedTests)
buffer.render()
while aliveTests > 0 or currentTest < tests.len():
buffer.render()
sleep(testWait)
if aliveTests < maxAliveTests and currentTest < tests.len():
if tests[currentTest].result == TestResult.Unstarted:
tests[currentTest].runTest(runner)
inc aliveTests
inc currentTest
else:
inc currentTest
inc finishedTests
for i in countup(0, min(currentTest, tests.high())):
if tests[i].result == TestResult.Running:
if tryFinishTest(tests[i]):
inc finishedTests
buffer.updateProgressBar(&"", totalTests, finishedTests)
dec aliveTests
else:
inc tests[i].cycles
buffer.render()
proc evalTest(test: Test) =
test.output = test.output.strip()
test.error = test.error.strip()
test.expectedOutput = test.expectedOutput.strip()
test.expectedError = test.expectedError.strip()
if test.output != test.expectedOutput or test.error != test.expectedError:
test.result = TestResult.Mismatch
else:
test.result = TestResult.Success
proc evalTests(tests: seq[Test]) =
for test in tests:
if test.result == TestResult.ToEval:
evalTest(test)
proc printResults(tests: seq[Test]) =
var
skipped = 0
success = 0
fail = 0
crash = 0
for test in tests:
log(LogLevel.Debug, &"Test {test.path} result: {test.result}")
case test.result:
of TestResult.Skip:
inc skipped
of TestResult.Mismatch:
inc fail
log(LogLevel.Debug, &"[{test.path}\noutput:\n{test.output}\nerror:\n{test.error}\nexpected output:\n{test.expectedOutput}\nexpectedError:\n{test.expectedError}\n]")
of TestResult.Crash:
inc crash
log(LogLevel.Debug, &"{test.path} \ncrash:\n{test.error}")
of TestResult.Success:
inc success
else:
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
log(finalLevel, &"{tests.len()} tests: {success} succeeded, {skipped} skipped, {fail} failed, {crash} crashed.")
import os, strformat, parseopt, strutils
when isMainModule:
const jatsVersion = "(dev)"
if paramCount() > 0:
if paramStr(1) == "-h":
echo "Usage: jats [-h | -v | -i | -o filename.txt]"
quit(0)
elif paramStr(1) == "-v":
echo "JATS v" & $jatsVersion
quit(0)
var optparser = initOptParser(commandLineParams())
type Action {.pure.} = enum
Run, Help, Version
var action: Action = Action.Run
type DebugAction {.pure.} = enum
Interactive, Stdout
var debugActions: seq[DebugAction]
var targetFiles: seq[string]
var verbose = true
log(LogLevel.Debug, &"Welcome to JATS")
type QuitValue {.pure.} = enum
Success, Failure, ArgParseErr, InternalErr
var quitVal = QuitValue.Success
runNimTests()
var jatr = "jatr"
var testDir = "japl"
if not fileExists(jatr) and fileExists("tests" / jatr):
log(LogLevel.Debug, &"Must be in root: prepending \"tests\" to paths")
jatr = "tests" / jatr
testDir = "tests" / testDir
proc evalKey(key: string) =
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)
else:
echo &"Unknown flag: {key}"
action = Action.Help
quitVal = QuitValue.ArgParseErr
log(LogLevel.Info, &"Running JAPL tests.")
log(LogLevel.Info, &"Building tests...")
let tests: seq[Test] = buildTests(testDir)
log(LogLevel.Debug, &"Tests built.")
log(LogLevel.Info, &"Running tests...")
tests.runTests(jatr)
log(LogLevel.Debug, &"Tests ran.")
log(LogLevel.Debug, &"Evaluating tests...")
tests.evalTests()
log(LogLevel.Debug, &"Tests evaluated.")
tests.printResults()
log(LogLevel.Debug, &"Quitting JATS.")
proc evalKeyVal(key: string, val: string) =
let key = key.toLower()
if key == "o" or key == "output":
targetFiles.add(val)
else:
echo &"Unknown option: {key}"
action = Action.Help
quitVal = QuitValue.ArgParseErr
# special options to view the entire debug log
proc evalArg(key: string) =
echo &"Unexpected argument"
action = Action.Help
quitVal = QuitValue.ArgParseErr
if paramCount() > 0:
if paramStr(1) == "-i":
writeFile("testresults.txt", getTotalLog())
discard execShellCmd("less testresults.txt")
removeFile("testresults.txt")
if paramStr(1) == "-o":
writeFile(paramStr(2), getTotalLog())
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)
proc printUsage =
echo """
JATS - Just Another Test Suite
Usage:
jats
Runs the tests
Flags:
-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
-h (or --help) displays this help message
-v (or --version) displays the version number of JATS
"""
proc printVersion =
echo &"JATS - Just Another Test Suite version {jatsVersion}"
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."
quit int(QuitValue.InternalErr)
setVerbosity(verbose)
setLogfiles(targetFiles)
# start of JATS
try:
log(LogLevel.Debug, &"Welcome to JATS")
runNimTests()
var jatr = "jatr"
var testDir = "japl"
if not fileExists(jatr) and fileExists("tests" / jatr):
log(LogLevel.Debug, &"Must be in root: prepending \"tests\" to paths")
jatr = "tests" / jatr
testDir = "tests" / testDir
log(LogLevel.Info, &"Running JAPL tests.")
log(LogLevel.Info, &"Building tests...")
let tests: seq[Test] = buildTests(testDir)
log(LogLevel.Debug, &"Tests built.")
log(LogLevel.Info, &"Running tests...")
tests.runTests(jatr)
log(LogLevel.Debug, &"Tests ran.")
log(LogLevel.Debug, &"Evaluating tests...")
tests.evalTests()
log(LogLevel.Debug, &"Tests evaluated.")
if not tests.printResults():
quitVal = QuitValue.Failure
log(LogLevel.Debug, &"Quitting JATS.")
# special options to view the entire debug log
finally:
let logs = getTotalLog()
for action in debugActions:
case action:
of DebugAction.Interactive:
let lessExe = findExe("less", extensions = @[""])
let moreExe = findExe("more", extensions = @[""])
var viewer = if lessExe == "": moreExe else: lessExe
if viewer != "":
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
quit int(quitVal)

93
tests/logutils.nim Normal file
View File

@ -0,0 +1,93 @@
# 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.
# logging stuff
import terminal, strformat, times, strutils
type LogLevel* {.pure.} = enum
Debug, # always written to file only (large outputs, such as the entire output of the failing test or stacktrace)
Info, # important information about the progress of the test suite
Error, # failing tests (printed with red)
Stdout, # always printed to stdout only (for cli experience)
const echoedLogs = {LogLevel.Info, LogLevel.Error, LogLevel.Stdout}
const echoedLogsSilent = {LogLevel.Error}
const savedLogs = {LogLevel.Debug, LogLevel.Info, LogLevel.Error}
const logColors = [LogLevel.Debug: fgDefault, LogLevel.Info: fgGreen, LogLevel.Error: fgRed, LogLevel.Stdout: fgYellow]
var totalLog = ""
var verbose = true
var logfiles: seq[string]
proc setVerbosity*(verb: bool) =
verbose = verb
proc log*(level: LogLevel, msg: string) =
let msg = &"[{$level} - {$getTime()}] {msg}"
if level in savedLogs:
totalLog &= msg & "\n"
if logfiles.len() > 0:
for file in logfiles:
let handle = file.open(fmAppend)
handle.writeLine(msg)
handle.close()
if (verbose and (level in echoedLogs)) or ((not verbose) and (level in echoedLogsSilent)):
setForegroundColor(logColors[level])
echo msg
setForegroundColor(fgDefault)
proc getTotalLog*: string =
totalLog
const progbarLength = 25
type Buffer* = ref object
contents: string
previous: string
proc newBuffer*: Buffer =
hideCursor()
new(result)
proc updateProgressBar*(buf: Buffer, text: string, total: int, current: int) =
var newline = ""
newline &= "["
let ratio = current / total
let filledCount = int(ratio * progbarLength)
if filledCount > 0:
newline &= "=".repeat(filledCount)
if filledCount < progbarLength:
newline &= " ".repeat(progbarLength - filledCount - 1)
newline &= &"] ({current}/{total}) {text}"
# to avoid process switching during half-written progress bars and whatnot all terminal editing happens at the end
let w = terminalWidth()
if w > newline.len():
newline &= " ".repeat(w - newline.len() - 1)
buf.contents = newline
proc clearLineAndWrite(text: string, oldsize: int) =
write stdout, text & "\r"
proc render*(buf: Buffer) =
if verbose: #and buf.previous != buf.contents:
clearLineAndWrite(buf.contents, buf.previous.len())
buf.previous = buf.contents
proc endBuffer*(buf: Buffer) =
showCursor()
proc setLogfiles*(files: seq[string]) =
logfiles = files

View File

@ -1,5 +1,5 @@
import multibyte
import ../testutils
import ../logutils
proc runNimTests* =
log(LogLevel.Info, "Running nim tests.")

58
tests/testobject.nim Normal file
View File

@ -0,0 +1,58 @@
# 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.
# Test object and helpers
import re, strutils, osproc
# types
type
TestResult* {.pure.} = enum
Unstarted, Running, ToEval, Success, Skip, Mismatch, Crash
Test* = ref object
result*: TestResult
path*: string
expectedOutput*: string
expectedError*: string
input*: string
output*: string
error*: string
process*: Process
cycles*: int
# parsing the test notation
proc compileExpectedOutput*(source: string): string =
for line in source.split('\n'):
if line =~ re"^.*//stdout:[ ]?(.*)$":
result &= matches[0] & "\n"
proc compileExpectedError*(source: string): string =
for line in source.split('\n'):
if line =~ re"^.*//stderr:[ ]?(.*)$":
result &= matches[0] & "\n"
proc compileInput*(source: string): string =
for line in source.split('\n'):
if line =~ re"^.*//stdin:[ ]?(.*)$":
result &= matches[0] & "\n"
# stuff for cleaning test output
proc tuStrip*(input: string): string =
return input.replace(re"[\n\r]*$", "")

View File

@ -1,92 +1,162 @@
# Common code from between the JAPL testing suites
# (during transition from runtests -> Just Another Test Runner
# 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.
import re, strutils, terminal, osproc, strformat, times, os
# Test object helpers
# types
import testobject, logutils, os, osproc, streams, strformat
type
TestResult* {.pure.} = enum
Unstarted, Running, ToEval, Success, Skip, Mismatch, Crash
# Tests that represent not-yet implemented behaviour
const exceptions = ["all.jpl", "for_with_function.jpl", "runtime_interning.jpl", "problem4.jpl"]
# TODO: for_with_function.jpl and problem4.jpl should already be implemented, check on them
Test* = ref object
result*: TestResult
path*: string
expectedOutput*: string
expectedError*: string
output*: string
error*: string
process*: Process
cycles*: int
proc buildTest(path: string): Test =
log(LogLevel.Debug, &"Building test {path}")
let source = readFile(path)
result = Test(
path: path,
result: if path.extractFilename in exceptions: TestResult.Skip
else: TestResult.Unstarted,
expectedOutput: compileExpectedOutput(source),
expectedError: compileExpectedError(source),
input: compileInput(source)
)
# logging stuff
proc buildTests*(testDir: string): seq[Test] =
for candidateObj in walkDir(testDir):
let candidate = candidateObj.path
if dirExists(candidate):
log(LogLevel.Debug, &"Descending into dir {candidate}")
result &= buildTests(candidate)
else:
result.add buildTest(candidate)
type LogLevel* {.pure.} = enum
Debug, # always written to file only (large outputs, such as the entire output of the failing test or stacktrace)
Info, # important information about the progress of the test suite
Error, # failing tests (printed with red)
Stdout, # always printed to stdout only (for cli experience)
proc runTest(test: Test, runner: string) =
log(LogLevel.Debug, &"Starting test {test.path}.")
let process = startProcess(runner, args = @[test.path])
test.process = process
if test.input.len() > 0:
var f: File
let suc = f.open(process.inputHandle, fmWrite)
if suc:
f.write(test.input)
else:
log(LogLevel.Error, &"Stdin File handle could not be opened for test {test.path}")
test.result = Crash
test.result = TestResult.Running
proc tryFinishTest(test: Test): bool =
if test.process.running():
return false
test.output = test.process.outputStream.readAll()
test.error = test.process.errorStream.readAll()
if test.process.peekExitCode() == 0:
test.result = TestResult.ToEval
else:
test.result = TestResult.Crash
test.process.close()
log(LogLevel.Debug, &"Test {test.path} finished.")
return true
proc killTest(test: Test) =
if test.process.running():
test.process.kill()
discard test.process.waitForExit()
log(LogLevel.Error, &"Test {test.path} was killed for taking too long.")
discard test.tryFinishTest()
const maxAliveTests = 16
const testWait = 100
const timeout = 100 # number of cycles after which a test is killed for timeout
proc runTests*(tests: seq[Test], runner: string) =
var
aliveTests = 0
currentTest = 0
finishedTests = 0
buffer = newBuffer()
let totalTests = tests.len()
buffer.updateProgressBar(&"", totalTests, finishedTests)
buffer.render()
while aliveTests > 0 or currentTest < tests.len():
buffer.render()
sleep(testWait)
if aliveTests < maxAliveTests and currentTest < tests.len():
if tests[currentTest].result == TestResult.Unstarted:
tests[currentTest].runTest(runner)
inc aliveTests
inc currentTest
else:
inc currentTest
inc finishedTests
for i in countup(0, min(currentTest, tests.high())):
if tests[i].result == TestResult.Running:
if tryFinishTest(tests[i]):
inc finishedTests
buffer.updateProgressBar(&"Finished {tests[i].path}.", totalTests, finishedTests)
dec aliveTests
elif tests[i].cycles >= timeout:
tests[i].killTest()
inc finishedTests
dec aliveTests
buffer.updateProgressBar(&"Killed {tests[i].path}.", totalTests, finishedTests)
else:
inc tests[i].cycles
buffer.render()
buffer.endBuffer()
proc evalTest(test: Test) =
test.output = test.output.tuStrip()
test.error = test.error.tuStrip()
test.expectedOutput = test.expectedOutput.tuStrip()
test.expectedError = test.expectedError.tuStrip()
if test.output != test.expectedOutput or test.error != test.expectedError:
test.result = TestResult.Mismatch
else:
test.result = TestResult.Success
proc evalTests*(tests: seq[Test]) =
for test in tests:
if test.result == TestResult.ToEval:
evalTest(test)
proc printResults*(tests: seq[Test]): bool =
var
skipped = 0
success = 0
fail = 0
crash = 0
for test in tests:
log(LogLevel.Debug, &"Test {test.path} result: {test.result}")
case test.result:
of TestResult.Skip:
inc skipped
of TestResult.Mismatch:
inc fail
log(LogLevel.Debug, &"[{test.path}\noutput:\n{test.output}\nerror:\n{test.error}\nexpected output:\n{test.expectedOutput}\nexpectedError:\n{test.expectedError}\n]")
of TestResult.Crash:
inc crash
log(LogLevel.Debug, &"{test.path} \ncrash:\n{test.error}")
of TestResult.Success:
inc success
else:
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
log(finalLevel, &"{tests.len()} tests: {success} succeeded, {skipped} skipped, {fail} failed, {crash} crashed.")
fail == 0 and crash == 0
const echoedLogs = {LogLevel.Info, LogLevel.Error, LogLevel.Stdout}
const savedLogs = {LogLevel.Debug, LogLevel.Info, LogLevel.Error}
const logColors = [LogLevel.Debug: fgDefault, LogLevel.Info: fgGreen, LogLevel.Error: fgRed, LogLevel.Stdout: fgYellow]
var totalLog = ""
proc log*(level: LogLevel, msg: string) =
let msg = &"[{$level} - {$getTime()}] {msg}"
if level in savedLogs:
totalLog &= msg & "\n"
if level in echoedLogs:
setForegroundColor(logColors[level])
echo msg
setForegroundColor(fgDefault)
proc getTotalLog*: string =
totalLog
const progbarLength = 25
type Buffer* = ref object
contents: string
previous: string
proc newBuffer*: Buffer =
new(result)
proc updateProgressBar*(buf: Buffer, text: string, total: int, current: int) =
var newline = ""
newline &= "["
let ratio = current / total
let filledCount = int(ratio * progbarLength)
for i in countup(1, filledCount):
newline &= "="
for i in countup(filledCount + 1, progbarLength):
newline &= " "
newline &= &"] ({current}/{total}) {text}"
# to avoid process switching during half-written progress bars and whatnot all terminal editing happens at the end
buf.contents = newline
proc render*(buf: Buffer) =
if buf.previous != buf.contents:
echo buf.contents
buf.previous = buf.contents
# parsing the test notation
proc compileExpectedOutput*(source: string): string =
for line in source.split('\n'):
if line =~ re"^.*//output:[ ]?(.*)$":
result &= matches[0] & "\n"
proc compileExpectedError*(source: string): string =
for line in source.split('\n'):
if line =~ re"^.*//error:[ ]?(.*)$":
result &= matches[0] & "\n"
# stuff for cleaning test output
proc strip*(input: string): string =
return input.replace(re"[\n\r]*$", "")