diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 06c78ad..594ea45 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,7 +45,7 @@ jobs: browser-test-aarch64: runs-on: ubuntu-22.04-arm - name: Browser Integration Test + name: Browser Integration Test (hello_world) steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -59,5 +59,33 @@ jobs: "cd tests/browser && \ npm install && \ npx playwright install --with-deps chromium && \ - bash build.sh && \ - npx playwright test" + bash build.sh hello && \ + npx playwright test --project=hello" + + browser-test-bash-aarch64: + runs-on: ubuntu-22.04-arm + name: Browser Integration Test (bash) + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: build container image + run: docker build . --build-arg ECV_AARCH64=1 -t elfconv-image + + - name: run browser test + run: | + docker run --rm -w /root/elfconv elfconv-image \ + "apt-get update && apt-get install -qqy --no-install-recommends bash-static && \ + BUSYBOX_VERSION=1.36.1 && \ + wget -q https://busybox.net/downloads/busybox-\${BUSYBOX_VERSION}.tar.bz2 && \ + tar xjf busybox-\${BUSYBOX_VERSION}.tar.bz2 && \ + cp examples/examples-repos/.config busybox-\${BUSYBOX_VERSION}/.config && \ + make -C busybox-\${BUSYBOX_VERSION} -j\$(nproc) && \ + mkdir -p examples/examples-repos/busybox && \ + cp busybox-\${BUSYBOX_VERSION}/busybox examples/examples-repos/busybox/busybox && \ + rm -rf busybox-\${BUSYBOX_VERSION} busybox-\${BUSYBOX_VERSION}.tar.bz2 && \ + cd tests/browser && \ + npm install && \ + npx playwright install --with-deps chromium && \ + bash build.sh bash && \ + npx playwright test --project=bash" diff --git a/.gitignore b/.gitignore index 5aaa5cf..37233c0 100644 --- a/.gitignore +++ b/.gitignore @@ -135,6 +135,7 @@ elfconv-v* !tests/browser/*.html !tests/browser/package.json tests/browser/wasm-out/ +tests/browser/wasm-out-bash/ tests/browser/test-results/ # AI diff --git a/tests/browser/bash.spec.js b/tests/browser/bash.spec.js new file mode 100644 index 0000000..60341af --- /dev/null +++ b/tests/browser/bash.spec.js @@ -0,0 +1,84 @@ +const { test, expect } = require('@playwright/test'); + +// This function is serialized and executed inside the browser context via waitForFunction. +function readTerminalText() { + const xterm = window.__test_xterm; + if (!xterm) return ''; + const buf = xterm.buffer.active; + let text = ''; + for (let i = 0; i <= buf.cursorY + buf.baseY; i++) { + const line = buf.getLine(i); + if (line) { + text += line.translateToString(true) + '\n'; + } + } + return text; +} + +async function waitForTerminalContent(page, content, timeout = 90000) { + return page.waitForFunction( + ({ fn, expected }) => { + const text = new Function('return (' + fn + ')()')(); + if (text.includes(expected)) return text; + return false; + }, + { fn: readTerminalText.toString(), expected: content }, + { timeout } + ); +} + +async function typeCommand(page, command) { + await page.locator('.xterm-helper-textarea').focus(); + await page.keyboard.type(command); + await page.keyboard.press('Enter'); +} + +test.describe('Bash + Busybox browser tests', () => { + + test.beforeEach(async ({ page }) => { + page.on('console', msg => { + if (msg.type() === 'error') { + console.log(`[browser error] ${msg.text()}`); + } + }); + page.on('pageerror', err => console.log(`PAGE ERROR: ${err.message}`)); + }); + + test('bash starts and shows prompt', async ({ page }) => { + await page.goto('/'); + + const output = await waitForTerminalContent(page, 'bash-static.wasm'); + const text = await output.jsonValue(); + expect(text).toContain('bash-static.wasm'); + }); + + test('uname -a returns expected output', async ({ page }) => { + await page.goto('/'); + + await waitForTerminalContent(page, 'bash-static.wasm'); + await typeCommand(page, 'uname -a'); + + const output = await waitForTerminalContent(page, 'wasm32 GNU/Linux'); + const text = await output.jsonValue(); + + expect(text).toContain('Linux'); + expect(text).toContain('wasm-host-01'); + expect(text).toContain('wasm32 GNU/Linux'); + }); + + test('ls shows filesystem directories', async ({ page }) => { + await page.goto('/'); + + await waitForTerminalContent(page, 'bash-static.wasm'); + await typeCommand(page, 'ls'); + + const output = await waitForTerminalContent(page, 'dev'); + const text = await output.jsonValue(); + + expect(text).toContain('dev'); + expect(text).toContain('home'); + expect(text).toContain('proc'); + expect(text).toContain('tmp'); + expect(text).toContain('usr'); + }); +}); diff --git a/tests/browser/build.sh b/tests/browser/build.sh index 7a5d4d9..1eb5580 100755 --- a/tests/browser/build.sh +++ b/tests/browser/build.sh @@ -3,12 +3,32 @@ set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" -OUT_DIR="${SCRIPT_DIR}/wasm-out" -mkdir -p "${OUT_DIR}" +PROJECT="${1:-all}" -cd "${ROOT_DIR}/build" -TARGET=aarch64-wasm INITWASM=1 ECV_OUT_DIR="${OUT_DIR}" \ - "${ROOT_DIR}/scripts/dev.sh" "${ROOT_DIR}/examples/hello/c/hello_stripped" +build_hello() { + local OUT_DIR="${SCRIPT_DIR}/wasm-out" + mkdir -p "${OUT_DIR}" + cd "${ROOT_DIR}/build" + TARGET=aarch64-wasm INITWASM=1 ECV_OUT_DIR="${OUT_DIR}" \ + "${ROOT_DIR}/scripts/dev.sh" "${ROOT_DIR}/examples/hello/c/hello_stripped" + echo "Browser Wasm artifacts built in ${OUT_DIR}" +} -echo "Browser Wasm artifacts built in ${OUT_DIR}" +build_bash() { + local BASH_OUT_DIR="${SCRIPT_DIR}/wasm-out-bash" + mkdir -p "${BASH_OUT_DIR}" + cd "${ROOT_DIR}/build" + TARGET=aarch64-wasm INITWASM=1 ECV_OUT_DIR="${BASH_OUT_DIR}" \ + "${ROOT_DIR}/scripts/dev.sh" /usr/bin/bash-static + TARGET=aarch64-wasm ECV_OUT_DIR="${BASH_OUT_DIR}" \ + "${ROOT_DIR}/scripts/dev.sh" "${ROOT_DIR}/examples/examples-repos/busybox/busybox" + echo "Browser Wasm artifacts (bash+busybox) built in ${BASH_OUT_DIR}" +} + +case "${PROJECT}" in + hello) build_hello ;; + bash) build_bash ;; + all) build_hello; build_bash ;; + *) echo "Usage: $0 {hello|bash|all}"; exit 1 ;; +esac diff --git a/tests/browser/playwright.config.js b/tests/browser/playwright.config.js index 74cfeb0..9eeb6a3 100644 --- a/tests/browser/playwright.config.js +++ b/tests/browser/playwright.config.js @@ -3,19 +3,48 @@ const { defineConfig } = require('@playwright/test'); module.exports = defineConfig({ testDir: '.', testMatch: '*.spec.js', - timeout: 60000, + timeout: 120000, use: { baseURL: 'http://localhost:3000', }, - webServer: { - command: 'node test-server.js', - port: 3000, - reuseExistingServer: false, - }, + webServer: [ + { + command: 'node test-server.js', + port: 3000, + reuseExistingServer: false, + env: { + TEST_PORT: '3000', + SERVE_DIR: 'wasm-out', + TEST_HTML: 'test-main.html', + }, + }, + { + command: 'node test-server.js', + port: 3001, + reuseExistingServer: false, + env: { + TEST_PORT: '3001', + SERVE_DIR: 'wasm-out-bash', + TEST_HTML: 'test-bash.html', + }, + }, + ], projects: [ { - name: 'chromium', - use: { browserName: 'chromium' }, + name: 'hello', + testMatch: 'hello.spec.js', + use: { + browserName: 'chromium', + baseURL: 'http://localhost:3000', + }, + }, + { + name: 'bash', + testMatch: 'bash.spec.js', + use: { + browserName: 'chromium', + baseURL: 'http://localhost:3001', + }, }, ], }); diff --git a/tests/browser/test-bash.html b/tests/browser/test-bash.html new file mode 100644 index 0000000..692c0b9 --- /dev/null +++ b/tests/browser/test-bash.html @@ -0,0 +1,52 @@ + + + + + + + + +
+ + + + + diff --git a/tests/browser/test-server.js b/tests/browser/test-server.js index 20beb4b..f1f5382 100644 --- a/tests/browser/test-server.js +++ b/tests/browser/test-server.js @@ -3,7 +3,7 @@ const fs = require('fs'); const path = require('path'); const PORT = process.env.TEST_PORT || 3000; -const SERVE_DIR = process.env.SERVE_DIR || path.resolve(__dirname, 'wasm-out'); +const SERVE_DIR = path.resolve(__dirname, process.env.SERVE_DIR || 'wasm-out'); const MIME_TYPES = { '.html': 'text/html', @@ -14,7 +14,7 @@ const MIME_TYPES = { '.json': 'application/json', }; -const TEST_HTML = path.resolve(__dirname, 'test-main.html'); +const TEST_HTML = path.resolve(__dirname, process.env.TEST_HTML || 'test-main.html'); const server = http.createServer((req, res) => { const filePath = req.url === '/' ? TEST_HTML : path.join(SERVE_DIR, req.url);