forked from titzer/virgil
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
327 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
../../lib/util/*.v3 | ||
../../lib/test/*.v3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// Copyright 2023 Virgil authors. All rights reserved. | ||
// See LICENSE for details of Apache 2.0 license. | ||
|
||
// Register tests. | ||
def T = UnitTests.register; | ||
def X = [ | ||
T("single", test_single), | ||
T("ok", test_ok), | ||
T("fail0", test_fail0), | ||
T("fail1", test_fail1), | ||
T("fail2", test_fail2), | ||
UnitTests.registerRenderer(FooBar.render), | ||
() | ||
]; | ||
|
||
// Tests | ||
//======================================================================== | ||
def test_single(t: Tester) { | ||
} | ||
|
||
def test_ok(t: Tester) { | ||
t.asserti(11, 11); | ||
t.assertz(true, true); | ||
t.assert(6 == 6, "sanity test"); | ||
} | ||
|
||
def test_fail0(t: Tester) { | ||
t.fail("fail on purpose"); | ||
} | ||
|
||
def test_fail1(t: Tester) { | ||
t.asserti(7, 8); | ||
} | ||
|
||
def test_fail2(t: Tester) { | ||
t.assert_eq(FooBar.new(7, 8), FooBar.new(8, 9)); | ||
} | ||
|
||
class FooBar(x: int, y: int) { | ||
def render(buf: StringBuilder) -> StringBuilder { | ||
return buf.put2("FooBar(%d, %d)", x, y); | ||
} | ||
} | ||
|
||
// Main | ||
//======================================================================== | ||
def main(args: Array<string>) -> int { | ||
return UnitTests.run(args); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// Copyright 2023 Virgil authors. All rights reserved. | ||
// See LICENSE for details of Apache 2.0 license. | ||
|
||
// A {Tester} is instantiated for each execution of a unit test. It contains | ||
// methods that help with assertions and matching. It outputs failures in the | ||
// standard format (for the "progress" utility) directly to the stdout. | ||
class Tester(name: string) { | ||
var ok = true; | ||
var msg: string; | ||
|
||
def fail(msg: string) { | ||
if (!ok) return; | ||
this.ok = false; | ||
this.msg = msg; | ||
if (UnitTests.fatal) System.error("UnitTestError", msg); | ||
} | ||
def assert(cond: bool, msg: string) -> this { | ||
if (!cond) fail(msg); | ||
} | ||
def asserti(expected: int, got: int) -> this { | ||
if (expected != got) fail2("expected %d, got %d", expected, got); | ||
} | ||
def assertz(expected: bool, got: bool) -> this { | ||
if (expected != got) fail2("expected %z, got %z", expected, got); | ||
} | ||
def assertl(expected: long, got: long) -> this { | ||
if (expected != got) fail2("expected %d, got %d", expected, got); | ||
} | ||
def assert_eq<T>(a: T, b: T) -> this { | ||
if (a != b && !UnitTests.equal(a, b)) { | ||
var buf = StringBuilder.new().puts("expected "); | ||
render(buf, a); | ||
buf.puts(" == "); | ||
render(buf, b); | ||
fail(buf.toString()); | ||
} | ||
} | ||
def assert_ne<T>(a: T, b: T) -> this { | ||
if (a == b || UnitTests.equal(a, b)) { | ||
var buf = StringBuilder.new().puts("expected "); | ||
render(buf, a); | ||
buf.puts(" != "); | ||
render(buf, b); | ||
fail(buf.toString()); | ||
} | ||
} | ||
def assert_type<F, T>(v: F) -> this { | ||
if (!T.?(v)) fail("expected type"); | ||
} | ||
def assertb(expected: Array<byte>, got: Array<byte>) -> this { | ||
asserta("data", expected, got, StringBuilder.putx<byte>); | ||
} | ||
def assert_string(expected: string, got: string) -> this { | ||
if (!Strings.equal(expected, got)) { | ||
var msg = StringBuilder.new().puts("expected "); | ||
if (expected == null) msg.puts("null"); | ||
else msg.putsq(expected); | ||
msg.puts(", got "); | ||
if (got == null) msg.puts("null"); | ||
else msg.putsq(got); | ||
fail(msg.extract()); | ||
} | ||
} | ||
def asserta<T>(thing: string, expected: Array<T>, got: Array<T>, render: (StringBuilder, T) -> StringBuilder) { | ||
if (expected.length != got.length) { | ||
return fail3("expected %s.length == %d, got %d", thing, expected.length, got.length); | ||
} | ||
for (i < expected.length) { | ||
var e = expected[i], g = got[i]; | ||
if (e != g && !UnitTests.equal(e, g)) { | ||
var buf = StringBuilder.new() | ||
.put2("expected %s[%d] == ", thing, i); | ||
render(buf, e); | ||
buf.puts(", got "); | ||
render(buf, g); | ||
return fail(buf.extract()); | ||
} | ||
} | ||
} | ||
def assertar<T>(thing: string, expected: Array<T>, got: Array<T>, render: (T, StringBuilder) -> StringBuilder) { | ||
return asserta(thing, expected, got, commute(render, _)); | ||
} | ||
def commute<A, B, R>(f: (A, B) -> R, t: (B, A)) -> R { // XXX: factor out to good location | ||
return f(t.1, t.0); | ||
} | ||
private def render<T>(buf: StringBuilder, v: T) -> StringBuilder { | ||
match (v) { | ||
x: u32 => buf.putd(x); | ||
x: int => buf.putd(x); | ||
x: u64 => buf.putd(x); | ||
x: long => buf.putd(x); | ||
x: bool => buf.putz(x); | ||
x: string => buf.putsq(x); | ||
_ => UnitTests.render(v, buf); | ||
} | ||
return buf; | ||
} | ||
def fail1<T>(msg: string, a: T) { | ||
fail(Strings.format1(msg, a)); | ||
} | ||
def fail2<T, U>(msg: string, a: T, b: U) { | ||
fail(Strings.format2(msg, a, b)); | ||
} | ||
def fail3<T, U, V>(msg: string, a: T, b: U, c: V) { | ||
fail(Strings.format3(msg, a, b, c)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
// Copyright 2020 Ben L. Titzer. All rights reserved. | ||
// See LICENSE for details of Apache 2.0 license. | ||
|
||
// Global unittest registry. | ||
component UnitTests { | ||
private var buf = StringBuilder.new(); | ||
private def expected = Strings.newMap<bool>(); // contains expected failures | ||
private var list: List<UnitTest>; | ||
private var renderers: Renderer; // list of custom renderers | ||
private var comparators: Comparator; // list of custom comparators | ||
|
||
var fatal: bool = false; | ||
var trace: bool = false; | ||
|
||
// Registration methods. | ||
def register(name: string, fun: Tester -> ()) { | ||
list = List.new(UnitTest(name, fun), list); | ||
} | ||
def registerT<T>(prefix: string, name: string, n: Tester -> T, f: T -> void) { | ||
if (prefix != null) name = buf.reset().puts(prefix).puts(name).extract(); | ||
register(name, runNew<T>(_, n, f)); | ||
} | ||
private def runNew<T>(t: Tester, n: Tester -> T, f: T -> ()) { | ||
return f(n(t)); | ||
} | ||
|
||
// Run method, e.g. from command-line. Parses {args}. | ||
def run(args: Array<string>) -> int { | ||
var matchers = Vector<GlobMatcher>.new(); | ||
// Parse options first | ||
for (i < args.length) { | ||
var arg = args[i]; | ||
if (arg == null) continue; | ||
if (arg.length > 0 && arg[0] == '-') { | ||
if (Strings.equal(arg, "-fatal")) { | ||
fatal = true; | ||
} else if (Strings.startsWith(arg, "-expected=")) { | ||
loadExpectedFile(expected, Arrays.range(arg, "-expected=".length, arg.length)); | ||
} else { | ||
System.puts("Unknown option: "); | ||
System.puts(arg); | ||
System.ln(); | ||
return 1; | ||
} | ||
} else { | ||
matchers.put(GlobMatcher.new(arg)); | ||
} | ||
|
||
} | ||
// Filter the registered tests with matchers | ||
var count = 0, r: List<UnitTest>; | ||
for (l = UnitTests.list; l != null; l = l.tail) { // count and reverse list | ||
var t = l.head; | ||
if (matchers.length > 0) { | ||
var skip = true; | ||
for (i < matchers.length) { | ||
if (skip) skip = !matchers[i].matches(t.name); | ||
} | ||
if (skip) continue; | ||
} | ||
r = List.new(l.head, r); | ||
count++; | ||
} | ||
// Run tests | ||
System.puts("##>"); | ||
System.puti(count); | ||
System.puts(" unit tests\n"); | ||
var fail = false; | ||
for (l = r; l != null; l = l.tail) { | ||
var u = l.head; | ||
var t = Tester.new(u.name); | ||
System.puts("##+"); | ||
System.puts(u.name); | ||
System.ln(); | ||
var before = if(trace, System.ticksUs()); | ||
u.fun(t); | ||
if (trace) { | ||
var diff = System.ticksUs() - before; | ||
System.puts("##@"); | ||
System.puts(u.name); | ||
System.puts(" : "); | ||
System.puti(diff); | ||
System.puts(" us\n"); | ||
} | ||
if (t.ok) { | ||
System.puts("##-ok\n"); | ||
} else if (expected[u.name]) { | ||
System.puts("##-ok (ignored failure: "); | ||
System.puts(t.msg); | ||
System.puts(")\n"); | ||
} else { | ||
fail = true; | ||
System.puts("##-fail ("); | ||
System.puts(t.msg); | ||
System.puts(")\n"); | ||
} | ||
} | ||
return if(fail, 1, 0); | ||
} | ||
// Register a custom rendering routine for the type {T}. | ||
def registerRenderer<T>(func: (T, StringBuilder) -> StringBuilder) { | ||
renderers = RendererOf<T>.new(func, renderers); | ||
} | ||
def render<T>(t: T, buf: StringBuilder) -> StringBuilder { | ||
for (l = renderers; l != null; l = l.next) match (l) { | ||
x: RendererOf<T> => return x.func(t, buf); | ||
} | ||
return buf.puts("?"); | ||
} | ||
// Register a custom comparator for the type {T}. | ||
def registerComparator<T>(func: (T, T) -> bool) { | ||
comparators = ComparatorOf<T>.new(func, comparators); | ||
} | ||
def equal<T>(a: T, b: T) -> bool { | ||
if (a == b) return true; | ||
for (l = comparators; l != null; l = l.next) match (l) { | ||
x: ComparatorOf<T> => return x.func(a, b); | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
// An individual unit test. | ||
private type UnitTest(name: string, fun: Tester -> ()) #unboxed; | ||
|
||
// Custom renderers to make using assertions extensible. | ||
private class Renderer(next: Renderer) { | ||
} | ||
private class RendererOf<T> extends Renderer { | ||
def func: (T, StringBuilder) -> StringBuilder; | ||
new(func, next: Renderer) super(next) { } | ||
} | ||
// Custom comparators to make using assertions extensible. | ||
private class Comparator(next: Comparator) { | ||
} | ||
private class ComparatorOf<T> extends Comparator { | ||
def func: (T, T) -> bool; | ||
new(func, next: Comparator) super(next) { } | ||
} | ||
|
||
// Load a file that contains expected failures, one on each line | ||
def loadExpectedFile(expected: Map<string, bool>, fileName: string) { | ||
var data = System.fileLoad(fileName); | ||
if (data == null) return; | ||
var line = 0, pos = 0; | ||
while (pos < data.length) { | ||
if (data[pos] == '\n') { | ||
var test = Arrays.range(data, line, pos); | ||
if (UnitTests.trace) { | ||
System.puts("ignore: "); | ||
System.puts(test); | ||
System.ln(); | ||
} | ||
if (pos > line) expected[test] = true; | ||
line = pos + 1; | ||
} | ||
pos++; | ||
} | ||
if (pos > line) { | ||
var test = Arrays.range(data, line, pos); | ||
if (UnitTests.trace) { | ||
System.puts("ignore: "); | ||
System.puts(test); | ||
System.ln(); | ||
} | ||
expected[test] = true; | ||
line = pos + 1; | ||
} | ||
} |