new syntax for internals

This commit is contained in:
2026-02-10 11:03:01 -06:00
parent 6df3b741cf
commit 4deb0e2577
9 changed files with 483 additions and 292 deletions

301
test.ce
View File

@@ -8,7 +8,7 @@ var dbg = use('js')
// run gc with dbg.gc()
if (!args) args = []
var _args = args == null ? [] : args
var target_pkg = null // null = current package
var target_test = null // null = all tests, otherwise specific test file
@@ -22,19 +22,20 @@ var actor_test_results = []
// Check if current directory is a valid cell package
function is_valid_package(dir) {
if (!dir) dir = '.'
return fd.is_file(dir + '/cell.toml')
var _dir = dir == null ? '.' : dir
return fd.is_file(_dir + '/cell.toml')
}
// Get current package name from cell.toml or null
function get_current_package_name() {
if (!is_valid_package('.')) return null
try {
var _load = function() {
var config = pkg.load_config(null)
return config.package || 'local'
} catch (e) {
} disruption {
return 'local'
}
return _load()
}
// Parse arguments
@@ -48,16 +49,21 @@ function get_current_package_name() {
function parse_args() {
var cleaned_args = []
for (var i = 0; i < length(args); i++) {
if (args[i] == '-g') {
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 {
push(cleaned_args, args[i])
push(cleaned_args, _args[i])
}
}
args = cleaned_args
_args = cleaned_args
if (length(args) == 0) {
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')
@@ -67,7 +73,7 @@ function parse_args() {
return true
}
if (args[0] == 'all') {
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')
@@ -77,14 +83,14 @@ function parse_args() {
return true
}
if (args[0] == 'package') {
if (length(args) < 2) {
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') {
if (_args[1] == 'all') {
// cell test package all - run tests from all packages
all_pkgs = true
log.console('Testing all packages...')
@@ -92,18 +98,19 @@ function parse_args() {
}
// cell test package <name> [test]
var name = args[1]
name = _args[1]
// Check if package exists in lock or is a local path
var lock = shop.load_lock()
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('.')) {
var resolved = pkg.alias_to_package(null, name)
resolved = pkg.alias_to_package(null, name)
if (resolved) {
target_pkg = resolved
} else {
@@ -116,9 +123,9 @@ function parse_args() {
}
}
if (length(args) >= 3) {
if (length(_args) >= 3) {
// cell test package <name> <test>
target_test = args[2]
target_test = _args[2]
}
log.console(`Testing package: ${target_pkg}`)
@@ -126,7 +133,7 @@ function parse_args() {
}
// cell test tests/suite or cell test <path> - specific test file
var test_path = args[0]
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, '/')) {
@@ -160,9 +167,10 @@ function ensure_dir(path) {
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
for (var i = 0; i < length(parts); i++) {
var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue
current += parts[i] + '/'
current = current + parts[i] + '/'
if (!fd.is_dir(current))
fd.mkdir(current)
}
@@ -189,23 +197,29 @@ function collect_actor_tests(package_name, specific_test) {
var files = pkg.list_files(package_name)
var actor_tests = []
for (var i = 0; i < length(files); i++) {
var f = files[i]
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) {
var test_name = text(f, 0, -3) // remove .ce
var match_name = 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
var test_base = test_name
var match_base = ends_with(match_name, '.ce') ? text(match_name, 0, -3) : match_name
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,{
push(actor_tests, {
package: package_name || "local",
file: f,
path: prefix + '/' + f
@@ -229,19 +243,19 @@ function spawn_actor_test(test_info) {
actor: null
}
try {
// Spawn the actor test - it should send back results
var _spawn = function() {
var actor_path = text(test_info.path, 0, -3) // remove .ce
entry.actor = $start(actor_path)
push(pending_actor_tests, entry)
} catch (e) {
} disruption {
entry.status = "failed"
entry.error = { message: `Failed to spawn actor: ${e}` }
entry.error = { message: `Failed to spawn actor` }
entry.duration_ns = 0
push(actor_test_results, entry)
log.console(` FAIL ${test_name}: `)
log.error(e)
log.error()
}
_spawn()
}
function run_tests(package_name, specific_test) {
@@ -260,17 +274,24 @@ function run_tests(package_name, specific_test) {
var files = pkg.list_files(package_name)
var test_files = []
for (var i = 0; i < length(files); i++) {
var f = files[i]
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) {
var test_name = text(f, 0, -3) // remove .cm
var match_name = 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
var match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
if (test_name != match_base) continue
}
push(test_files, f)
@@ -282,24 +303,31 @@ function run_tests(package_name, specific_test) {
else log.console(`Running tests for local package`)
}
for (var i = 0; i < length(test_files); i++) {
var f = test_files[i]
var mod_path = text(f, 0, -3) // remove .cm
var _load_file = null
for (i = 0; i < length(test_files); i++) {
f = test_files[i]
mod_path = text(f, 0, -3) // remove .cm
var file_result = {
file_result = {
name: f,
tests: [],
passed: 0,
failed: 0
}
try {
var test_mod
// For local packages (null), use the current directory as package context
_load_file = function() {
var test_mod = null
var use_pkg = package_name ? package_name : fd.realpath('.')
test_mod = shop.use(mod_path, use_pkg)
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
if (is_function(test_mod)) {
push(tests, {name: 'main', fn: test_mod})
} else if (is_object(test_mod)) {
@@ -312,50 +340,55 @@ function run_tests(package_name, specific_test) {
if (length(tests) > 0) {
log.console(` ${f}`)
for (var j = 0; j < length(tests); j++) {
var t = tests[j]
var test_entry = {
for (j = 0; j < length(tests); j++) {
t = tests[j]
test_entry = {
package: pkg_result.package,
test: t.name,
status: "pending",
duration_ns: 0
}
var start_time = time.number()
try {
start_time = time.number()
_test_error = null
_run_one = function() {
var ret = t.fn()
if (is_text(ret)) {
throw Error(ret)
_test_error = Error(ret)
disrupt
} else if (ret && (is_text(ret.message) || is_proto(ret, Error))) {
throw ret
_test_error = ret
disrupt
}
test_entry.status = "passed"
log.console(` PASS ${t.name}`)
pkg_result.passed++
file_result.passed++
} catch (e) {
} disruption {
var e = _test_error
test_entry.status = "failed"
test_entry.error = {
message: e,
stack: e.stack || ""
stack: (e && e.stack) ? e.stack : ""
}
if (e.name) test_entry.error.name = e.name
if (e && e.name) test_entry.error.name = e.name
if (is_object(e) && e.message) {
test_entry.error.message = 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 ')}`)
log.console(` ${text(array(test_entry.error.stack, '\n'), '\n ')}`)
}
pkg_result.failed++
file_result.failed++
}
var end_time = time.number()
_run_one()
end_time = time.number()
test_entry.duration_ns = round((end_time - start_time) * 1000000000)
push(file_result.tests, test_entry)
@@ -366,15 +399,15 @@ function run_tests(package_name, specific_test) {
}
}
} catch (e) {
log.console(` Error loading ${f}: ${e}`)
var test_entry = {
} disruption {
var test_entry = {
package: pkg_result.package,
test: "load_module",
status: "failed",
duration_ns: 0,
error: { message: `Error loading module: ${e}` }
error: { message: `Error loading module` }
}
log.console(` Error loading ${f}`)
push(file_result.tests, test_entry)
pkg_result.failed++
file_result.failed++
@@ -383,6 +416,7 @@ function run_tests(package_name, specific_test) {
dbg.gc()
}
}
_load_file()
push(pkg_result.files, file_result)
}
return pkg_result
@@ -390,6 +424,8 @@ function run_tests(package_name, specific_test) {
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
@@ -399,8 +435,8 @@ if (all_pkgs) {
}
// Then all packages in lock
var packages = shop.list_packages()
for (var i = 0; i < length(packages); i++) {
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))
}
@@ -412,7 +448,7 @@ if (all_pkgs) {
// Spawn actor tests if any
if (length(all_actor_tests) > 0) {
log.console(`Running ${length(all_actor_tests)} actor test(s)...`)
for (var i = 0; i < length(all_actor_tests); i++) {
for (i = 0; i < length(all_actor_tests); i++) {
spawn_actor_test(all_actor_tests[i])
}
}
@@ -421,7 +457,10 @@ if (length(all_actor_tests) > 0) {
function handle_actor_message(msg) {
var sender = msg.$sender
var found_idx = -1
for (var i = 0; i < length(pending_actor_tests); i++) {
var i = 0
var res = null
var entry = null
for (i = 0; i < length(pending_actor_tests); i++) {
if (pending_actor_tests[i].actor == sender) {
found_idx = i
break
@@ -445,9 +484,9 @@ function handle_actor_message(msg) {
results = [msg]
}
for (var i = 0; i < length(results); i++) {
var res = results[i] || {}
var entry = {
for (i = 0; i < length(results); i++) {
res = results[i] || {}
entry = {
package: base_entry.package,
file: base_entry.file,
test: res.test || base_entry.test + (length(results) > 1 ? `#${i+1}` : ""),
@@ -481,18 +520,22 @@ function handle_actor_message(msg) {
function check_timeouts() {
var now = time.number()
var timed_out = []
var i = 0
var entry = null
var elapsed_ms = null
var idx = null
for (var i = length(pending_actor_tests) - 1; i >= 0; i--) {
var entry = pending_actor_tests[i]
var elapsed_ms = (now - entry.start_time) * 1000
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 (var i = 0; i < length(timed_out); i++) {
var idx = timed_out[i]
var entry = pending_actor_tests[idx]
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"
@@ -519,11 +562,17 @@ function check_completion() {
}
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 (var i = 0; i < length(actor_test_results); i++) {
var r = actor_test_results[i]
var pkg_result = null
for (var j = 0; j < length(all_results); j++) {
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
@@ -534,8 +583,8 @@ function finalize_results() {
push(all_results, pkg_result)
}
var file_result = null
for (var j = 0; j < length(pkg_result.files); j++) {
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
@@ -559,7 +608,7 @@ function finalize_results() {
// Calculate totals
var totals = { total: 0, passed: 0, failed: 0 }
for (var i = 0; i < length(all_results); i++) {
for (i = 0; i < length(all_results); i++) {
totals.total += all_results[i].total
totals.passed += all_results[i].passed
totals.failed += all_results[i].failed
@@ -573,10 +622,10 @@ function finalize_results() {
}
// If no actor tests, finalize immediately
var totals
var totals = null
if (length(all_actor_tests) == 0) {
totals = { total: 0, passed: 0, failed: 0 }
for (var i = 0; i < length(all_results); i++) {
for (i = 0; i < length(all_results); i++) {
totals.total += all_results[i].total
totals.passed += all_results[i].passed
totals.failed += all_results[i].failed
@@ -593,6 +642,16 @@ 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())}
@@ -600,53 +659,53 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
=== SUMMARY ===
`
for (var i = 0; i < length(all_results); i++) {
var pkg_res = all_results[i]
for (i = 0; i < length(all_results); i++) {
pkg_res = all_results[i]
if (pkg_res.total == 0) continue
txt_report += `Package: ${pkg_res.package}\n`
for (var j = 0; j < length(pkg_res.files); j++) {
var f = pkg_res.files[j]
var status = f.failed == 0 ? "PASS" : "FAIL"
txt_report += ` [${status}] ${f.name} (${f.passed}/${length(f.tests)})\n`
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 += `\n=== FAILURES ===\n`
txt_report = txt_report + `\n=== FAILURES ===\n`
var has_failures = false
for (var i = 0; i < length(all_results); i++) {
var pkg_res = all_results[i]
for (var j = 0; j < length(pkg_res.files); j++) {
var f = pkg_res.files[j]
for (var k = 0; k < length(f.tests); k++) {
var t = f.tests[k]
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 += `FAIL: ${pkg_res.package} :: ${f.name} :: ${t.test}\n`
txt_report = txt_report + `FAIL: ${pkg_res.package} :: ${f.name} :: ${t.test}\n`
if (t.error) {
txt_report += ` Message: ${t.error.message}\n`
txt_report = txt_report + ` Message: ${t.error.message}\n`
if (t.error.stack) {
txt_report += ` Stack:\n${text(array(array(t.error.stack, '\n'), l => ` ${l}`), '\n')}\n`
txt_report = txt_report + ` Stack:\n${text(array(array(t.error.stack, '\n'), l => ` ${l}`), '\n')}\n`
}
}
txt_report += `\n`
txt_report = txt_report + `\n`
}
}
}
}
if (!has_failures) txt_report += `None\n`
if (!has_failures) txt_report = txt_report + `None\n`
txt_report += `\n=== DETAILED RESULTS ===\n`
for (var i = 0; i < length(all_results); i++) {
var pkg_res = all_results[i]
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 (var j = 0; j < length(pkg_res.files); j++) {
var f = pkg_res.files[j]
for (var k = 0; k < length(f.tests); 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`
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`
}
}
}
@@ -655,19 +714,19 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
log.console(`Report written to ${report_dir}/test.txt`)
// Generate JSON per package
for (var i = 0; i < length(all_results); i++) {
var pkg_res = all_results[i]
for (i = 0; i < length(all_results); i++) {
pkg_res = all_results[i]
if (pkg_res.total == 0) continue
var pkg_tests = []
for (var j = 0; j < length(pkg_res.files); j++) {
var f = pkg_res.files[j]
for (var k = 0; k < length(f.tests); k++) {
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])
}
}
var json_path = `${report_dir}/${replace(pkg_res.package, /\//, '_')}.json`
json_path = `${report_dir}/${replace(pkg_res.package, /\//, '_')}.json`
fd.slurpwrite(json_path, stone(blob(json.encode(pkg_tests))))
}
}