diff --git a/src/test/exec.tests.js b/src/test/exec.tests.js new file mode 100644 index 000000000..0ce045971 --- /dev/null +++ b/src/test/exec.tests.js @@ -0,0 +1,176 @@ +import { TestRunner } from "./tester"; + +export async function runExecutorTests(writeOutput) { + const runner = new TestRunner("Executor API Tests"); + + runner.test("Executor available", async (test) => { + test.assert(typeof Executor !== "undefined", "Executor should be available globally"); + }); + + runner.test("Background Executor available", async (test) => { + test.assert(typeof Executor.BackgroundExecutor !== "undefined", "Background Executor should be available globally"); + }); + + + runner.test("execute()", async (test) => { + + test.assert( + await Executor.execute("echo test123").includes("test123"), + "Command output should match" + ); + + }); + + + + runner.test("execute() (BackgroundExecutor)", async (test) => { + + test.assert( + await Executor.BackgroundExecutor.execute("echo test123").includes("test123"), + "Command output should match" + ); + + }); + + runner.test("start()", async (test) => { + let stdout = ""; + + const uuid = await Executor.start("sh", (type, data) => { + if (type === "stdout") stdout += data; + }); + + await Executor.write(uuid, "echo hello\n"); + await new Promise(r => setTimeout(r, 200)); + await Executor.stop(uuid); + + await new Promise(r => setTimeout(r, 200)); + + test.assert(stdout.includes("hello"), "Shell should echo output"); + }); + + + runner.test("start() (BackgroundExecutor)", async (test) => { + let stdout = ""; + + const uuid = await Executor.BackgroundExecutor.start("sh", (type, data) => { + if (type === "stdout") stdout += data; + }); + + await Executor.BackgroundExecutor.write(uuid, "echo hello\n"); + await new Promise(r => setTimeout(r, 200)); + await Executor.BackgroundExecutor.stop(uuid); + + await new Promise(r => setTimeout(r, 200)); + + test.assert(stdout.includes("hello"), "Shell should echo output"); + }); + + + runner.test("start/stop()", async (test) => { + let stdout = ""; + + const uuid = await Executor.start("sh", (type, data) => { + + }); + + await new Promise(r => setTimeout(r, 200)); + + + + const isRunning = await Executor.isRunning(uuid); + + test.assert(isRunning === true, "Executor must be running"); + + + await new Promise(r => setTimeout(r, 200)); + + + await Executor.stop(uuid); + + await new Promise(r => setTimeout(r, 200)); + + test.assert(await Executor.isRunning(uuid) === false, "Executor must be stopped"); + + }); + + runner.test("start/stop() (BackgroundExecutor)", async (test) => { + let stdout = ""; + + const uuid = await Executor.BackgroundExecutor.start("sh", (type, data) => { + + }); + + await new Promise(r => setTimeout(r, 200)); + + + + const isRunning = await Executor.BackgroundExecutor.isRunning(uuid); + + test.assert(isRunning === true, "Executor must be running"); + + + await new Promise(r => setTimeout(r, 200)); + + + await Executor.BackgroundExecutor.stop(uuid); + + await new Promise(r => setTimeout(r, 200)); + + + test.assert(isRunning !== await Executor.BackgroundExecutor.isRunning(uuid), "Executor must be stopped"); + test.assert(await Executor.BackgroundExecutor.isRunning(uuid) === false, "Executor must be stopped"); + + }); + + + + + + + runner.test("start/stop()", async (test) => { + let stdout = ""; + + const uuid = await Executor.start("sh", (type, data) => { + + }); + + await new Promise(r => setTimeout(r, 200)); + + + + const isRunning = await Executor.isRunning(uuid); + + test.assert(isRunning === true, "Executor must be running"); + + + await new Promise(r => setTimeout(r, 200)); + + + await Executor.stop(uuid); + + await new Promise(r => setTimeout(r, 200)); + + test.assert(await Executor.isRunning(uuid) === false, "Executor must be stopped"); + + }); + + runner.test("FDROID env variable", async (test) => { + const result = await Executor.execute("echo $FDROID"); + + const isSet = result.trim().length > 0; + + test.assert( + isSet, + "FDROID env variable should be set" + ); + }); + + + + + + + + + return await runner.run(writeOutput); +} \ No newline at end of file diff --git a/src/test/tester.js b/src/test/tester.js index a51206523..44644e2e4 100644 --- a/src/test/tester.js +++ b/src/test/tester.js @@ -1,5 +1,8 @@ -import { runAceEditorTests } from "./editor.tests"; + +import { runExecutorTests } from "./exec.tests"; import { runSanityTests } from "./sanity.tests"; +import { runAceEditorTests } from "./editor.tests"; + export async function runAllTests() { const terminal = acode.require("terminal"); @@ -16,6 +19,7 @@ export async function runAllTests() { // Run unit tests await runSanityTests(write); await runAceEditorTests(write); + await runExecutorTests(write); write("\x1b[36m\x1b[1mTests completed!\x1b[0m\n"); } catch (error) { @@ -80,6 +84,7 @@ class TestRunner { this.passed = 0; this.failed = 0; this.results = []; + this.skipped = 0; } /** @@ -104,6 +109,11 @@ class TestRunner { } } + skip(reason = "Skipped") { + throw new SkipTest(reason); + } + + async _runWithTimeout(fn, ctx, timeoutMs) { return new Promise((resolve, reject) => { let finished = false; @@ -161,32 +171,52 @@ class TestRunner { try { await delay(200); - await this._runWithTimeout(test.fn, this, 3000); stopSpinner(); this.passed++; - this.results.push({ name: test.name, status: "PASS", error: null }); + this.results.push({ name: test.name, status: "PASS" }); line(` ${COLORS.GREEN}✓${COLORS.RESET} ${test.name}`, COLORS.GREEN); + } catch (error) { stopSpinner(); - this.failed++; - this.results.push({ - name: test.name, - status: "FAIL", - error: error.message, - }); - line( - ` ${COLORS.RED}✗${COLORS.RESET} ${test.name}`, - COLORS.RED + COLORS.BRIGHT, - ); - line( - ` ${COLORS.DIM}└─ ${error.message}${COLORS.RESET}`, - COLORS.RED + COLORS.DIM, - ); + if (error instanceof SkipTest) { + this.skipped++; + this.results.push({ + name: test.name, + status: "SKIP", + reason: error.message, + }); + + line( + ` ${COLORS.YELLOW}?${COLORS.RESET} ${test.name}`, + COLORS.YELLOW + COLORS.BRIGHT, + ); + line( + ` ${COLORS.DIM}└─ ${error.message}${COLORS.RESET}`, + COLORS.YELLOW + COLORS.DIM, + ); + } else { + this.failed++; + this.results.push({ + name: test.name, + status: "FAIL", + error: error.message, + }); + + line( + ` ${COLORS.RED}✗${COLORS.RESET} ${test.name}`, + COLORS.RED + COLORS.BRIGHT, + ); + line( + ` ${COLORS.DIM}└─ ${error.message}${COLORS.RESET}`, + COLORS.RED + COLORS.DIM, + ); + } } + } // Summary @@ -194,15 +224,20 @@ class TestRunner { line("─────────────────────────────────────────────", COLORS.GRAY); const total = this.tests.length; - const percentage = total ? ((this.passed / total) * 100).toFixed(1) : "0.0"; + const effectiveTotal = total - this.skipped; + + const percentage = effectiveTotal + ? ((this.passed / effectiveTotal) * 100).toFixed(1) + : "0.0"; + const statusColor = this.failed === 0 ? COLORS.GREEN : COLORS.YELLOW; line( ` Tests: ${COLORS.BRIGHT}${total}${COLORS.RESET} | ` + - `${statusColor}Passed: ${this.passed}${COLORS.RESET} | ` + - `${COLORS.RED}Failed: ${this.failed}${COLORS.RESET}`, - statusColor, + `${COLORS.GREEN}Passed: ${this.passed}${COLORS.RESET} | ` + + `${COLORS.YELLOW}Skipped: ${this.skipped}${COLORS.RESET} | ` + + `${COLORS.RED}Failed: ${this.failed}${COLORS.RESET}`, ); line( @@ -226,4 +261,13 @@ class TestRunner { } } + +class SkipTest extends Error { + constructor(message = "Skipped") { + super(message); + this.name = "SkipTest"; + } +} + + export { TestRunner };