Files
puter/src/backend/tools/test.js
T
KernelDeimos df6fc8a432 test: fix test kernel after adding runtimle modules
RuntimeModules (a feature for extensions) can be accessed by core
modules through a registry exposed in Context by Kernel. However,
TestKernel was not exposing this same registry which caused an error
when module installation occurred during tests.
2025-09-29 14:27:07 -04:00

285 lines
8.3 KiB
JavaScript

// METADATA // {"ai-commented":{"service":"claude"}}
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { AdvancedBase } = require("@heyputer/putility");
const useapi = require("useapi");
const { BaseService, EssentialModules } = require("../exports");
const CoreModule = require("../src/CoreModule");
const { Context } = require("../src/util/context");
const { Kernel } = require("../src/Kernel");
const { HTTPThumbnailService } = require("../src/services/thumbnails/HTTPThumbnailService");
const { RuntimeModuleRegistry } = require("../src/extension/RuntimeModuleRegistry");
/**
* A simple implementation of the log interface for the test kernel.
*/
class TestLogger {
constructor () {
console.log(
`\x1B[36;1mBoot logger started :)\x1B[0m`,
);
}
info (...args) {
console.log(
'\x1B[36;1m[TESTKERNEL/INFO]\x1B[0m',
...args,
);
}
error (...args) {
console.log(
'\x1B[31;1m[TESTKERNEL/ERROR]\x1B[0m',
...args,
);
}
}
/**
* TestKernel class extends AdvancedBase to provide a testing environment for Puter services
* Implements a simplified version of the main Kernel for testing purposes, including:
* - Module management and installation
* - Service container initialization
* - Custom logging functionality
* - Context creation and management
* Does not include full service initialization or legacy service support
*/
class TestKernel extends AdvancedBase {
constructor () {
super();
this.modules = [];
this.useapi = useapi();
/**
* Initializes the useapi instance for the test kernel.
* Defines base Module and Service classes in the useapi context.
* @returns {void}
*/
this.useapi.withuse(() => {
def('Module', AdvancedBase)
def('Service', BaseService)
});
this.logfn_ = (...a) => a;
this.runtimeModuleRegistry = new RuntimeModuleRegistry();
}
add_module (module) {
this.modules.push(module);
}
/**
* Adds a module to the test kernel's module list
* @param {Module} module - The module instance to add
* @description Stores the provided module in the kernel's internal modules array for later installation
*/
boot () {
const { consoleLogManager } = require('../src/util/consolelog');
consoleLogManager.initialize_proxy_methods();
consoleLogManager.decorate_all(({ manager, replace }, ...a) => {
replace(...this.logfn_(...a));
});
const { Container } = require('../src/services/Container');
this.testLogger = new TestLogger();
const services = new Container({ logger: this.testLogger });
this.services = services;
// app.set('services', services);
const root_context = Context.create({
services,
useapi: this.useapi,
['runtime-modules']: this.runtimeModuleRegistry,
}, 'app');
globalThis.root_context = root_context;
root_context.arun(async () => {
await this._install_modules();
// await this._boot_services();
});
// Error.stackTraceLimit = Infinity;
Error.stackTraceLimit = 200;
}
/**
* Installs modules into the test kernel environment
*/
async _install_modules () {
const { services } = this;
const mod_install_root_context = Context.get();
for ( const module of this.modules ) {
console.log('module?"???', module)
const mod_context = this._create_mod_context(
mod_install_root_context,
{
name: module.constructor.name,
['module']: module,
external: false,
},
);
await module.install(mod_context);
}
// Real kernel initializes services here, but in this test kernel
// we don't initialize any services.
// Real kernel adds legacy services here but these will break
// the test kernel.
services.ready.resolve();
// provide services to helpers
// const { tmp_provide_services } = require('../src/helpers');
// tmp_provide_services(services);
}
}
TestKernel.prototype._create_mod_context =
Kernel.prototype._create_mod_context;
const k = new TestKernel();
for ( const mod of EssentialModules ) {
k.add_module(new mod());
}
k.add_module({
install: async (context) => {
const services = context.get('services');
services.registerService('thumbs-http', HTTPThumbnailService);
}
});
k.boot();
const do_after_tests_ = [];
/**
* Executes a function immediately and adds it to the list of functions to be executed after tests
*
* This is used to log things inline with console output from tests, and then
* again later without those console outputs.
*
* @param {Function} fn - The function to execute and store for later
*/
const repeat_after = (fn) => {
fn();
do_after_tests_.push(fn);
};
let total_passed = 0;
let total_failed = 0;
/**
* Tracks test results across all services
* @type {number} total_passed - Count of all passed assertions
* @type {number} total_failed - Count of all failed assertions
*/
const main = async () => {
console.log('awaiting services readty');
await k.services.ready;
console.log('services have become ready');
const service_names = process.argv.length > 2
? process.argv.slice(2)
: Object.keys(k.services.instances_);
for ( const name of service_names) {
if ( ! k.services.instances_[name] ) {
console.log(`\x1B[31;1mService not found: ${name}\x1B[0m`);
process.exit(1);
}
const ins = k.services.instances_[name];
ins.construct();
if ( ! ins._test || typeof ins._test !== 'function' ) {
continue;
}
ins.log = k.testLogger;
let passed = 0;
let failed = 0;
repeat_after(() => {
console.log(`\x1B[33;1m=== [ Service :: ${name} ] ===\x1B[0m`);
});
const testapi = {
assert: (condition, name) => {
name = name || condition.toString();
if ( condition() ) {
passed++;
repeat_after(() => console.log(`\x1B[32;1m ✔ ${name}\x1B[0m`));
} else {
failed++;
repeat_after(() => console.log(`\x1B[31;1m ✘ ${name}\x1B[0m`));
}
}
};
testapi.assert.equal = (a, b, name) => {
name = name || `${a} === ${b}`;
if ( a === b ) {
passed++;
repeat_after(() => console.log(`\x1B[32;1m ✔ ${name}\x1B[0m`));
} else {
failed++;
repeat_after(() => {
console.log(`\x1B[31;1m ✘ ${name}\x1B[0m`);
console.log(`\x1B[31;1m Expected: ${b}\x1B[0m`);
console.log(`\x1B[31;1m Got: ${a}\x1B[0m`);
});
}
};
await ins._test(testapi);
total_passed += passed;
total_failed += failed;
}
console.log(`\x1B[36;1m<===\x1B[0m ` +
'ASSERTION OUTPUTS ARE REPEATED BELOW' +
` \x1B[36;1m===>\x1B[0m`);
for ( const fn of do_after_tests_ ) {
fn();
}
console.log(`\x1B[36;1m=== [ Summary ] ===\x1B[0m`);
console.log(`Passed: ${total_passed}`);
console.log(`Failed: ${total_failed}`);
process.exit(total_failed ? 1 : 0);
}
main();