tests can now start actor based tests
This commit is contained in:
@@ -1184,11 +1184,7 @@ Shop.remove = function(alias) {
|
||||
// Remove directory
|
||||
if (fd.is_dir(target_dir)) {
|
||||
log.console("Removing " + target_dir)
|
||||
try {
|
||||
fd.rmdir(target_dir)
|
||||
} catch (e) {
|
||||
log.error("Failed to remove directory: " + e)
|
||||
}
|
||||
fd.rmdir(target_dir)
|
||||
}
|
||||
|
||||
log.console("Removed " + alias)
|
||||
|
||||
372
scripts/test.ce
372
scripts/test.ce
@@ -9,6 +9,11 @@ if (!args) args = []
|
||||
var target_pkg = null // null = current, otherwise canonical path
|
||||
var all_pkgs = false
|
||||
|
||||
// Actor test support
|
||||
def ACTOR_TEST_TIMEOUT = 30000 // 30 seconds timeout for actor tests
|
||||
var pending_actor_tests = []
|
||||
var actor_test_results = []
|
||||
|
||||
if (args.length > 0) {
|
||||
if (args[0] == 'package') {
|
||||
if (args.length < 2) {
|
||||
@@ -51,6 +56,58 @@ function ensure_dir(path) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Collect .ce actor tests from a package
|
||||
function collect_actor_tests(pkg) {
|
||||
var prefix = pkg ? `.cell/modules/${pkg}` : "."
|
||||
var tests_dir = `${prefix}/tests`
|
||||
|
||||
if (!fd.is_dir(tests_dir)) return []
|
||||
|
||||
var files = shop.list_files(pkg)
|
||||
var actor_tests = []
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var f = files[i]
|
||||
// Check if file is in tests/ folder and is a .ce actor
|
||||
if (f.startsWith("tests/") && f.endsWith(".ce")) {
|
||||
actor_tests.push({
|
||||
package: pkg || "local",
|
||||
file: f,
|
||||
path: pkg ? `${prefix}/${f}` : f
|
||||
})
|
||||
}
|
||||
}
|
||||
return actor_tests
|
||||
}
|
||||
|
||||
// Spawn an actor test and track it
|
||||
function spawn_actor_test(test_info) {
|
||||
var test_name = test_info.file.substring(6, test_info.file.length - 3) // remove "tests/" and ".ce"
|
||||
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
|
||||
}
|
||||
|
||||
try {
|
||||
// Spawn the actor test - it should send back results
|
||||
// The actor receives $_.parent which it can use to send results
|
||||
var actor_path = test_info.path.substring(0, test_info.path.length - 3) // remove .ce
|
||||
entry.actor = $_.spawn(actor_path)
|
||||
pending_actor_tests.push(entry)
|
||||
} catch (e) {
|
||||
entry.status = "failed"
|
||||
entry.error = { message: `Failed to spawn actor: ${e}` }
|
||||
entry.duration_ns = 0
|
||||
actor_test_results.push(entry)
|
||||
log.console(` FAIL ${test_name}: ${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
function run_tests(pkg) {
|
||||
var prefix = pkg ? `.cell/modules/${pkg}` : "."
|
||||
var tests_dir = `${prefix}/tests`
|
||||
@@ -69,7 +126,7 @@ function run_tests(pkg) {
|
||||
var test_files = []
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var f = files[i]
|
||||
// Check if file is in tests/ folder and is a .cm module
|
||||
// Check if file is in tests/ folder and is a .cm module (not .ce - those are actor tests)
|
||||
if (f.startsWith("tests/") && f.endsWith(".cm")) {
|
||||
test_files.push(f)
|
||||
}
|
||||
@@ -186,113 +243,280 @@ function run_tests(pkg) {
|
||||
}
|
||||
|
||||
var all_results = []
|
||||
var all_actor_tests = []
|
||||
|
||||
if (all_pkgs) {
|
||||
// Run local first
|
||||
all_results.push(run_tests(null))
|
||||
all_actor_tests = all_actor_tests.concat(collect_actor_tests(null))
|
||||
|
||||
// Then all dependencies
|
||||
var deps = shop.list_packages(null)
|
||||
for (var i = 0; i < deps.length; i++) {
|
||||
all_results.push(run_tests(deps[i]))
|
||||
all_actor_tests = all_actor_tests.concat(collect_actor_tests(deps[i]))
|
||||
}
|
||||
} else {
|
||||
all_results.push(run_tests(target_pkg))
|
||||
all_actor_tests = all_actor_tests.concat(collect_actor_tests(target_pkg))
|
||||
}
|
||||
|
||||
// Calculate totals
|
||||
var totals = { total: 0, passed: 0, failed: 0 }
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
totals.total += all_results[i].total
|
||||
totals.passed += all_results[i].passed
|
||||
totals.failed += all_results[i].failed
|
||||
// Spawn actor tests if any
|
||||
if (all_actor_tests.length > 0) {
|
||||
log.console(`Running ${all_actor_tests.length} actor test(s)...`)
|
||||
for (var i = 0; i < all_actor_tests.length; i++) {
|
||||
spawn_actor_test(all_actor_tests[i])
|
||||
}
|
||||
}
|
||||
|
||||
log.console(`----------------------------------------`)
|
||||
log.console(`Tests: ${totals.passed} passed, ${totals.failed} failed, ${totals.total} total`)
|
||||
// Handle messages from actor tests
|
||||
// Actor tests should send: { type: "test_result", passed: bool, error: string|null }
|
||||
function handle_actor_message(msg) {
|
||||
// Find the pending test from this sender
|
||||
var sender = msg.$sender
|
||||
var found_idx = -1
|
||||
for (var i = 0; i < pending_actor_tests.length; i++) {
|
||||
if (pending_actor_tests[i].actor == sender) {
|
||||
found_idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (found_idx == -1) return // Unknown sender
|
||||
|
||||
var entry = pending_actor_tests[found_idx]
|
||||
pending_actor_tests.splice(found_idx, 1)
|
||||
|
||||
var end_time = time.number()
|
||||
entry.duration_ns = Math.round((end_time - entry.start_time) * 1000000000)
|
||||
|
||||
if (msg.type == "test_result") {
|
||||
if (msg.passed) {
|
||||
entry.status = "passed"
|
||||
log.console(` PASS ${entry.test}`)
|
||||
} else {
|
||||
entry.status = "failed"
|
||||
entry.error = { message: msg.error || "Test failed" }
|
||||
if (msg.stack) entry.error.stack = msg.stack
|
||||
log.console(` FAIL ${entry.test}: ${entry.error.message}`)
|
||||
}
|
||||
} else {
|
||||
entry.status = "failed"
|
||||
entry.error = { message: `Unexpected message type: ${msg.type}` }
|
||||
log.console(` FAIL ${entry.test}: unexpected message`)
|
||||
}
|
||||
|
||||
actor_test_results.push(entry)
|
||||
check_completion()
|
||||
}
|
||||
|
||||
// Check for timed out actor tests
|
||||
function check_timeouts() {
|
||||
var now = time.number()
|
||||
var timed_out = []
|
||||
|
||||
for (var i = pending_actor_tests.length - 1; i >= 0; i--) {
|
||||
var entry = pending_actor_tests[i]
|
||||
var elapsed_ms = (now - entry.start_time) * 1000
|
||||
if (elapsed_ms > ACTOR_TEST_TIMEOUT) {
|
||||
timed_out.push(i)
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < timed_out.length; i++) {
|
||||
var idx = timed_out[i]
|
||||
var entry = pending_actor_tests[idx]
|
||||
pending_actor_tests.splice(idx, 1)
|
||||
|
||||
entry.status = "failed"
|
||||
entry.error = { message: "Test timed out" }
|
||||
entry.duration_ns = ACTOR_TEST_TIMEOUT * 1000000
|
||||
actor_test_results.push(entry)
|
||||
log.console(` TIMEOUT ${entry.test}`)
|
||||
}
|
||||
|
||||
if (pending_actor_tests.length > 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 (pending_actor_tests.length > 0) return
|
||||
|
||||
finalized = true
|
||||
finalize_results()
|
||||
}
|
||||
|
||||
function finalize_results() {
|
||||
// Add actor test results to all_results
|
||||
for (var i = 0; i < actor_test_results.length; i++) {
|
||||
var r = actor_test_results[i]
|
||||
// Find or create package result
|
||||
var pkg_result = null
|
||||
for (var j = 0; j < all_results.length; 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 }
|
||||
all_results.push(pkg_result)
|
||||
}
|
||||
|
||||
// Find or create file result
|
||||
var file_result = null
|
||||
for (var j = 0; j < pkg_result.files.length; 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 }
|
||||
pkg_result.files.push(file_result)
|
||||
}
|
||||
|
||||
file_result.tests.push(r)
|
||||
pkg_result.total++
|
||||
if (r.status == "passed") {
|
||||
pkg_result.passed++
|
||||
file_result.passed++
|
||||
} else {
|
||||
pkg_result.failed++
|
||||
file_result.failed++
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate totals
|
||||
var totals = { total: 0, passed: 0, failed: 0 }
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
totals.total += all_results[i].total
|
||||
totals.passed += all_results[i].passed
|
||||
totals.failed += all_results[i].failed
|
||||
}
|
||||
|
||||
log.console(`----------------------------------------`)
|
||||
log.console(`Tests: ${totals.passed} passed, ${totals.failed} failed, ${totals.total} total`)
|
||||
|
||||
generate_reports(totals)
|
||||
$_.stop()
|
||||
}
|
||||
|
||||
// If no actor tests, finalize immediately
|
||||
if (all_actor_tests.length == 0) {
|
||||
// Calculate totals
|
||||
var totals = { total: 0, passed: 0, failed: 0 }
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
totals.total += all_results[i].total
|
||||
totals.passed += all_results[i].passed
|
||||
totals.failed += all_results[i].failed
|
||||
}
|
||||
|
||||
log.console(`----------------------------------------`)
|
||||
log.console(`Tests: ${totals.passed} passed, ${totals.failed} failed, ${totals.total} total`)
|
||||
} else {
|
||||
// Start timeout checker
|
||||
$_.delay(check_timeouts, 1000)
|
||||
}
|
||||
|
||||
|
||||
// Generate Reports
|
||||
var timestamp = Math.floor(time.number()).toString()
|
||||
var report_dir = `.cell/reports/test_${timestamp}`
|
||||
ensure_dir(report_dir)
|
||||
// Generate Reports function
|
||||
function generate_reports(totals) {
|
||||
var timestamp = Math.floor(time.number()).toString()
|
||||
var report_dir = `.cell/reports/test_${timestamp}`
|
||||
ensure_dir(report_dir)
|
||||
|
||||
// Generate test.txt
|
||||
var txt_report = `TEST REPORT
|
||||
// Generate test.txt
|
||||
var txt_report = `TEST REPORT
|
||||
Date: ${time.text(time.number())}
|
||||
Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
|
||||
|
||||
=== SUMMARY ===
|
||||
`
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
var pkg_res = all_results[i]
|
||||
if (pkg_res.total == 0) continue
|
||||
txt_report += `Package: ${pkg_res.package}\n`
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
var f = pkg_res.files[j]
|
||||
var status = f.failed == 0 ? "PASS" : "FAIL"
|
||||
txt_report += ` [${status}] ${f.name} (${f.passed}/${f.tests.length})\n`
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
var pkg_res = all_results[i]
|
||||
if (pkg_res.total == 0) continue
|
||||
txt_report += `Package: ${pkg_res.package}\n`
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
var f = pkg_res.files[j]
|
||||
var status = f.failed == 0 ? "PASS" : "FAIL"
|
||||
txt_report += ` [${status}] ${f.name} (${f.passed}/${f.tests.length})\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
txt_report += `\n=== FAILURES ===\n`
|
||||
var has_failures = false
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
var pkg_res = all_results[i]
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
var f = pkg_res.files[j]
|
||||
for (var k = 0; k < f.tests.length; k++) {
|
||||
var t = f.tests[k]
|
||||
if (t.status == "failed") {
|
||||
has_failures = true
|
||||
txt_report += `FAIL: ${pkg_res.package} :: ${f.name} :: ${t.test}\n`
|
||||
if (t.error) {
|
||||
txt_report += ` Message: ${t.error.message}\n`
|
||||
if (t.error.stack) {
|
||||
txt_report += ` Stack:\n${t.error.stack.split('\n').map(function(l){return ` ${l}`}).join('\n')}\n`
|
||||
txt_report += `\n=== FAILURES ===\n`
|
||||
var has_failures = false
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
var pkg_res = all_results[i]
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
var f = pkg_res.files[j]
|
||||
for (var k = 0; k < f.tests.length; k++) {
|
||||
var t = f.tests[k]
|
||||
if (t.status == "failed") {
|
||||
has_failures = true
|
||||
txt_report += `FAIL: ${pkg_res.package} :: ${f.name} :: ${t.test}\n`
|
||||
if (t.error) {
|
||||
txt_report += ` Message: ${t.error.message}\n`
|
||||
if (t.error.stack) {
|
||||
txt_report += ` Stack:\n${t.error.stack.split('\n').map(function(l){return ` ${l}`}).join('\n')}\n`
|
||||
}
|
||||
}
|
||||
txt_report += `\n`
|
||||
}
|
||||
txt_report += `\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!has_failures) txt_report += `None\n`
|
||||
if (!has_failures) txt_report += `None\n`
|
||||
|
||||
txt_report += `\n=== DETAILED RESULTS ===\n`
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
var pkg_res = all_results[i]
|
||||
if (pkg_res.total == 0) continue
|
||||
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
var f = pkg_res.files[j]
|
||||
for (var k = 0; k < f.tests.length; k++) {
|
||||
var t = f.tests[k]
|
||||
var dur = `${t.duration_ns}ns`
|
||||
var status = t.status == "passed" ? "PASS" : "FAIL"
|
||||
txt_report += `[${status}] ${pkg_res.package} ${t.test} (${dur})\n`
|
||||
txt_report += `\n=== DETAILED RESULTS ===\n`
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
var pkg_res = all_results[i]
|
||||
if (pkg_res.total == 0) continue
|
||||
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
var f = pkg_res.files[j]
|
||||
for (var k = 0; k < f.tests.length; k++) {
|
||||
var t = f.tests[k]
|
||||
var dur = `${t.duration_ns || 0}ns`
|
||||
var status = t.status == "passed" ? "PASS" : "FAIL"
|
||||
txt_report += `[${status}] ${pkg_res.package} ${t.test} (${dur})\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fd.slurpwrite(`${report_dir}/test.txt`, utf8.encode(txt_report))
|
||||
log.console(`Report written to ${report_dir}/test.txt`)
|
||||
|
||||
// Generate JSON per package
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
var pkg_res = all_results[i]
|
||||
if (pkg_res.total == 0) continue
|
||||
|
||||
var pkg_tests = []
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
var f = pkg_res.files[j]
|
||||
for (var k = 0; k < f.tests.length; k++) {
|
||||
pkg_tests.push(f.tests[k])
|
||||
}
|
||||
}
|
||||
|
||||
var json_path = `${report_dir}/${pkg_res.package.replace(/\//g, '_')}.json`
|
||||
fd.slurpwrite(json_path, utf8.encode(json.encode(pkg_tests)))
|
||||
}
|
||||
}
|
||||
|
||||
fd.slurpwrite(`${report_dir}/test.txt`, utf8.encode(txt_report))
|
||||
log.console(`Report written to ${report_dir}/test.txt`)
|
||||
|
||||
// Generate JSON per package
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
var pkg_res = all_results[i]
|
||||
if (pkg_res.total == 0) continue
|
||||
|
||||
var pkg_tests = []
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
var f = pkg_res.files[j]
|
||||
for (var k = 0; k < f.tests.length; k++) {
|
||||
pkg_tests.push(f.tests[k])
|
||||
}
|
||||
}
|
||||
|
||||
var json_path = `${report_dir}/${pkg_res.package.replace(/\//g, '_')}.json`
|
||||
fd.slurpwrite(json_path, utf8.encode(json.encode(pkg_tests)))
|
||||
// If no actor tests, generate reports and stop immediately
|
||||
if (all_actor_tests.length == 0) {
|
||||
generate_reports(totals)
|
||||
$_.stop()
|
||||
} else {
|
||||
// Set up portal to receive messages from actor tests
|
||||
$_.portal(function(msg) {
|
||||
handle_actor_message(msg)
|
||||
})
|
||||
}
|
||||
|
||||
$_.stop()
|
||||
|
||||
Reference in New Issue
Block a user