777 lines
26 KiB
Plaintext
777 lines
26 KiB
Plaintext
// cell test - Run tests for packages
|
|
|
|
var shop = use('internal/shop')
|
|
var pkg = use('package')
|
|
var fd = use('fd')
|
|
var time = use('time')
|
|
var json = use('json')
|
|
var blob = use('blob')
|
|
var dbg = use('js')
|
|
var testlib = use('internal/testlib')
|
|
|
|
// run gc with dbg.gc()
|
|
|
|
var _args = args == null ? [] : args
|
|
|
|
var target_pkg = null // null = current package
|
|
var target_test = null // null = all tests, otherwise specific test file
|
|
var all_pkgs = false
|
|
var gc_after_each_test = false
|
|
var verify_ir = false
|
|
var diff_mode = false
|
|
|
|
var os_ref = use('internal/os')
|
|
var analyze = os_ref.analyze
|
|
var run_ast_fn = os_ref.run_ast_fn
|
|
var run_ast_noopt_fn = os_ref.run_ast_noopt_fn
|
|
|
|
// Actor test support
|
|
def ACTOR_TEST_TIMEOUT = 30000 // 30 seconds timeout for actor tests
|
|
var pending_actor_tests = []
|
|
var actor_test_results = []
|
|
|
|
var is_valid_package = testlib.is_valid_package
|
|
var get_current_package_name = testlib.get_current_package_name
|
|
|
|
// Parse arguments
|
|
// Usage:
|
|
// cell test - run all tests for current package
|
|
// cell test tests/suite - run specific test file in current package
|
|
// cell test all - run all tests for current package
|
|
// cell test package <name> - run all tests for named package
|
|
// cell test package <name> <test> - run specific test in named package
|
|
// cell test package all - run all tests from all packages
|
|
//
|
|
// Flags:
|
|
// -g - run GC after each test
|
|
// --verify - enable IR verification (validates mcode IR after each optimizer pass)
|
|
// --diff - enable differential testing (run each test optimized and unoptimized, compare results)
|
|
|
|
function parse_args() {
|
|
var cleaned_args = []
|
|
var i = 0
|
|
var name = null
|
|
var lock = null
|
|
var resolved = null
|
|
var test_path = null
|
|
for (i = 0; i < length(_args); i++) {
|
|
if (_args[i] == '-g') {
|
|
gc_after_each_test = true
|
|
} else if (_args[i] == '--verify') {
|
|
verify_ir = true
|
|
} else if (_args[i] == '--diff') {
|
|
diff_mode = true
|
|
} else {
|
|
push(cleaned_args, _args[i])
|
|
}
|
|
}
|
|
_args = cleaned_args
|
|
|
|
if (length(_args) == 0) {
|
|
// cell test - run all tests for current package
|
|
if (!is_valid_package('.')) {
|
|
log.console('No cell.toml found in current directory')
|
|
return false
|
|
}
|
|
target_pkg = null
|
|
return true
|
|
}
|
|
|
|
if (_args[0] == 'all') {
|
|
// cell test all - run all tests for current package
|
|
if (!is_valid_package('.')) {
|
|
log.console('No cell.toml found in current directory')
|
|
return false
|
|
}
|
|
target_pkg = null
|
|
return true
|
|
}
|
|
|
|
if (_args[0] == 'package') {
|
|
if (length(_args) < 2) {
|
|
log.console('Usage: cell test package <name> [test]')
|
|
log.console(' cell test package all')
|
|
return false
|
|
}
|
|
|
|
if (_args[1] == 'all') {
|
|
// cell test package all - run tests from all packages
|
|
all_pkgs = true
|
|
log.console('Testing all packages...')
|
|
return true
|
|
}
|
|
|
|
// cell test package <name> [test]
|
|
name = _args[1]
|
|
|
|
// Check if package exists in lock or is a local path
|
|
lock = shop.load_lock()
|
|
if (lock[name]) {
|
|
target_pkg = name
|
|
} else if (starts_with(name, '/') && is_valid_package(name)) {
|
|
target_pkg = name
|
|
} else {
|
|
// Try to resolve as dependency alias from current package
|
|
resolved = null
|
|
if (is_valid_package('.')) {
|
|
resolved = pkg.alias_to_package(null, name)
|
|
if (resolved) {
|
|
target_pkg = resolved
|
|
} else {
|
|
log.console(`Package not found: ${name}`)
|
|
return false
|
|
}
|
|
} else {
|
|
log.console(`Package not found: ${name}`)
|
|
return false
|
|
}
|
|
}
|
|
|
|
if (length(_args) >= 3) {
|
|
// cell test package <name> <test>
|
|
target_test = _args[2]
|
|
}
|
|
|
|
log.console(`Testing package: ${target_pkg}`)
|
|
return true
|
|
}
|
|
|
|
// cell test tests/suite or cell test <path> - specific test file
|
|
test_path = _args[0]
|
|
|
|
// Normalize path - add tests/ prefix if not present and doesn't start with /
|
|
if (!starts_with(test_path, 'tests/') && !starts_with(test_path, '/')) {
|
|
// Check if file exists as-is first
|
|
if (!fd.is_file(test_path + '.cm') && !fd.is_file(test_path)) {
|
|
// Try with tests/ prefix
|
|
if (fd.is_file('tests/' + test_path + '.cm') || fd.is_file('tests/' + test_path)) {
|
|
test_path = 'tests/' + test_path
|
|
}
|
|
}
|
|
}
|
|
|
|
target_test = test_path
|
|
target_pkg = null
|
|
|
|
if (!is_valid_package('.')) {
|
|
log.console('No cell.toml found in current directory')
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
if (!parse_args()) {
|
|
$stop()
|
|
return
|
|
}
|
|
|
|
// Enable IR verification if requested
|
|
if (verify_ir) {
|
|
os_ref._verify_ir = true
|
|
log.console('IR verification enabled')
|
|
}
|
|
|
|
if (diff_mode && !run_ast_noopt_fn) {
|
|
log.console('error: --diff requires run_ast_noopt_fn (rebuild bootstrap)')
|
|
$stop()
|
|
return
|
|
}
|
|
|
|
var values_equal = testlib.values_equal
|
|
var describe = testlib.describe
|
|
|
|
// Diff mode: run a test function through noopt and compare
|
|
var diff_mismatches = 0
|
|
function diff_check(test_name, file_path, opt_fn, noopt_fn) {
|
|
if (!diff_mode) return
|
|
var opt_result = null
|
|
var noopt_result = null
|
|
var opt_err = null
|
|
var noopt_err = null
|
|
|
|
var _opt = function() {
|
|
opt_result = opt_fn()
|
|
} disruption {
|
|
opt_err = "disrupted"
|
|
}
|
|
_opt()
|
|
|
|
var _noopt = function() {
|
|
noopt_result = noopt_fn()
|
|
} disruption {
|
|
noopt_err = "disrupted"
|
|
}
|
|
_noopt()
|
|
|
|
if (opt_err != noopt_err) {
|
|
log.console(` DIFF ${test_name}: disruption mismatch opt=${opt_err != null ? opt_err : "ok"} noopt=${noopt_err != null ? noopt_err : "ok"}`)
|
|
diff_mismatches = diff_mismatches + 1
|
|
} else if (!values_equal(opt_result, noopt_result)) {
|
|
log.console(` DIFF ${test_name}: result mismatch opt=${describe(opt_result)} noopt=${describe(noopt_result)}`)
|
|
diff_mismatches = diff_mismatches + 1
|
|
}
|
|
}
|
|
|
|
var ensure_dir = fd.ensure_dir
|
|
var get_pkg_dir = testlib.get_pkg_dir
|
|
|
|
// Collect .ce actor tests from a package
|
|
function collect_actor_tests(package_name, specific_test) {
|
|
var prefix = get_pkg_dir(package_name)
|
|
var tests_dir = prefix + '/tests'
|
|
|
|
if (!fd.is_dir(tests_dir)) return []
|
|
|
|
var files = pkg.list_files(package_name)
|
|
var actor_tests = []
|
|
var i = 0
|
|
var f = null
|
|
var test_name = null
|
|
var match_name = null
|
|
var test_base = null
|
|
var match_base = null
|
|
for (i = 0; i < length(files); i++) {
|
|
f = files[i]
|
|
// Check if file is in tests/ folder and is a .ce actor
|
|
if (starts_with(f, "tests/") && ends_with(f, ".ce")) {
|
|
// If specific test requested, filter
|
|
if (specific_test) {
|
|
test_name = text(f, 0, -3) // remove .ce
|
|
match_name = specific_test
|
|
if (!starts_with(match_name, 'tests/')) match_name = 'tests/' + match_name
|
|
if (!ends_with(match_name, '.ce')) match_name = match_name
|
|
// Match without extension
|
|
test_base = test_name
|
|
match_base = ends_with(match_name, '.ce') ? text(match_name, 0, -3) : match_name
|
|
if (test_base != match_base) continue
|
|
}
|
|
|
|
push(actor_tests, {
|
|
package: package_name || "local",
|
|
file: f,
|
|
path: prefix + '/' + f
|
|
})
|
|
}
|
|
}
|
|
return actor_tests
|
|
}
|
|
|
|
// Spawn an actor test and track it
|
|
function spawn_actor_test(test_info) {
|
|
var test_name = text(test_info.file, 6, -3)
|
|
log.console(` [ACTOR] ${test_info.file}`)
|
|
|
|
var entry = {
|
|
package: test_info.package,
|
|
file: test_info.file,
|
|
test: test_name,
|
|
status: "running",
|
|
start_time: time.number(),
|
|
actor: null
|
|
}
|
|
|
|
var _spawn = function() {
|
|
var actor_path = text(test_info.path, 0, -3)
|
|
$start(function(event) {
|
|
var end_time = time.number()
|
|
var duration_ns = round((end_time - entry.start_time) * 1000000000)
|
|
if (event.type == 'greet') {
|
|
entry.actor = event.actor
|
|
return
|
|
}
|
|
var idx = find(pending_actor_tests, e => e == entry)
|
|
if (idx != null) {
|
|
pending_actor_tests = array(
|
|
array(pending_actor_tests, 0, idx),
|
|
array(pending_actor_tests, idx + 1)
|
|
)
|
|
}
|
|
entry.duration_ns = duration_ns
|
|
if (event.type == 'stop') {
|
|
entry.status = "passed"
|
|
log.console(` PASS ${test_name}`)
|
|
} else {
|
|
entry.status = "failed"
|
|
entry.error = { message: event.reason || "Actor disrupted" }
|
|
log.console(` FAIL ${test_name}: ${entry.error.message}`)
|
|
}
|
|
push(actor_test_results, entry)
|
|
if (gc_after_each_test) dbg.gc()
|
|
check_completion()
|
|
}, actor_path)
|
|
push(pending_actor_tests, entry)
|
|
} disruption {
|
|
entry.status = "failed"
|
|
entry.error = { message: "Failed to spawn actor" }
|
|
entry.duration_ns = 0
|
|
push(actor_test_results, entry)
|
|
log.console(` FAIL ${test_name}: Failed to spawn`)
|
|
}
|
|
_spawn()
|
|
}
|
|
|
|
function run_tests(package_name, specific_test) {
|
|
var prefix = get_pkg_dir(package_name)
|
|
var tests_dir = prefix + '/tests'
|
|
|
|
var pkg_result = {
|
|
package: package_name || "local",
|
|
files: [],
|
|
total: 0,
|
|
passed: 0,
|
|
failed: 0
|
|
}
|
|
|
|
if (!fd.is_dir(tests_dir)) return pkg_result
|
|
|
|
var files = pkg.list_files(package_name)
|
|
var test_files = []
|
|
var i = 0
|
|
var f = null
|
|
var test_name = null
|
|
var match_name = null
|
|
var match_base = null
|
|
var mod_path = null
|
|
var file_result = null
|
|
for (i = 0; i < length(files); i++) {
|
|
f = files[i]
|
|
// Check if file is in tests/ folder and is a .cm module (not .ce - those are actor tests)
|
|
if (starts_with(f, "tests/") && ends_with(f, ".cm")) {
|
|
// If specific test requested, filter
|
|
if (specific_test) {
|
|
test_name = text(f, 0, -3) // remove .cm
|
|
match_name = specific_test
|
|
if (!starts_with(match_name, 'tests/')) match_name = 'tests/' + match_name
|
|
// Match without extension
|
|
match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
|
|
if (test_name != match_base) continue
|
|
}
|
|
push(test_files, f)
|
|
}
|
|
}
|
|
|
|
if (length(test_files) > 0) {
|
|
if (package_name) log.console(`Running tests for ${package_name}`)
|
|
else log.console(`Running tests for local package`)
|
|
}
|
|
|
|
var _load_file = null
|
|
var load_error = false
|
|
var err_entry = null
|
|
for (i = 0; i < length(test_files); i++) {
|
|
f = test_files[i]
|
|
mod_path = text(f, 0, -3) // remove .cm
|
|
load_error = false
|
|
|
|
file_result = {
|
|
name: f,
|
|
tests: [],
|
|
passed: 0,
|
|
failed: 0
|
|
}
|
|
|
|
_load_file = function() {
|
|
var test_mod = null
|
|
var test_mod_noopt = null
|
|
var use_pkg = package_name ? package_name : fd.realpath('.')
|
|
var _load_noopt = null
|
|
test_mod = shop.use(mod_path, use_pkg)
|
|
|
|
// Load noopt version for diff mode
|
|
if (diff_mode) {
|
|
_load_noopt = function() {
|
|
var src_path = prefix + '/' + f
|
|
var src = text(fd.slurp(src_path))
|
|
var ast = analyze(src, src_path)
|
|
test_mod_noopt = run_ast_noopt_fn(mod_path + '_noopt', ast, stone({
|
|
use: function(path) { return shop.use(path, use_pkg) }
|
|
}))
|
|
} disruption {
|
|
log.console(` DIFF: failed to load noopt module for ${f}`)
|
|
}
|
|
_load_noopt()
|
|
}
|
|
|
|
var tests = []
|
|
var j = 0
|
|
var t = null
|
|
var test_entry = null
|
|
var start_time = null
|
|
var _test_error = null
|
|
var end_time = null
|
|
var _run_one = null
|
|
var all_keys = null
|
|
var fn_count = 0
|
|
var null_count = 0
|
|
var other_count = 0
|
|
var first_null_key = null
|
|
var first_other_key = null
|
|
if (is_function(test_mod)) {
|
|
push(tests, {name: 'main', fn: test_mod})
|
|
} else if (is_object(test_mod)) {
|
|
all_keys = array(test_mod)
|
|
log.console(` Found ${length(all_keys)} test entries`)
|
|
arrfor(all_keys, function(k) {
|
|
if (is_function(test_mod[k])) {
|
|
fn_count = fn_count + 1
|
|
push(tests, {name: k, fn: test_mod[k]})
|
|
} else if (is_null(test_mod[k])) {
|
|
null_count = null_count + 1
|
|
if (!first_null_key) first_null_key = k
|
|
} else {
|
|
other_count = other_count + 1
|
|
if (!first_other_key) first_other_key = k
|
|
}
|
|
})
|
|
log.console(` functions=${fn_count} nulls=${null_count} other=${other_count}`)
|
|
if (first_other_key) {
|
|
log.console(` first other key: ${first_other_key}`)
|
|
log.console(` is_number=${is_number(test_mod[first_other_key])} is_text=${is_text(test_mod[first_other_key])} is_logical=${is_logical(test_mod[first_other_key])} is_object=${is_object(test_mod[first_other_key])}`)
|
|
}
|
|
}
|
|
|
|
if (length(tests) > 0) {
|
|
log.console(` ${f}`)
|
|
for (j = 0; j < length(tests); j++) {
|
|
t = tests[j]
|
|
test_entry = {
|
|
package: pkg_result.package,
|
|
test: t.name,
|
|
status: "pending",
|
|
duration_ns: 0
|
|
}
|
|
|
|
start_time = time.number()
|
|
_test_error = null
|
|
_run_one = function() {
|
|
var ret = t.fn()
|
|
|
|
if (is_text(ret)) {
|
|
_test_error = ret
|
|
disrupt
|
|
} else if (ret && is_text(ret.message)) {
|
|
_test_error = ret.message
|
|
disrupt
|
|
}
|
|
|
|
test_entry.status = "passed"
|
|
log.console(` PASS ${t.name}`)
|
|
} disruption {
|
|
var e = _test_error
|
|
test_entry.status = "failed"
|
|
test_entry.error = {
|
|
message: e,
|
|
stack: (e && e.stack) ? e.stack : ""
|
|
}
|
|
if (e && e.name) test_entry.error.name = e.name
|
|
|
|
if (is_object(e) && e.message) {
|
|
test_entry.error.message = e.message
|
|
}
|
|
|
|
log.console(` FAIL ${t.name} ${test_entry.error.message}`)
|
|
if (test_entry.error.stack) {
|
|
log.console(` ${text(array(test_entry.error.stack, '\n'), '\n ')}`)
|
|
}
|
|
}
|
|
_run_one()
|
|
|
|
// Differential check: compare opt vs noopt
|
|
if (diff_mode && test_mod_noopt && is_object(test_mod_noopt) && is_function(test_mod_noopt[t.name])) {
|
|
diff_check(t.name, f, t.fn, test_mod_noopt[t.name])
|
|
}
|
|
|
|
end_time = time.number()
|
|
test_entry.duration_ns = round((end_time - start_time) * 1000000000)
|
|
|
|
// Update counters at _load_file level (not inside _run_one)
|
|
if (test_entry.status == "passed") {
|
|
pkg_result.passed = pkg_result.passed + 1
|
|
file_result.passed = file_result.passed + 1
|
|
} else {
|
|
pkg_result.failed = pkg_result.failed + 1
|
|
file_result.failed = file_result.failed + 1
|
|
}
|
|
|
|
push(file_result.tests, test_entry)
|
|
pkg_result.total = pkg_result.total + 1
|
|
if (gc_after_each_test) {
|
|
dbg.gc()
|
|
}
|
|
}
|
|
}
|
|
|
|
} disruption {
|
|
load_error = true
|
|
}
|
|
_load_file()
|
|
if (load_error) {
|
|
log.console(" Error loading " + f)
|
|
pkg_result.failed = pkg_result.failed + 1
|
|
file_result.failed = file_result.failed + 1
|
|
pkg_result.total = pkg_result.total + 1
|
|
}
|
|
push(pkg_result.files, file_result)
|
|
}
|
|
return pkg_result
|
|
}
|
|
|
|
var all_results = []
|
|
var all_actor_tests = []
|
|
var packages = null
|
|
var i = 0
|
|
|
|
if (all_pkgs) {
|
|
// Run local first if we're in a valid package
|
|
if (is_valid_package('.')) {
|
|
push(all_results, run_tests(null, null))
|
|
all_actor_tests = array(all_actor_tests, collect_actor_tests(null, null))
|
|
}
|
|
|
|
// Then all packages in lock
|
|
packages = shop.list_packages()
|
|
for (i = 0; i < length(packages); i++) {
|
|
push(all_results, run_tests(packages[i], null))
|
|
all_actor_tests = array(all_actor_tests, collect_actor_tests(packages[i], null))
|
|
}
|
|
} else {
|
|
push(all_results, run_tests(target_pkg, target_test))
|
|
all_actor_tests = array(all_actor_tests, collect_actor_tests(target_pkg, target_test))
|
|
}
|
|
|
|
// Spawn actor tests if any
|
|
if (length(all_actor_tests) > 0) {
|
|
log.console(`Running ${length(all_actor_tests)} actor test(s)...`)
|
|
for (i = 0; i < length(all_actor_tests); i++) {
|
|
spawn_actor_test(all_actor_tests[i])
|
|
}
|
|
}
|
|
|
|
// Check for timed out actor tests
|
|
function check_timeouts() {
|
|
var now = time.number()
|
|
var timed_out = []
|
|
var i = 0
|
|
var entry = null
|
|
var elapsed_ms = null
|
|
var idx = null
|
|
|
|
for (i = length(pending_actor_tests) - 1; i >= 0; i--) {
|
|
entry = pending_actor_tests[i]
|
|
elapsed_ms = (now - entry.start_time) * 1000
|
|
if (elapsed_ms > ACTOR_TEST_TIMEOUT) {
|
|
push(timed_out, i)
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < length(timed_out); i++) {
|
|
idx = timed_out[i]
|
|
entry = pending_actor_tests[idx]
|
|
pending_actor_tests = array(array(pending_actor_tests, 0, idx), array(pending_actor_tests, idx + 1))
|
|
|
|
entry.status = "failed"
|
|
entry.error = { message: "Test timed out" }
|
|
entry.duration_ns = ACTOR_TEST_TIMEOUT * 1000000
|
|
push(actor_test_results, entry)
|
|
log.console(` TIMEOUT ${entry.test}`)
|
|
}
|
|
|
|
if (length(pending_actor_tests) > 0) {
|
|
$delay(check_timeouts, 1000)
|
|
}
|
|
check_completion()
|
|
}
|
|
|
|
// Check if all tests are complete and finalize
|
|
var finalized = false
|
|
function check_completion() {
|
|
if (finalized) return
|
|
if (length(pending_actor_tests) > 0) return
|
|
|
|
finalized = true
|
|
finalize_results()
|
|
}
|
|
|
|
function finalize_results() {
|
|
var i = 0
|
|
var j = 0
|
|
var r = null
|
|
var pkg_result = null
|
|
var file_result = null
|
|
|
|
// Add actor test results to all_results
|
|
for (i = 0; i < length(actor_test_results); i++) {
|
|
r = actor_test_results[i]
|
|
pkg_result = null
|
|
for (j = 0; j < length(all_results); j++) {
|
|
if (all_results[j].package == r.package) {
|
|
pkg_result = all_results[j]
|
|
break
|
|
}
|
|
}
|
|
if (!pkg_result) {
|
|
pkg_result = { package: r.package, files: [], total: 0, passed: 0, failed: 0 }
|
|
push(all_results, pkg_result)
|
|
}
|
|
|
|
file_result = null
|
|
for (j = 0; j < length(pkg_result.files); j++) {
|
|
if (pkg_result.files[j].name == r.file) {
|
|
file_result = pkg_result.files[j]
|
|
break
|
|
}
|
|
}
|
|
if (!file_result) {
|
|
file_result = { name: r.file, tests: [], passed: 0, failed: 0 }
|
|
push(pkg_result.files, file_result)
|
|
}
|
|
|
|
push(file_result.tests, r)
|
|
pkg_result.total = pkg_result.total + 1
|
|
if (r.status == "passed") {
|
|
pkg_result.passed = pkg_result.passed + 1
|
|
file_result.passed = file_result.passed + 1
|
|
} else {
|
|
pkg_result.failed = pkg_result.failed + 1
|
|
file_result.failed = file_result.failed + 1
|
|
}
|
|
}
|
|
|
|
// Calculate totals
|
|
var totals = { total: 0, passed: 0, failed: 0 }
|
|
for (i = 0; i < length(all_results); i++) {
|
|
totals.total = totals.total + all_results[i].total
|
|
totals.passed = totals.passed + all_results[i].passed
|
|
totals.failed = totals.failed + all_results[i].failed
|
|
}
|
|
|
|
log.console(`----------------------------------------`)
|
|
log.console(`Tests: ${totals.passed} passed, ${totals.failed} failed, ${totals.total} total`)
|
|
if (diff_mode) {
|
|
log.console(`Diff mismatches: ${text(diff_mismatches)}`)
|
|
}
|
|
|
|
generate_reports(totals)
|
|
$stop()
|
|
}
|
|
|
|
// If no actor tests, finalize immediately
|
|
var totals = null
|
|
if (length(all_actor_tests) == 0) {
|
|
totals = { total: 0, passed: 0, failed: 0 }
|
|
for (i = 0; i < length(all_results); i++) {
|
|
totals.total = totals.total + all_results[i].total
|
|
totals.passed = totals.passed + all_results[i].passed
|
|
totals.failed = totals.failed + all_results[i].failed
|
|
}
|
|
|
|
log.console(`----------------------------------------`)
|
|
log.console(`Tests: ${totals.passed} passed, ${totals.failed} failed, ${totals.total} total`)
|
|
if (diff_mode) {
|
|
log.console(`Diff mismatches: ${text(diff_mismatches)}`)
|
|
}
|
|
} else {
|
|
$delay(check_timeouts, 1000)
|
|
}
|
|
|
|
// Generate Reports function
|
|
function generate_reports(totals) {
|
|
var timestamp = text(floor(time.number()))
|
|
var report_dir = shop.get_reports_dir() + '/test_' + timestamp
|
|
ensure_dir(report_dir)
|
|
var i = 0
|
|
var j = 0
|
|
var k = 0
|
|
var pkg_res = null
|
|
var f = null
|
|
var status = null
|
|
var t = null
|
|
var dur = null
|
|
var pkg_tests = null
|
|
var json_path = null
|
|
|
|
var txt_report = `TEST REPORT
|
|
Date: ${time.text(time.number())}
|
|
Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
|
|
|
|
=== SUMMARY ===
|
|
`
|
|
for (i = 0; i < length(all_results); i++) {
|
|
pkg_res = all_results[i]
|
|
if (pkg_res.total == 0) continue
|
|
txt_report = txt_report + `Package: ${pkg_res.package}\n`
|
|
for (j = 0; j < length(pkg_res.files); j++) {
|
|
f = pkg_res.files[j]
|
|
status = f.failed == 0 ? "PASS" : "FAIL"
|
|
txt_report = txt_report + ` [${status}] ${f.name} (${f.passed}/${length(f.tests)})\n`
|
|
}
|
|
}
|
|
|
|
txt_report = txt_report + `\n=== FAILURES ===\n`
|
|
var has_failures = false
|
|
for (i = 0; i < length(all_results); i++) {
|
|
pkg_res = all_results[i]
|
|
for (j = 0; j < length(pkg_res.files); j++) {
|
|
f = pkg_res.files[j]
|
|
for (k = 0; k < length(f.tests); k++) {
|
|
t = f.tests[k]
|
|
if (t.status == "failed") {
|
|
has_failures = true
|
|
txt_report = txt_report + `FAIL: ${pkg_res.package} :: ${f.name} :: ${t.test}\n`
|
|
if (t.error) {
|
|
txt_report = txt_report + ` Message: ${t.error.message}\n`
|
|
if (t.error.stack) {
|
|
txt_report = txt_report + ` Stack:\n${text(array(array(t.error.stack, '\n'), l => ` ${l}`), '\n')}\n`
|
|
}
|
|
}
|
|
txt_report = txt_report + `\n`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!has_failures) txt_report = txt_report + `None\n`
|
|
|
|
txt_report = txt_report + `\n=== DETAILED RESULTS ===\n`
|
|
for (i = 0; i < length(all_results); i++) {
|
|
pkg_res = all_results[i]
|
|
if (pkg_res.total == 0) continue
|
|
|
|
for (j = 0; j < length(pkg_res.files); j++) {
|
|
f = pkg_res.files[j]
|
|
for (k = 0; k < length(f.tests); k++) {
|
|
t = f.tests[k]
|
|
dur = `${t.duration_ns || 0}ns`
|
|
status = t.status == "passed" ? "PASS" : "FAIL"
|
|
txt_report = txt_report + `[${status}] ${pkg_res.package} ${t.test} (${dur})\n`
|
|
}
|
|
}
|
|
}
|
|
ensure_dir(report_dir)
|
|
fd.slurpwrite(`${report_dir}/test.txt`, stone(blob(txt_report)))
|
|
log.console(`Report written to ${report_dir}/test.txt`)
|
|
|
|
// Generate JSON per package
|
|
for (i = 0; i < length(all_results); i++) {
|
|
pkg_res = all_results[i]
|
|
if (pkg_res.total == 0) continue
|
|
|
|
pkg_tests = []
|
|
for (j = 0; j < length(pkg_res.files); j++) {
|
|
f = pkg_res.files[j]
|
|
for (k = 0; k < length(f.tests); k++) {
|
|
push(pkg_tests, f.tests[k])
|
|
}
|
|
}
|
|
|
|
json_path = `${report_dir}/${replace(pkg_res.package, /\//, '_')}.json`
|
|
fd.slurpwrite(json_path, stone(blob(json.encode(pkg_tests))))
|
|
}
|
|
}
|
|
|
|
// If no actor tests, generate reports and stop immediately
|
|
if (length(all_actor_tests) == 0) {
|
|
generate_reports(totals)
|
|
$stop()
|
|
}
|