299 lines
9.3 KiB
Plaintext
299 lines
9.3 KiB
Plaintext
var shop = use('shop')
|
|
var fd = use('fd')
|
|
var time = use('time')
|
|
var json = use('json')
|
|
var utf8 = use('utf8')
|
|
|
|
if (!args) args = []
|
|
|
|
var target_pkg = null // null = current, otherwise canonical path
|
|
var all_pkgs = false
|
|
|
|
if (args.length > 0) {
|
|
if (args[0] == 'package') {
|
|
if (args.length < 2) {
|
|
log.console(`Usage: cell test package <name>`)
|
|
$_.stop()
|
|
return
|
|
}
|
|
var name = args[1]
|
|
var resolved = shop.resolve_alias(name, null)
|
|
if (resolved) {
|
|
target_pkg = resolved.pkg
|
|
log.console(`Testing package: ${resolved.alias} (${resolved.pkg})`)
|
|
} else {
|
|
log.console(`Package not found: ${name}`)
|
|
$_.stop()
|
|
return
|
|
}
|
|
} else if (args[0] == 'all') {
|
|
all_pkgs = true
|
|
log.console(`Testing all packages...`)
|
|
} else {
|
|
log.console(`Usage: cell test [package <name> | all]`)
|
|
$_.stop()
|
|
return
|
|
}
|
|
}
|
|
|
|
function ensure_dir(path) {
|
|
if (fd.is_dir(path)) return true
|
|
|
|
var parts = path.split('/')
|
|
var current = ''
|
|
for (var i = 0; i < parts.length; i++) {
|
|
if (parts[i] == '') continue
|
|
current += `${parts[i]}/`
|
|
if (!fd.is_dir(current)) {
|
|
fd.mkdir(current)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
function run_tests(pkg) {
|
|
var prefix = pkg ? `.cell/modules/${pkg}` : "."
|
|
var tests_dir = `${prefix}/tests`
|
|
|
|
var pkg_result = {
|
|
package: pkg || "local",
|
|
files: [],
|
|
total: 0,
|
|
passed: 0,
|
|
failed: 0
|
|
}
|
|
|
|
if (!fd.is_dir(tests_dir)) return pkg_result
|
|
|
|
var files = shop.list_files(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
|
|
if (f.startsWith("tests/") && f.endsWith(".cm")) {
|
|
test_files.push(f)
|
|
}
|
|
}
|
|
|
|
if (test_files.length > 0) {
|
|
if (pkg) log.console(`Running tests for ${pkg}`)
|
|
else log.console(`Running tests for local package`)
|
|
}
|
|
|
|
for (var i = 0; i < test_files.length; i++) {
|
|
var f = test_files[i]
|
|
var mod_path = f.substring(0, f.length - 3) // remove .cm
|
|
|
|
var file_result = {
|
|
name: f,
|
|
tests: [],
|
|
passed: 0,
|
|
failed: 0
|
|
}
|
|
|
|
try {
|
|
var test_mod
|
|
if (pkg) {
|
|
test_mod = shop.use(mod_path, pkg)
|
|
} else {
|
|
test_mod = globalThis.use(mod_path)
|
|
}
|
|
|
|
var tests = []
|
|
if (typeof test_mod == 'function') {
|
|
tests.push({name: 'main', fn: test_mod})
|
|
} else if (typeof test_mod == 'object') {
|
|
for (var k in test_mod) {
|
|
if (typeof test_mod[k] == 'function') {
|
|
tests.push({name: k, fn: test_mod[k]})
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tests.length > 0) {
|
|
log.console(` ${f}`)
|
|
for (var j = 0; j < tests.length; j++) {
|
|
var t = tests[j]
|
|
var test_entry = {
|
|
package: pkg_result.package,
|
|
test: t.name,
|
|
status: "pending",
|
|
duration_ns: 0
|
|
}
|
|
|
|
var start_time = time.number()
|
|
try {
|
|
var ret = t.fn()
|
|
|
|
if (typeof ret == 'string') {
|
|
throw new Error(ret)
|
|
} else if (ret && (typeof ret.message == 'string' || ret instanceof Error)) {
|
|
throw ret
|
|
}
|
|
|
|
test_entry.status = "passed"
|
|
log.console(` PASS ${t.name}`)
|
|
pkg_result.passed++
|
|
file_result.passed++
|
|
} catch (e) {
|
|
test_entry.status = "failed"
|
|
test_entry.error = {
|
|
message: e.toString(),
|
|
stack: e.stack || ""
|
|
}
|
|
if (e.name) test_entry.error.name = e.name
|
|
|
|
// If it's an object but not a native Error, try to extract more info
|
|
if (typeof e == 'object' && e.message) {
|
|
test_entry.error.message = e.message
|
|
}
|
|
|
|
log.console(` FAIL ${t.name} ${test_entry.error.message}`)
|
|
if (test_entry.error.stack) {
|
|
log.console(` ${test_entry.error.stack.split('\n').join('\n ')}`)
|
|
}
|
|
|
|
pkg_result.failed++
|
|
file_result.failed++
|
|
}
|
|
var end_time = time.number()
|
|
test_entry.duration_ns = Math.round((end_time - start_time) * 1000000000)
|
|
|
|
file_result.tests.push(test_entry)
|
|
pkg_result.total++
|
|
}
|
|
}
|
|
|
|
} catch (e) {
|
|
log.console(` Error loading ${f}: ${e}`)
|
|
// Treat load error as a file failure?
|
|
// Or maybe add a dummy test entry for "load"
|
|
var test_entry = {
|
|
package: pkg_result.package,
|
|
test: "load_module",
|
|
status: "failed",
|
|
duration_ns: 0,
|
|
error: { message: `Error loading module: ${e}` }
|
|
}
|
|
file_result.tests.push(test_entry)
|
|
pkg_result.failed++
|
|
file_result.failed++
|
|
pkg_result.total++
|
|
}
|
|
pkg_result.files.push(file_result)
|
|
}
|
|
return pkg_result
|
|
}
|
|
|
|
var all_results = []
|
|
|
|
if (all_pkgs) {
|
|
// Run local first
|
|
all_results.push(run_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]))
|
|
}
|
|
} else {
|
|
all_results.push(run_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
|
|
}
|
|
|
|
log.console(`----------------------------------------`)
|
|
log.console(`Tests: ${totals.passed} passed, ${totals.failed} failed, ${totals.total} total`)
|
|
|
|
|
|
// Generate Reports
|
|
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
|
|
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`
|
|
}
|
|
}
|
|
|
|
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`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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`
|
|
}
|
|
}
|
|
}
|
|
|
|
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)))
|
|
}
|
|
|
|
$_.stop()
|