new syntax for internals
This commit is contained in:
@@ -865,6 +865,7 @@ $_.clock(_ => {
|
||||
var pkg = file_info ? file_info.package : null
|
||||
env.use = function(path) { return shop.use(path, pkg) }
|
||||
env.args = _cell.args.arg
|
||||
env.log = log
|
||||
|
||||
var script = text(fd.slurp(prog_path))
|
||||
var ast = analyze(script, prog_path)
|
||||
|
||||
Binary file not shown.
245
internal/shop.cm
245
internal/shop.cm
@@ -35,7 +35,8 @@ function ensure_dir(path) {
|
||||
if (fd.stat(path).isDirectory) return
|
||||
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 = current + parts[i] + '/'
|
||||
if (!fd.stat(current).isDirectory) {
|
||||
@@ -119,12 +120,16 @@ function split_explicit_package_import(path)
|
||||
if (!looks_explicit) return null
|
||||
|
||||
// Find the longest prefix that is an installed package
|
||||
for (var i = length(parts) - 1; i >= 1; i--) {
|
||||
var pkg_candidate = text(array(parts, 0, i), '/')
|
||||
var mod_path = text(array(parts, i), '/')
|
||||
var i = 0
|
||||
var pkg_candidate = null
|
||||
var mod_path = null
|
||||
var candidate_dir = null
|
||||
for (i = length(parts) - 1; i >= 1; i--) {
|
||||
pkg_candidate = text(array(parts, 0, i), '/')
|
||||
mod_path = text(array(parts, i), '/')
|
||||
if (!mod_path || length(mod_path) == 0) continue
|
||||
|
||||
var candidate_dir = get_packages_dir() + '/' + safe_package_path(pkg_candidate)
|
||||
candidate_dir = get_packages_dir() + '/' + safe_package_path(pkg_candidate)
|
||||
if (fd.is_file(candidate_dir + '/cell.toml'))
|
||||
return {package: pkg_candidate, path: mod_path}
|
||||
|
||||
@@ -158,8 +163,9 @@ function abs_path_to_package(package_dir)
|
||||
return 'core'
|
||||
}
|
||||
// Also check if core_dir is a symlink pointing to package_dir
|
||||
var core_target = null
|
||||
if (fd.is_link(core_dir)) {
|
||||
var core_target = fd.readlink(core_dir)
|
||||
core_target = fd.readlink(core_dir)
|
||||
if (core_target == package_dir || fd.realpath(core_dir) == package_dir) {
|
||||
return 'core'
|
||||
}
|
||||
@@ -181,9 +187,11 @@ function abs_path_to_package(package_dir)
|
||||
|
||||
// For local directories (e.g., linked targets), read the package name from cell.toml
|
||||
var _toml_path = package_dir + '/cell.toml'
|
||||
var content = null
|
||||
var cfg = null
|
||||
if (fd.is_file(_toml_path)) {
|
||||
var content = text(fd.slurp(_toml_path))
|
||||
var cfg = toml.decode(content)
|
||||
content = text(fd.slurp(_toml_path))
|
||||
cfg = toml.decode(content)
|
||||
if (cfg.package)
|
||||
return cfg.package
|
||||
}
|
||||
@@ -316,12 +324,16 @@ Shop.verify_package_name = function(pkg) {
|
||||
// Convert module package to download URL
|
||||
Shop.get_download_url = function(pkg, commit_hash) {
|
||||
var info = Shop.resolve_package_info(pkg)
|
||||
var parts = null
|
||||
var host = null
|
||||
var user = null
|
||||
var repo = null
|
||||
|
||||
if (info == 'gitea') {
|
||||
var parts = array(pkg, '/')
|
||||
var host = parts[0]
|
||||
var user = parts[1]
|
||||
var repo = parts[2]
|
||||
parts = array(pkg, '/')
|
||||
host = parts[0]
|
||||
user = parts[1]
|
||||
repo = parts[2]
|
||||
|
||||
return 'https://' + host + '/' + user + '/' + repo + '/archive/' + commit_hash + '.zip'
|
||||
}
|
||||
@@ -332,12 +344,16 @@ Shop.get_download_url = function(pkg, commit_hash) {
|
||||
// Get the API URL for checking remote git commits
|
||||
Shop.get_api_url = function(pkg) {
|
||||
var info = Shop.resolve_package_info(pkg)
|
||||
var parts = null
|
||||
var host = null
|
||||
var user = null
|
||||
var repo = null
|
||||
|
||||
if (info == 'gitea') {
|
||||
var parts = array(pkg, '/')
|
||||
var host = parts[0]
|
||||
var user = parts[1]
|
||||
var repo = parts[2]
|
||||
parts = array(pkg, '/')
|
||||
host = parts[0]
|
||||
user = parts[1]
|
||||
repo = parts[2]
|
||||
return 'https://' + host + '/api/v1/repos/' + user + '/' + repo + '/branches/'
|
||||
}
|
||||
|
||||
@@ -393,9 +409,12 @@ function inject_env(inject) {
|
||||
}
|
||||
|
||||
// Add capability injections
|
||||
for (var i = 0; i < length(inject); i++) {
|
||||
var inj = inject[i]
|
||||
var key = trim(inj, '$')
|
||||
var i = 0
|
||||
var inj = null
|
||||
var key = null
|
||||
for (i = 0; i < length(inject); i++) {
|
||||
inj = inject[i]
|
||||
key = trim(inj, '$')
|
||||
if (key == 'fd') env[key] = fd
|
||||
else env[key] = my$_[key]
|
||||
}
|
||||
@@ -409,15 +428,19 @@ function inject_bindings_code(inject) {
|
||||
var runtime_fns = ['logical', 'some', 'every', 'starts_with', 'ends_with',
|
||||
'actor', 'is_actor', 'log', 'send',
|
||||
'fallback', 'parallel', 'race', 'sequence']
|
||||
for (var i = 0; i < length(runtime_fns); i++) {
|
||||
var fn = runtime_fns[i]
|
||||
var i = 0
|
||||
var fn = null
|
||||
var inj = null
|
||||
var key = null
|
||||
for (i = 0; i < length(runtime_fns); i++) {
|
||||
fn = runtime_fns[i]
|
||||
push(lines, `var ${fn} = env["${fn}"];`)
|
||||
}
|
||||
|
||||
// Capability bindings ($delay, $start, etc.)
|
||||
for (var i = 0; i < length(inject); i++) {
|
||||
var inj = inject[i]
|
||||
var key = trim(inj, '$')
|
||||
for (i = 0; i < length(inject); i++) {
|
||||
inj = inject[i]
|
||||
key = trim(inj, '$')
|
||||
push(lines, `var $${key} = env["${key}"];`)
|
||||
}
|
||||
return text(lines, '\n')
|
||||
@@ -474,26 +497,34 @@ function resolve_mod_fn(path, pkg) {
|
||||
// given a path and a package context
|
||||
// return module info about where it was found
|
||||
function resolve_locator(path, ctx)
|
||||
{
|
||||
{
|
||||
var explicit = split_explicit_package_import(path)
|
||||
var explicit_path = null
|
||||
var fn = null
|
||||
var core_dir = null
|
||||
var core_file_path = null
|
||||
var is_core = null
|
||||
var scope = null
|
||||
var alias_path = null
|
||||
|
||||
if (explicit) {
|
||||
if (is_internal_path(explicit.path) && ctx && explicit.package != ctx)
|
||||
explicit = null
|
||||
}
|
||||
if (explicit) {
|
||||
var explicit_path = get_packages_dir() + '/' + safe_package_path(explicit.package) + '/' + explicit.path
|
||||
explicit_path = get_packages_dir() + '/' + safe_package_path(explicit.package) + '/' + explicit.path
|
||||
if (fd.is_file(explicit_path)) {
|
||||
var fn = resolve_mod_fn(explicit_path, explicit.package)
|
||||
fn = resolve_mod_fn(explicit_path, explicit.package)
|
||||
return {path: explicit_path, scope: SCOPE_PACKAGE, symbol: fn}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. If no context, resolve from core only
|
||||
if (!ctx) {
|
||||
var core_dir = Shop.get_core_dir()
|
||||
var core_file_path = core_dir + '/' + path
|
||||
core_dir = Shop.get_core_dir()
|
||||
core_file_path = core_dir + '/' + path
|
||||
if (fd.is_file(core_file_path)) {
|
||||
var fn = resolve_mod_fn(core_file_path, 'core')
|
||||
fn = resolve_mod_fn(core_file_path, 'core')
|
||||
return {path: core_file_path, scope: SCOPE_CORE, symbol: fn}
|
||||
}
|
||||
return null
|
||||
@@ -502,7 +533,7 @@ function resolve_locator(path, ctx)
|
||||
// check in ctx package
|
||||
// If ctx is an absolute path (starts with /), use it directly
|
||||
// Otherwise, look it up in the packages directory
|
||||
var ctx_dir
|
||||
var ctx_dir = null
|
||||
if (starts_with(ctx, '/')) {
|
||||
ctx_dir = ctx
|
||||
} else {
|
||||
@@ -511,10 +542,10 @@ function resolve_locator(path, ctx)
|
||||
var ctx_path = ctx_dir + '/' + path
|
||||
|
||||
if (fd.is_file(ctx_path)) {
|
||||
var fn = resolve_mod_fn(ctx_path, ctx)
|
||||
fn = resolve_mod_fn(ctx_path, ctx)
|
||||
// Check if ctx is the core package (either by name or by path)
|
||||
var is_core = (ctx == 'core') || (ctx_dir == Shop.get_core_dir())
|
||||
var scope = is_core ? SCOPE_CORE : SCOPE_LOCAL
|
||||
is_core = (ctx == 'core') || (ctx_dir == Shop.get_core_dir())
|
||||
scope = is_core ? SCOPE_CORE : SCOPE_LOCAL
|
||||
return {path: ctx_path, scope: scope, symbol: fn}
|
||||
}
|
||||
|
||||
@@ -524,24 +555,24 @@ function resolve_locator(path, ctx)
|
||||
// check for aliased dependency
|
||||
var alias = pkg_tools.split_alias(ctx, path)
|
||||
if (alias) {
|
||||
var alias_path = get_packages_dir() + '/' + safe_package_path(alias.package) + '/' + alias.path
|
||||
alias_path = get_packages_dir() + '/' + safe_package_path(alias.package) + '/' + alias.path
|
||||
if (fd.is_file(alias_path)) {
|
||||
var fn = resolve_mod_fn(alias_path, ctx)
|
||||
fn = resolve_mod_fn(alias_path, ctx)
|
||||
return {path: alias_path, scope:SCOPE_PACKAGE, symbol:fn}
|
||||
}
|
||||
}
|
||||
|
||||
var package_path = get_packages_dir() + '/' + safe_package_path(path)
|
||||
var package_path = get_packages_dir() + '/' + safe_package_path(path)
|
||||
if (fd.is_file(package_path)) {
|
||||
var fn = resolve_mod_fn(package_path, ctx)
|
||||
fn = resolve_mod_fn(package_path, ctx)
|
||||
return {path: package_path, scope: SCOPE_PACKAGE, symbol: fn}
|
||||
}
|
||||
|
||||
// 4. Check core as fallback
|
||||
var core_dir = Shop.get_core_dir()
|
||||
var core_file_path = core_dir + '/' + path
|
||||
core_dir = Shop.get_core_dir()
|
||||
core_file_path = core_dir + '/' + path
|
||||
if (fd.is_file(core_file_path)) {
|
||||
var fn = resolve_mod_fn(core_file_path, 'core')
|
||||
fn = resolve_mod_fn(core_file_path, 'core')
|
||||
return {path: core_file_path, scope: SCOPE_CORE, symbol: fn}
|
||||
}
|
||||
|
||||
@@ -573,7 +604,7 @@ Shop.open_package_dylib = function(pkg) {
|
||||
var link_target = link.get_target(pkg)
|
||||
var resolved_pkg = link_target ? link_target : pkg
|
||||
|
||||
var pkg_dir;
|
||||
var pkg_dir = null
|
||||
if (starts_with(resolved_pkg, '/')) {
|
||||
pkg_dir = resolved_pkg
|
||||
} else {
|
||||
@@ -581,9 +612,11 @@ Shop.open_package_dylib = function(pkg) {
|
||||
}
|
||||
|
||||
var toml_path = pkg_dir + '/cell.toml'
|
||||
var content = null
|
||||
var cfg = null
|
||||
if (fd.is_file(toml_path)) {
|
||||
var content = text(fd.slurp(toml_path))
|
||||
var cfg = toml.decode(content)
|
||||
content = text(fd.slurp(toml_path))
|
||||
cfg = toml.decode(content)
|
||||
if (cfg.dependencies) {
|
||||
arrfor(array(cfg.dependencies), function(alias, i) {
|
||||
var dep_pkg = cfg.dependencies[alias]
|
||||
@@ -606,12 +639,19 @@ Shop.open_package_dylib = function(pkg) {
|
||||
// Core is never loaded as a dynamic library via dlopen
|
||||
function resolve_c_symbol(path, package_context) {
|
||||
var explicit = split_explicit_package_import(path)
|
||||
var sym = null
|
||||
var dl_path = null
|
||||
var _path = null
|
||||
var core_sym = null
|
||||
var canon_pkg = null
|
||||
var mod_name = null
|
||||
|
||||
if (explicit) {
|
||||
if (is_internal_path(explicit.path) && package_context && explicit.package != package_context)
|
||||
explicit = null
|
||||
}
|
||||
if (explicit) {
|
||||
var sym = make_c_symbol(explicit.package, explicit.path)
|
||||
sym = make_c_symbol(explicit.package, explicit.path)
|
||||
if (os.internal_exists(sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
@@ -622,7 +662,7 @@ function resolve_c_symbol(path, package_context) {
|
||||
}
|
||||
|
||||
Shop.open_package_dylib(explicit.package)
|
||||
var dl_path = get_lib_path(explicit.package)
|
||||
dl_path = get_lib_path(explicit.package)
|
||||
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
|
||||
return {
|
||||
symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) },
|
||||
@@ -635,8 +675,8 @@ function resolve_c_symbol(path, package_context) {
|
||||
|
||||
// If no package context, only check core internal symbols
|
||||
if (!package_context || package_context == 'core') {
|
||||
var _path = replace(path, '/', '_')
|
||||
var core_sym = `js_${_path}_use`
|
||||
_path = replace(path, '/', '_')
|
||||
core_sym = `js_${_path}_use`
|
||||
if (os.internal_exists(core_sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(core_sym) },
|
||||
@@ -648,7 +688,7 @@ function resolve_c_symbol(path, package_context) {
|
||||
}
|
||||
|
||||
// 1. Check own package first (internal, then dylib)
|
||||
var sym = make_c_symbol(package_context, path)
|
||||
sym = make_c_symbol(package_context, path)
|
||||
if (os.internal_exists(sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
@@ -658,7 +698,7 @@ function resolve_c_symbol(path, package_context) {
|
||||
}
|
||||
|
||||
Shop.open_package_dylib(package_context)
|
||||
var dl_path = get_lib_path(package_context)
|
||||
dl_path = get_lib_path(package_context)
|
||||
|
||||
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
|
||||
return {
|
||||
@@ -674,10 +714,10 @@ function resolve_c_symbol(path, package_context) {
|
||||
// 2. Check aliased package imports (e.g. 'prosperon/sprite')
|
||||
var pkg_alias = get_import_package(path)
|
||||
if (pkg_alias) {
|
||||
var canon_pkg = get_aliased_package(path, package_context)
|
||||
canon_pkg = get_aliased_package(path, package_context)
|
||||
if (canon_pkg) {
|
||||
var mod_name = get_import_name(path)
|
||||
var sym = make_c_symbol(canon_pkg, mod_name)
|
||||
mod_name = get_import_name(path)
|
||||
sym = make_c_symbol(canon_pkg, mod_name)
|
||||
|
||||
// Check internal first
|
||||
if (os.internal_exists(sym)) {
|
||||
@@ -691,7 +731,7 @@ function resolve_c_symbol(path, package_context) {
|
||||
|
||||
// Then check dylib
|
||||
Shop.open_package_dylib(canon_pkg)
|
||||
var dl_path = get_lib_path(canon_pkg)
|
||||
dl_path = get_lib_path(canon_pkg)
|
||||
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
|
||||
return {
|
||||
symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) },
|
||||
@@ -704,7 +744,7 @@ function resolve_c_symbol(path, package_context) {
|
||||
}
|
||||
|
||||
// 3. Check core internal symbols (core is never a dynamic library)
|
||||
var core_sym = `js_${replace(path, '/', '_')}_use`
|
||||
core_sym = `js_${replace(path, '/', '_')}_use`
|
||||
if (os.internal_exists(core_sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(core_sym) },
|
||||
@@ -732,31 +772,37 @@ function resolve_module_info(path, package_context) {
|
||||
if (min_scope == 999)
|
||||
return null
|
||||
|
||||
var cache_key
|
||||
var cache_key = null
|
||||
var real_path = null
|
||||
var real_info = null
|
||||
var pkg_alias = null
|
||||
var canon_pkg = null
|
||||
var mod_name = null
|
||||
|
||||
if (mod_resolve.scope == SCOPE_CORE) {
|
||||
cache_key = 'core/' + path
|
||||
} else if (mod_resolve.scope < 900 && mod_resolve.path) {
|
||||
var real_path = fd.realpath(mod_resolve.path)
|
||||
real_path = fd.realpath(mod_resolve.path)
|
||||
if (real_path) {
|
||||
var real_info = Shop.file_info(real_path)
|
||||
real_info = Shop.file_info(real_path)
|
||||
if (real_info.package && real_info.name)
|
||||
cache_key = real_info.package + '/' + real_info.name
|
||||
else
|
||||
cache_key = real_path
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!cache_key) {
|
||||
if (min_scope == SCOPE_CORE)
|
||||
cache_key = 'core/' + path
|
||||
else if (min_scope == SCOPE_LOCAL && package_context)
|
||||
cache_key = package_context + '/' + path
|
||||
else if (min_scope == SCOPE_PACKAGE) {
|
||||
var pkg_alias = get_import_package(path)
|
||||
pkg_alias = get_import_package(path)
|
||||
if (pkg_alias) {
|
||||
var canon_pkg = get_canonical_package(pkg_alias, package_context)
|
||||
canon_pkg = get_canonical_package(pkg_alias, package_context)
|
||||
if (canon_pkg) {
|
||||
var mod_name = get_import_name(path)
|
||||
mod_name = get_import_name(path)
|
||||
cache_key = canon_pkg + '/' + mod_name
|
||||
} else
|
||||
cache_key = path
|
||||
@@ -807,20 +853,26 @@ function execute_module(info)
|
||||
var c_resolve = info.c_resolve
|
||||
var mod_resolve = info.mod_resolve
|
||||
|
||||
var used
|
||||
var used = null
|
||||
var context = null
|
||||
var file_info = null
|
||||
var inject = null
|
||||
var env = null
|
||||
var pkg = null
|
||||
var use_fn = null
|
||||
|
||||
if (mod_resolve.scope < 900) {
|
||||
var context = null
|
||||
context = null
|
||||
if (c_resolve.scope < 900) {
|
||||
context = call_c_module(c_resolve)
|
||||
}
|
||||
|
||||
// Get file info to determine inject list
|
||||
var file_info = Shop.file_info(mod_resolve.path)
|
||||
var inject = Shop.script_inject_for(file_info)
|
||||
var env = inject_env(inject)
|
||||
var pkg = file_info.package
|
||||
var use_fn = make_use_fn(pkg)
|
||||
file_info = Shop.file_info(mod_resolve.path)
|
||||
inject = Shop.script_inject_for(file_info)
|
||||
env = inject_env(inject)
|
||||
pkg = file_info.package
|
||||
use_fn = make_use_fn(pkg)
|
||||
|
||||
// Call with signature: setup_module(args, use, env)
|
||||
// args is null for module loading
|
||||
@@ -942,17 +994,18 @@ Shop.fetch = function(pkg) {
|
||||
// Check if we have the zip cached
|
||||
var zip_blob = get_cached_zip(pkg, commit)
|
||||
|
||||
var actual_hash = null
|
||||
if (zip_blob) {
|
||||
// If we have a hash on record, verify it
|
||||
if (expected_hash) {
|
||||
var actual_hash = text(crypto.blake2(zip_blob), 'h')
|
||||
actual_hash = text(crypto.blake2(zip_blob), 'h')
|
||||
if (actual_hash == expected_hash) {
|
||||
return { status: 'cached' }
|
||||
}
|
||||
log.console("Zip hash mismatch for " + pkg + ", re-fetching...")
|
||||
} else {
|
||||
// No hash stored yet - compute and store it
|
||||
var actual_hash = text(crypto.blake2(zip_blob), 'h')
|
||||
actual_hash = text(crypto.blake2(zip_blob), 'h')
|
||||
lock_entry.zip_hash = actual_hash
|
||||
Shop.save_lock(lock)
|
||||
return { status: 'cached' }
|
||||
@@ -1004,10 +1057,12 @@ Shop.extract = function(pkg) {
|
||||
// Check if already extracted at correct commit
|
||||
var lock = Shop.load_lock()
|
||||
var lock_entry = lock[pkg]
|
||||
var extracted_commit_file = null
|
||||
var extracted_commit = null
|
||||
if (lock_entry && lock_entry.commit) {
|
||||
var extracted_commit_file = target_dir + '/.cell_commit'
|
||||
extracted_commit_file = target_dir + '/.cell_commit'
|
||||
if (fd.is_file(extracted_commit_file)) {
|
||||
var extracted_commit = trim(text(fd.slurp(extracted_commit_file)))
|
||||
extracted_commit = trim(text(fd.slurp(extracted_commit_file)))
|
||||
if (extracted_commit == lock_entry.commit) {
|
||||
// Already extracted at this commit, skip
|
||||
return true
|
||||
@@ -1059,6 +1114,7 @@ Shop.update = function(pkg) {
|
||||
|
||||
log.console(`checking ${pkg}`)
|
||||
|
||||
var new_entry = null
|
||||
if (info == 'local') {
|
||||
// Check if local path exists
|
||||
if (!fd.is_dir(pkg)) {
|
||||
@@ -1066,7 +1122,7 @@ Shop.update = function(pkg) {
|
||||
return null
|
||||
}
|
||||
// Local packages always get a lock entry
|
||||
var new_entry = {
|
||||
new_entry = {
|
||||
type: 'local',
|
||||
updated: time.number()
|
||||
}
|
||||
@@ -1089,7 +1145,7 @@ Shop.update = function(pkg) {
|
||||
if (local_commit == remote_commit)
|
||||
return null
|
||||
|
||||
var new_entry = {
|
||||
new_entry = {
|
||||
type: info,
|
||||
commit: remote_commit,
|
||||
updated: time.number()
|
||||
@@ -1114,21 +1170,28 @@ function install_zip(zip_blob, target_dir) {
|
||||
var count = zip.count()
|
||||
var created_dirs = {}
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
var i = 0
|
||||
var filename = null
|
||||
var slash_pos = null
|
||||
var rel_path = null
|
||||
var full_path = null
|
||||
var dir_path = null
|
||||
var file_data = null
|
||||
for (i = 0; i < count; i++) {
|
||||
if (zip.is_directory(i)) continue
|
||||
var filename = zip.get_filename(i)
|
||||
var slash_pos = search(filename, '/')
|
||||
filename = zip.get_filename(i)
|
||||
slash_pos = search(filename, '/')
|
||||
if (slash_pos == null) continue
|
||||
if (slash_pos + 1 >= length(filename)) continue
|
||||
var rel_path = text(filename, slash_pos + 1)
|
||||
var full_path = target_dir + '/' + rel_path
|
||||
var dir_path = fd.dirname(full_path)
|
||||
rel_path = text(filename, slash_pos + 1)
|
||||
full_path = target_dir + '/' + rel_path
|
||||
dir_path = fd.dirname(full_path)
|
||||
|
||||
if (!created_dirs[dir_path]) {
|
||||
ensure_dir(dir_path)
|
||||
created_dirs[dir_path] = true
|
||||
}
|
||||
var file_data = zip.slurp(filename)
|
||||
file_data = zip.slurp(filename)
|
||||
|
||||
stone(file_data)
|
||||
|
||||
@@ -1151,14 +1214,16 @@ Shop.remove = function(pkg) {
|
||||
|
||||
Shop.get = function(pkg) {
|
||||
var lock = Shop.load_lock()
|
||||
|
||||
var info = null
|
||||
var commit = null
|
||||
|
||||
if (!lock[pkg]) {
|
||||
var info = Shop.resolve_package_info(pkg)
|
||||
info = Shop.resolve_package_info(pkg)
|
||||
if (!info) {
|
||||
print("Invalid package: " + pkg); disrupt
|
||||
}
|
||||
|
||||
var commit = null
|
||||
|
||||
commit = null
|
||||
if (info != 'local') {
|
||||
commit = fetch_remote_hash(pkg)
|
||||
if (!commit) {
|
||||
@@ -1216,9 +1281,11 @@ function get_package_scripts(package)
|
||||
{
|
||||
var files = pkg_tools.list_files(package)
|
||||
var scripts = []
|
||||
|
||||
for (var i = 0; i < length(files); i++) {
|
||||
var file = files[i]
|
||||
|
||||
var i = 0
|
||||
var file = null
|
||||
for (i = 0; i < length(files); i++) {
|
||||
file = files[i]
|
||||
if (ends_with(file, '.cm') || ends_with(file, '.ce')) {
|
||||
push(scripts, file)
|
||||
}
|
||||
|
||||
@@ -4,19 +4,21 @@ var pkg = use('package')
|
||||
|
||||
// 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
|
||||
if (!_dir) _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()
|
||||
}
|
||||
|
||||
// Get the directory for a package
|
||||
@@ -37,9 +39,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)
|
||||
}
|
||||
|
||||
6
link.cm
6
link.cm
@@ -34,7 +34,8 @@ function ensure_dir(path) {
|
||||
if (fd.stat(path).isDirectory) return
|
||||
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 = current + parts[i] + '/'
|
||||
if (!fd.stat(current).isDirectory) {
|
||||
@@ -119,8 +120,9 @@ Link.add = function(canonical, target, shop) {
|
||||
// Read the target's cell.toml to find its dependencies
|
||||
var target_path = starts_with(target, '/') ? target : get_package_abs_dir(target)
|
||||
var toml_path = target_path + '/cell.toml'
|
||||
var _install_deps = null
|
||||
if (fd.is_file(toml_path)) {
|
||||
var _install_deps = function() {
|
||||
_install_deps = function() {
|
||||
var content = text(fd.slurp(toml_path))
|
||||
var cfg = toml.decode(content)
|
||||
if (cfg && cfg.dependencies) {
|
||||
|
||||
106
package.cm
106
package.cm
@@ -101,11 +101,12 @@ package.alias_to_package = function(name, alias)
|
||||
}
|
||||
|
||||
// alias is optional
|
||||
package.add_dependency = function(name, locator, alias = locator)
|
||||
package.add_dependency = function(name, locator, alias)
|
||||
{
|
||||
var _alias = alias == null ? locator : alias
|
||||
var config = package.load_config(name)
|
||||
if (!config.dependencies) config.dependencies = {}
|
||||
config.dependencies[alias] = locator
|
||||
config.dependencies[_alias] = locator
|
||||
package.save_config(name, config)
|
||||
}
|
||||
|
||||
@@ -115,10 +116,11 @@ package.remove_dependency = function(name, locator)
|
||||
var config = package.load_config(name)
|
||||
if (!config.dependencies) return
|
||||
|
||||
var alias = null
|
||||
if (config.dependencies[locator])
|
||||
delete config.dependencies[locator]
|
||||
else {
|
||||
var alias = package.find_alias(name, locator)
|
||||
alias = package.find_alias(name, locator)
|
||||
if (alias)
|
||||
delete config.dependencies[alias]
|
||||
}
|
||||
@@ -133,8 +135,9 @@ package.find_package_dir = function(file)
|
||||
if (fd.is_file(dir))
|
||||
dir = fd.dirname(dir)
|
||||
|
||||
var toml_path = null
|
||||
while (dir && length(dir) > 0) {
|
||||
var toml_path = dir + '/cell.toml'
|
||||
toml_path = dir + '/cell.toml'
|
||||
if (fd.is_file(toml_path)) {
|
||||
return dir
|
||||
}
|
||||
@@ -163,9 +166,11 @@ package.split_alias = function(name, path)
|
||||
if (!config) return null
|
||||
|
||||
var deps = config.dependencies
|
||||
var dep_locator = null
|
||||
var remaining_path = null
|
||||
if (deps && deps[first_part]) {
|
||||
var dep_locator = deps[first_part]
|
||||
var remaining_path = text(array(parts, 1), '/')
|
||||
dep_locator = deps[first_part]
|
||||
remaining_path = text(array(parts, 1), '/')
|
||||
return { package: dep_locator, path: remaining_path }
|
||||
}
|
||||
return null
|
||||
@@ -208,18 +213,23 @@ package.list_files = function(pkg) {
|
||||
var walk = function(current_dir, current_prefix) {
|
||||
var list = fd.readdir(current_dir)
|
||||
if (!list) return
|
||||
|
||||
for (var i = 0; i < length(list); i++) {
|
||||
var item = list[i]
|
||||
|
||||
var i = 0
|
||||
var item = null
|
||||
var full_path = null
|
||||
var rel_path = null
|
||||
var st = null
|
||||
for (i = 0; i < length(list); i++) {
|
||||
item = list[i]
|
||||
if (item == '.' || item == '..') continue
|
||||
if (starts_with(item, '.')) continue
|
||||
|
||||
if (starts_with(item, '.')) continue
|
||||
|
||||
// Skip build directories in root
|
||||
|
||||
var full_path = current_dir + "/" + item
|
||||
var rel_path = current_prefix ? current_prefix + "/" + item : item
|
||||
|
||||
var st = fd.stat(full_path)
|
||||
full_path = current_dir + "/" + item
|
||||
rel_path = current_prefix ? current_prefix + "/" + item : item
|
||||
|
||||
st = fd.stat(full_path)
|
||||
if (st.isDirectory) {
|
||||
walk(full_path, rel_path)
|
||||
} else {
|
||||
@@ -237,7 +247,8 @@ package.list_files = function(pkg) {
|
||||
package.list_modules = function(name) {
|
||||
var files = package.list_files(name)
|
||||
var modules = []
|
||||
for (var i = 0; i < length(files); i++) {
|
||||
var i = 0
|
||||
for (i = 0; i < length(files); i++) {
|
||||
if (ends_with(files[i], '.cm')) {
|
||||
push(modules, text(files[i], 0, -3))
|
||||
}
|
||||
@@ -248,7 +259,8 @@ package.list_modules = function(name) {
|
||||
package.list_programs = function(name) {
|
||||
var files = package.list_files(name)
|
||||
var programs = []
|
||||
for (var i = 0; i < length(files); i++) {
|
||||
var i = 0
|
||||
for (i = 0; i < length(files); i++) {
|
||||
if (ends_with(files[i], '.ce')) {
|
||||
push(programs, text(files[i], 0, -3))
|
||||
}
|
||||
@@ -265,14 +277,16 @@ package.get_flags = function(name, flag_type, target) {
|
||||
var flags = []
|
||||
|
||||
// Base flags
|
||||
var base = null
|
||||
var target_flags = null
|
||||
if (config.compilation && config.compilation[flag_type]) {
|
||||
var base = config.compilation[flag_type]
|
||||
base = config.compilation[flag_type]
|
||||
flags = array(flags, filter(array(base, /\s+/), function(f) { return length(f) > 0 }))
|
||||
}
|
||||
|
||||
|
||||
// Target-specific flags
|
||||
if (target && config.compilation && config.compilation[target] && config.compilation[target][flag_type]) {
|
||||
var target_flags = config.compilation[target][flag_type]
|
||||
target_flags = config.compilation[target][flag_type]
|
||||
flags = array(flags, filter(array(target_flags, /\s+/), function(f) { return length(f) > 0 }))
|
||||
}
|
||||
|
||||
@@ -290,23 +304,36 @@ package.get_c_files = function(name, target, exclude_main) {
|
||||
// Group files by their base name (without target suffix)
|
||||
var groups = {} // base_key -> { generic: file, variants: { target: file } }
|
||||
|
||||
for (var i = 0; i < length(files); i++) {
|
||||
var file = files[i]
|
||||
var i = 0
|
||||
var file = null
|
||||
var ext = null
|
||||
var base = null
|
||||
var name_part = null
|
||||
var dir_part = null
|
||||
var dir = null
|
||||
var is_variant = null
|
||||
var variant_target = null
|
||||
var generic_name = null
|
||||
var t = 0
|
||||
var suffix = null
|
||||
var group_key = null
|
||||
for (i = 0; i < length(files); i++) {
|
||||
file = files[i]
|
||||
if (!ends_with(file, '.c') && !ends_with(file, '.cpp')) continue
|
||||
|
||||
var ext = ends_with(file, '.cpp') ? '.cpp' : '.c'
|
||||
var base = text(file, 0, -length(ext))
|
||||
var name_part = fd.basename(base)
|
||||
var dir_part = fd.dirname(base)
|
||||
var dir = (dir_part && dir_part != '.') ? dir_part + '/' : ''
|
||||
|
||||
|
||||
ext = ends_with(file, '.cpp') ? '.cpp' : '.c'
|
||||
base = text(file, 0, -length(ext))
|
||||
name_part = fd.basename(base)
|
||||
dir_part = fd.dirname(base)
|
||||
dir = (dir_part && dir_part != '.') ? dir_part + '/' : ''
|
||||
|
||||
// Check for target suffix
|
||||
var is_variant = false
|
||||
var variant_target = null
|
||||
var generic_name = name_part
|
||||
|
||||
for (var t = 0; t < length(known_targets); t++) {
|
||||
var suffix = '_' + known_targets[t]
|
||||
is_variant = false
|
||||
variant_target = null
|
||||
generic_name = name_part
|
||||
|
||||
for (t = 0; t < length(known_targets); t++) {
|
||||
suffix = '_' + known_targets[t]
|
||||
if (ends_with(name_part, suffix)) {
|
||||
is_variant = true
|
||||
variant_target = known_targets[t]
|
||||
@@ -315,7 +342,7 @@ package.get_c_files = function(name, target, exclude_main) {
|
||||
}
|
||||
}
|
||||
|
||||
var group_key = dir + generic_name + ext
|
||||
group_key = dir + generic_name + ext
|
||||
if (!groups[group_key]) {
|
||||
groups[group_key] = { generic: null, variants: {} }
|
||||
}
|
||||
@@ -332,18 +359,19 @@ package.get_c_files = function(name, target, exclude_main) {
|
||||
arrfor(array(groups), function(key) {
|
||||
var group = groups[key]
|
||||
var selected = null
|
||||
|
||||
var basename = null
|
||||
|
||||
// Prefer target-specific variant if available
|
||||
if (target && group.variants[target]) {
|
||||
selected = group.variants[target]
|
||||
} else if (group.generic) {
|
||||
selected = group.generic
|
||||
}
|
||||
|
||||
|
||||
if (selected) {
|
||||
// Skip main.c if requested
|
||||
if (exclude_main) {
|
||||
var basename = fd.basename(selected)
|
||||
basename = fd.basename(selected)
|
||||
if (basename == 'main.c' || starts_with(basename, 'main_')) return
|
||||
}
|
||||
push(result, selected)
|
||||
|
||||
301
test.ce
301
test.ce
@@ -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))))
|
||||
}
|
||||
}
|
||||
|
||||
5
time.cm
5
time.cm
@@ -124,14 +124,15 @@ function time_number(_rec) {
|
||||
var dst = r.dst ? 1 : 0
|
||||
var yday = r.yday || 0
|
||||
|
||||
var i = 0
|
||||
if (year > time.epoch) {
|
||||
var i = time.epoch
|
||||
i = time.epoch
|
||||
while (i < year) {
|
||||
c = c + time.day * time.yearsize(i)
|
||||
i = i + 1
|
||||
}
|
||||
} else if (year < time.epoch) {
|
||||
var i = time.epoch - 1
|
||||
i = time.epoch - 1
|
||||
while (i > year) {
|
||||
c = c + time.day * time.yearsize(i)
|
||||
i = i - 1
|
||||
|
||||
96
toml.cm
96
toml.cm
@@ -26,23 +26,33 @@ function parse_toml(toml_text) {
|
||||
var current_section = result
|
||||
var current_section_name = ''
|
||||
|
||||
for (var i = 0; i < length(lines); i++) {
|
||||
var line = trim(lines[i])
|
||||
var i = 0
|
||||
var line = null
|
||||
var inner = null
|
||||
var section_path = null
|
||||
var j = 0
|
||||
var key = null
|
||||
var eq_index = null
|
||||
var key_part = null
|
||||
var value = null
|
||||
var unquoted = null
|
||||
for (i = 0; i < length(lines); i++) {
|
||||
line = trim(lines[i])
|
||||
if (line == null) line = lines[i]
|
||||
// Skip empty lines and comments
|
||||
if (!line || starts_with(line, '#')) continue
|
||||
|
||||
// Section header
|
||||
if (starts_with(line, '[') && ends_with(line, ']')) {
|
||||
var inner = text(line, 1, -1)
|
||||
var section_path = parse_key_path(inner)
|
||||
inner = text(line, 1, -1)
|
||||
section_path = parse_key_path(inner)
|
||||
if (section_path == null) return null
|
||||
|
||||
current_section = result
|
||||
current_section_name = text(section_path, '.')
|
||||
|
||||
for (var j = 0; j < length(section_path); j++) {
|
||||
var key = section_path[j]
|
||||
for (j = 0; j < length(section_path); j++) {
|
||||
key = section_path[j]
|
||||
|
||||
// Only treat null as "missing"; do not clobber false/0/""
|
||||
if (current_section[key] == null) {
|
||||
@@ -58,18 +68,18 @@ function parse_toml(toml_text) {
|
||||
}
|
||||
|
||||
// Key-value pair
|
||||
var eq_index = search(line, '=')
|
||||
eq_index = search(line, '=')
|
||||
if (eq_index != null && eq_index > 0) {
|
||||
var key_part = trim(text(line, 0, eq_index))
|
||||
var value = trim(text(line, eq_index + 1))
|
||||
key_part = trim(text(line, 0, eq_index))
|
||||
value = trim(text(line, eq_index + 1))
|
||||
if (key_part == null) key_part = trim(text(line, 0, eq_index))
|
||||
if (value == null) value = trim(text(line, eq_index + 1))
|
||||
|
||||
var key = parse_key(key_part)
|
||||
key = parse_key(key_part)
|
||||
if (key == null) return null
|
||||
|
||||
if (starts_with(value, '"') && ends_with(value, '"')) {
|
||||
var unquoted = text(value, 1, -1)
|
||||
unquoted = text(value, 1, -1)
|
||||
current_section[key] = toml_unescape(unquoted)
|
||||
if (current_section[key] == null) return null
|
||||
} else if (starts_with(value, '[') && ends_with(value, ']')) {
|
||||
@@ -91,9 +101,9 @@ function parse_toml(toml_text) {
|
||||
|
||||
function parse_key(str) {
|
||||
if (!is_text(str)) return null
|
||||
|
||||
var inner = null
|
||||
if (starts_with(str, '"') && ends_with(str, '"')) {
|
||||
var inner = text(str, 1, -1)
|
||||
inner = text(str, 1, -1)
|
||||
return toml_unescape(inner)
|
||||
}
|
||||
return str
|
||||
@@ -107,12 +117,15 @@ function parse_key_path(str) {
|
||||
var current = ''
|
||||
var in_quote = false
|
||||
|
||||
for (var i = 0; i < length(str); i++) {
|
||||
var c = str[i]
|
||||
var i = 0
|
||||
var c = null
|
||||
var piece = null
|
||||
for (i = 0; i < length(str); i++) {
|
||||
c = str[i]
|
||||
if (c == '"' && (i == 0 || str[i - 1] != '\\')) {
|
||||
in_quote = !in_quote
|
||||
} else if (c == '.' && !in_quote) {
|
||||
var piece = trim(current)
|
||||
piece = trim(current)
|
||||
if (piece == null) piece = trim(current)
|
||||
push(parts, parse_key(piece))
|
||||
current = ''
|
||||
@@ -140,14 +153,17 @@ function parse_array(str) {
|
||||
var current = ''
|
||||
var in_quotes = false
|
||||
|
||||
for (var i = 0; i < length(s); i++) {
|
||||
var ch = s[i]
|
||||
var i = 0
|
||||
var ch = null
|
||||
var piece = null
|
||||
for (i = 0; i < length(s); i++) {
|
||||
ch = s[i]
|
||||
|
||||
if (ch == '"' && (i == 0 || s[i - 1] != '\\')) {
|
||||
in_quotes = !in_quotes
|
||||
current = current + ch
|
||||
} else if (ch == ',' && !in_quotes) {
|
||||
var piece = trim(current)
|
||||
piece = trim(current)
|
||||
if (piece == null) piece = trim(current)
|
||||
push(items, parse_value(piece))
|
||||
current = ''
|
||||
@@ -181,12 +197,14 @@ function encode_toml(obj) {
|
||||
var result = []
|
||||
|
||||
function encode_value(value) {
|
||||
var items = null
|
||||
var i = 0
|
||||
if (is_text(value)) return '"' + toml_escape(value) + '"'
|
||||
if (is_logical(value)) return value ? 'true' : 'false'
|
||||
if (is_number(value)) return text(value)
|
||||
if (is_array(value)) {
|
||||
var items = []
|
||||
for (var i = 0; i < length(value); i++) push(items, encode_value(value[i]))
|
||||
items = []
|
||||
for (i = 0; i < length(value); i++) push(items, encode_value(value[i]))
|
||||
return '[' + text(items, ', ') + ']'
|
||||
}
|
||||
return text(value)
|
||||
@@ -201,29 +219,41 @@ function encode_toml(obj) {
|
||||
|
||||
// First pass: encode top-level simple values
|
||||
var keys = array(obj)
|
||||
for (var i = 0; i < length(keys); i++) {
|
||||
var key = keys[i]
|
||||
var value = obj[key]
|
||||
var i = 0
|
||||
var key = null
|
||||
var value = null
|
||||
for (i = 0; i < length(keys); i++) {
|
||||
key = keys[i]
|
||||
value = obj[key]
|
||||
if (!is_object(value)) push(result, quote_key(key) + ' = ' + encode_value(value))
|
||||
}
|
||||
|
||||
// Second pass: encode nested objects
|
||||
function encode_section(o, path) {
|
||||
var keys = array(o)
|
||||
for (var i = 0; i < length(keys); i++) {
|
||||
var key = keys[i]
|
||||
var value = o[key]
|
||||
var i = 0
|
||||
var key = null
|
||||
var value = null
|
||||
var quoted = null
|
||||
var section_path = null
|
||||
var section_keys = null
|
||||
var j = 0
|
||||
var sk = null
|
||||
var sv = null
|
||||
for (i = 0; i < length(keys); i++) {
|
||||
key = keys[i]
|
||||
value = o[key]
|
||||
|
||||
if (is_object(value)) {
|
||||
var quoted = quote_key(key)
|
||||
var section_path = path ? path + '.' + quoted : quoted
|
||||
quoted = quote_key(key)
|
||||
section_path = path ? path + '.' + quoted : quoted
|
||||
push(result, '[' + section_path + ']')
|
||||
|
||||
// Direct properties
|
||||
var section_keys = array(value)
|
||||
for (var j = 0; j < length(section_keys); j++) {
|
||||
var sk = section_keys[j]
|
||||
var sv = value[sk]
|
||||
section_keys = array(value)
|
||||
for (j = 0; j < length(section_keys); j++) {
|
||||
sk = section_keys[j]
|
||||
sv = value[sk]
|
||||
if (!is_object(sv)) push(result, quote_key(sk) + ' = ' + encode_value(sv))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user