Files
herolib/test_basic.vsh
despiegk 3547f04a79 Merge branch 'development_heropods' into development_nile_installers
* development_heropods: (21 commits)
  test: Ignore virt/heropods/network_test.v in CI
  feat: implement container keep-alive feature
  test: Add comprehensive heropods network and container tests
  refactor: Refactor Mycelium configuration and dependencies
  feat: Add Mycelium IPv6 overlay networking
  test: Replace hero binary checks with network test
  feat: Add iptables FORWARD rules for bridge
  Revert "feat: Add `pods` command for container management"
  feat: Add `pods` command for container management
  chore: Enable execution of cmd_run
  feat: Add `run` command for Heroscript execution
  feat: Separate initialization and configuration
  refactor: Remove hero binary installation from rootfs
  refactor: Integrate logger and refactor network operations
  feat: Implement container networking and improve lifecycle
  feat: Auto-install hero binary in containers
  feat: Add container management actions for heropods
  feat: Add heropods library to plbook
  refactor: Rename heropods variable and method
  refactor: Rename container factory to heropods
  ...
2025-11-25 18:39:08 +01:00

286 lines
7.3 KiB
GLSL
Executable File

#!/usr/bin/env -S v -cc gcc -n -w -gc none -d use_openssl -enable-globals run
import os
import flag
import time
import json
const cache_file = '/tmp/herolib_tests.json'
const test_expiry_seconds = 3600 // 1 hour
struct TestCache {
mut:
tests map[string]i64 // Map of test paths to last successful run timestamp
}
// Load the test cache from JSON file
fn load_test_cache() TestCache {
if !os.exists(cache_file) {
return TestCache{
tests: map[string]i64{}
}
}
content := os.read_file(cache_file) or { return TestCache{
tests: map[string]i64{}
} }
return json.decode(TestCache, content) or { return TestCache{
tests: map[string]i64{}
} }
}
fn in_github_actions() bool {
a := os.environ()['GITHUB_ACTIONS'] or { return false }
return true
}
// Save the test cache to JSON file
fn save_test_cache(cache TestCache) {
json_str := json.encode_pretty(cache)
os.write_file(cache_file, json_str) or { eprintln('Failed to save test cache: ${err}') }
}
// Check if a test needs to be rerun based on timestamp
fn should_rerun_test(cache TestCache, test_key string) bool {
last_run := cache.tests[test_key] or { return true }
now := time.now().unix()
return (now - last_run) > test_expiry_seconds
}
// Update test timestamp in cache
fn update_test_cache(mut cache TestCache, test_key string) {
cache.tests[test_key] = time.now().unix()
save_test_cache(cache)
}
// Normalize a path for consistent handling
fn normalize_path(path string) string {
mut norm_path := os.abs_path(path)
norm_path = norm_path.replace('//', '/') // Remove any double slashes
return norm_path
}
// Get normalized and relative path
fn get_normalized_paths(path string, base_dir_norm string) (string, string) {
// base_dir_norm is already normalized
norm_path := normalize_path(path)
rel_path := norm_path.replace(base_dir_norm + '/', '')
return norm_path, rel_path
}
// Generate a cache key from a path
fn get_cache_key(path string, base_dir string) string {
_, rel_path := get_normalized_paths(path, base_dir)
// Create consistent key format
return rel_path.replace('/', '_').trim('_').to_lower()
}
// Check if a file should be ignored or marked as error based on its path
fn process_test_file(path string, base_dir string, test_files_ignore []string, test_files_error []string, mut cache TestCache) ! {
// Get normalized paths
norm_path, rel_path := get_normalized_paths(path, base_dir)
mut should_ignore := false
mut is_error := false
if !path.to_lower().contains('_test.v') {
return
}
// Check if any ignore pattern matches the path
for pattern in test_files_ignore {
println('Check ignore test: ${pattern} -- ${rel_path} ::: ${rel_path.contains(pattern.trim_space())}')
if pattern.trim_space() != '' && rel_path.contains(pattern.trim_space()) {
println('Ignoring test: ${rel_path}')
return
}
}
// Check if any error pattern matches the path
for pattern in test_files_error {
if pattern.trim_space() != '' && rel_path.contains(pattern.trim_space()) {
println('Ignoring test because is error: ${rel_path}')
return
}
}
dotest(norm_path, base_dir, mut cache)!
}
fn dotest(path string, base_dir string, mut cache TestCache) ! {
norm_path, _ := get_normalized_paths(path, base_dir)
test_key := get_cache_key(norm_path, base_dir)
// Check if test result is cached and still valid
if !should_rerun_test(cache, test_key) {
println('Test cached (passed): ${path}')
return
}
println('Running test (not cached or expired): ${path} (key: ${test_key})')
cmd := 'v -stats -enable-globals -n -w -gc none test ${norm_path}'
println(cmd)
// Use system() instead of execute() to avoid hanging on unclosed file descriptors
exit_code := os.system(cmd)
if exit_code != 0 {
eprintln('Test failed: ${path} (exit code: ${exit_code})')
exit(1)
}
// Update cache with successful test run
cache.tests[test_key] = time.now().unix()
save_test_cache(cache)
println('Test passed: ${path}')
}
/////////////////////////
/////////////////////////
// Parse command line flags
mut fp := flag.new_flag_parser(os.args)
fp.application('test_basic')
fp.description('Run tests for herolib')
remove_cache := fp.bool('r', `r`, false, 'Remove cache file before running tests')
fp.finalize() or {
eprintln(err)
exit(1)
}
// Remove cache file if -r flag is set
if remove_cache && os.exists(cache_file) {
os.rm(cache_file) or {
eprintln('Failed to remove cache file: ${err}')
exit(1)
}
println('Removed cache file: ${cache_file}')
}
abs_dir_of_script := dir(@FILE)
norm_dir_of_script := normalize_path(abs_dir_of_script)
os.chdir(abs_dir_of_script) or { panic(err) }
// can use // inside this list as well to ignore temporary certain dirs, useful for testing
tests := '
lib/data
lib/osal
lib/lang
lib/clients
lib/core
lib/develop
lib/hero/heromodels
lib/virt/heropods
lib/virt/crun
'
// the following tests have no prio and can be ignored
mut tests_ignore := '
notifier_test.v
clients/meilisearch
clients/zdb
tst/
clients/openai
systemd_process_test.v
data/graphdb
data/radixtree
clients/livekit
core/playcmds
doctree/
jina/
params_reflection_test.v
regex_convert_test.v
python/
rust_test.v
rclone/
qdrant/
sshagent_test.v
virt/kubernetes/
'
if in_github_actions() {
println('**** WE ARE IN GITHUB ACTION')
tests_ignore += '\nosal/tmux\n'
tests_ignore += '\nvirt/heropods\n' // Requires root for network bridge operations (ip link add)
}
tests_error := '
encoderhero
tmux_window_test.v
tmux_test.v
startupmanager_test.v
python_test.v
flist_test.v
mnemonic_test.v
decode_test.v
codegen_test.v
generate_test.v
dbfs_test.v
namedb_test.v
timetools_test.v
code/codeparser
gittools_test.v
link_def_test.v
python_test.v
ipaddress_test.v
'
// Split tests into array and remove empty lines
test_files := tests.split('\n').filter(it.trim_space() != '')
test_files_ignore := tests_ignore.split('\n').filter(it.trim_space() != '')
test_files_error := tests_error.split('\n').filter(it.trim_space() != '')
// Load test cache
mut cache := load_test_cache()
println('Test cache loaded from ${cache_file}')
println('Cache contains ${cache.tests.len} entries')
if cache.tests.len > 0 {
println('Sample cache entries:')
mut count := 0
for key, timestamp in cache.tests {
if count < 3 {
println(' ${key}: ${timestamp}')
count++
}
}
}
println('tests to ignore')
println(tests_ignore)
// Run each test with proper v command flags
println('Starting main test loop with ${test_files.len} test entries')
for test in test_files {
if test.trim_space() == '' || test.trim_space().starts_with('//')
|| test.trim_space().starts_with('#') {
continue
}
full_path := os.join_path(abs_dir_of_script, test)
if !os.exists(full_path) {
eprintln('Path does not exist: ${full_path}')
exit(1)
}
if os.is_dir(full_path) {
// If directory, run tests for each .v file in it recursively
files := os.walk_ext(full_path, '.v')
println('Found ${files.len} .v files in directory: ${full_path}')
for file in files {
println('Processing file: ${file}')
process_test_file(file, norm_dir_of_script, test_files_ignore, test_files_error, mut
cache)!
}
println('Completed directory: ${full_path}')
} else if os.is_file(full_path) {
println('Processing single file: ${full_path}')
process_test_file(full_path, norm_dir_of_script, test_files_ignore, test_files_error, mut
cache)!
}
}
println('All (non skipped) tests ok')
println('Test runner completed successfully. Exiting.')
exit(0)