τau / testing /

testing.tau

source
/Users/niconex/Documents/tau/stdlib/testing/testing.tau
1# testing - a small test runner.2#3# A test file declares its cases and hands them to Main, which runs them all4# and exits non zero if any of them failed:5#6#	testing = import("testing")7#	cmp = import("cmp")8#9#	testing.Main([10#		["Equal on lists", fn(t) {11#			t.AssertEq(cmp.Equal([1, 2], [1, 2]), true)12#		}],13#		["Compare", fn(t) {14#			t.AssertEq(cmp.Compare(2, 3), -1)15#		}],16#	])17#18# There are no exceptions, so a failed assertion records the failure and the19# case keeps going: to stop it, return right after t.Fatal.2021cmp = import("cmp")22time = import("time")2324# newT builds the object a test case is given.25newT = fn(name) {26	t = new()27	t.Name = name28	t.failed = false29	t.skipped = false30	t.messages = []3132	# Error records a failure and lets the case carry on.33	t.Error = fn(msg) {34		t.failed = true35		t.messages = append(t.messages, string(msg))36	}3738	# Fatal records a failure meant to end the case. Nothing can interrupt a39	# function from the outside, so return right after calling it.40	t.Fatal = fn(msg) {41		t.Error(msg)42		return null43	}4445	# Assert fails the case if cond isn't true.46	t.Assert = fn(cond, msg) {47		if cond != true {48			t.Error(if msg == null { "assertion failed" } else { msg })49			return false50		}51		return true52	}5354	# AssertEq fails the case unless got and want hold the same value,55	# compared structurally.56	t.AssertEq = fn(got, want) {57		if !cmp.Equal(got, want) {58			t.Error("got {got}, want {want}")59			return false60		}61		return true62	}6364	# AssertNe is AssertEq the other way round.65	t.AssertNe = fn(got, want) {66		if cmp.Equal(got, want) {67			t.Error("got {got}, want anything else")68			return false69		}70		return true71	}7273	# AssertError fails the case unless x is an error.74	t.AssertError = fn(x) {75		if type(x) != "error" {76			t.Error("got {type(x)} {x}, want an error")77			return false78		}79		return true80	}8182	# Skip marks the case as not run.83	t.Skip = fn(msg) {84		t.skipped = true85		t.messages = append(t.messages, if msg == null { "skipped" } else { string(msg) })86		return null87	}8889	t.Failed = fn() { t.failed }9091	return t92}9394# Run runs one case and returns its t, already reported.95Run = fn(name, f) {96	t = newT(name)97	start = time.Mono()98	f(t)99	elapsed = time.Since(start)100101	if t.skipped {102		println("--- SKIP: {name}")103	} else if t.failed {104		println("--- FAIL: {name} ({elapsed}ms)")105	} else {106		println("--- PASS: {name} ({elapsed}ms)")107	}108109	for i = 0; i < len(t.messages); ++i {110		println("        {t.messages[i]}")111	}112113	return t114}115116# Main runs every [name, function] pair and exits: 0 when they all pass, 1 as117# soon as one of them doesn't.118Main = fn(cases) {119	failed = 0120	skipped = 0121	start = time.Mono()122123	for i = 0; i < len(cases); ++i {124		t = Run(cases[i][0], cases[i][1])125		if t.failed {126			++failed127		}128		if t.skipped {129			++skipped130		}131	}132133	elapsed = time.Since(start)134	total = len(cases)135	passed = total - failed - skipped136137	if failed > 0 {138		println("FAIL    {failed} failed, {passed} passed of {total} ({elapsed}ms)")139		exit(1)140	}141142	println("ok      {passed} passed of {total} ({elapsed}ms)")143	exit(0)144}