mirror of
https://github.com/uutils/uutils.github.io.git
synced 2026-06-10 16:12:28 -07:00
Add playground tests and CI integration
- Browser-based unit tests (111 tests) covering parsing, builtins, pipes, UTF-8, l10n, virtual filesystem, and WASM integration - Headless Puppeteer test runner (scripts/run-tests.js) - CI workflow step to build WASM and run tests
This commit is contained in:
@@ -45,6 +45,8 @@ jobs:
|
||||
|
||||
- name: Install `rust` toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-wasip1
|
||||
|
||||
- name: Install system deps
|
||||
run: |
|
||||
@@ -55,7 +57,7 @@ jobs:
|
||||
- name: Install necessary tools (mdbook and mdbook-toc)
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: mdbook,mdbook-toc
|
||||
tool: mdbook@0.5.0,mdbook-toc@0.15.3
|
||||
|
||||
- name: Copy l10n locales into coreutils
|
||||
run: |
|
||||
@@ -112,6 +114,22 @@ jobs:
|
||||
sed -i '/^multilingual/d' book.toml
|
||||
mdbook build
|
||||
|
||||
- name: Build uutils WASM binary
|
||||
run: |
|
||||
cd coreutils
|
||||
# Build the multicall binary for WASI target
|
||||
# Use --no-default-features to avoid platform-specific dependencies
|
||||
cargo build --release --target wasm32-wasip1 -p coreutils --no-default-features --features feat_wasm
|
||||
if [ -f target/wasm32-wasip1/release/coreutils.wasm ]; then
|
||||
mkdir -p ../uutils.github.io/static/wasm
|
||||
cp target/wasm32-wasip1/release/coreutils.wasm ../uutils.github.io/static/wasm/uutils.wasm
|
||||
# Optimize WASM size if wasm-opt is available
|
||||
if command -v wasm-opt &> /dev/null; then
|
||||
wasm-opt -Oz ../uutils.github.io/static/wasm/uutils.wasm -o ../uutils.github.io/static/wasm/uutils.wasm
|
||||
fi
|
||||
echo "WASM binary size: $(du -h ../uutils.github.io/static/wasm/uutils.wasm | cut -f1)"
|
||||
fi
|
||||
|
||||
- name: Run Zola
|
||||
uses: shalzz/zola-deploy-action@v0.21.0
|
||||
env:
|
||||
@@ -130,6 +148,14 @@ jobs:
|
||||
cp -r "$lang_dir" "public/coreutils/docs-${lang}"
|
||||
done
|
||||
|
||||
- name: Run playground JS tests
|
||||
run: |
|
||||
npm install puppeteer@24
|
||||
node uutils.github.io/scripts/run-tests.js --dir public --port 8080
|
||||
|
||||
- name: Remove test files from deploy output
|
||||
run: rm -f public/js/wasm-terminal.test.html
|
||||
|
||||
- name: Upload artifact for checking the output
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Run the wasm-terminal unit tests headlessly using Puppeteer.
|
||||
*
|
||||
* Usage:
|
||||
* npm install puppeteer # one-time setup
|
||||
* node scripts/run-tests.js [--port 8080] [--dir public]
|
||||
*
|
||||
* The script starts a local HTTP server, opens the test page in headless
|
||||
* Chrome, waits for the results, and exits with code 1 on failure.
|
||||
*/
|
||||
|
||||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Parse CLI args
|
||||
const args = process.argv.slice(2);
|
||||
let port = 8080;
|
||||
let dir = "public";
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === "--port" && args[i + 1]) port = parseInt(args[++i]);
|
||||
if (args[i] === "--dir" && args[i + 1]) dir = args[++i];
|
||||
}
|
||||
|
||||
// Resolve serve directory — fall back to static/ if public/ doesn't exist
|
||||
// (public/ is the zola build output; static/ works for running without a build)
|
||||
const serveDir = fs.existsSync(path.resolve(dir))
|
||||
? path.resolve(dir)
|
||||
: path.resolve("static");
|
||||
|
||||
const MIME_TYPES = {
|
||||
".html": "text/html",
|
||||
".js": "application/javascript",
|
||||
".css": "text/css",
|
||||
".wasm": "application/wasm",
|
||||
".json": "application/json",
|
||||
".svg": "image/svg+xml",
|
||||
".png": "image/png",
|
||||
};
|
||||
|
||||
function startServer() {
|
||||
return new Promise((resolve) => {
|
||||
const server = http.createServer((req, res) => {
|
||||
const url = req.url.split("?")[0];
|
||||
let filePath = path.join(serveDir, url === "/" ? "index.html" : url);
|
||||
|
||||
// Prevent path traversal outside the serve directory
|
||||
if (!path.resolve(filePath).startsWith(serveDir + path.sep) && path.resolve(filePath) !== serveDir) {
|
||||
res.writeHead(403);
|
||||
res.end("Forbidden");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
res.writeHead(404);
|
||||
res.end("Not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (fs.statSync(filePath).isDirectory()) {
|
||||
filePath = path.join(filePath, "index.html");
|
||||
}
|
||||
|
||||
const ext = path.extname(filePath);
|
||||
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
||||
|
||||
res.writeHead(200, { "Content-Type": mime });
|
||||
fs.createReadStream(filePath).pipe(res);
|
||||
});
|
||||
|
||||
server.listen(port, "127.0.0.1", () => {
|
||||
console.log(`Serving ${serveDir} on http://127.0.0.1:${port}`);
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function runTests(server) {
|
||||
let puppeteer;
|
||||
try {
|
||||
puppeteer = require("puppeteer");
|
||||
} catch {
|
||||
console.error("Puppeteer not found. Install it with: npm install puppeteer");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
page.on("console", (msg) => console.log("BROWSER:", msg.text()));
|
||||
page.on("pageerror", (err) => console.error("PAGE ERROR:", err.message));
|
||||
|
||||
const testUrl = `http://127.0.0.1:${port}/js/wasm-terminal.test.html`;
|
||||
console.log(`Opening ${testUrl}`);
|
||||
|
||||
await page.goto(testUrl, { waitUntil: "networkidle0", timeout: 30000 });
|
||||
await page.waitForFunction("window.__testsFailed !== undefined", {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
const failed = await page.evaluate(() => window.__testsFailed);
|
||||
const passed = await page.evaluate(() => window.__testsPassed);
|
||||
|
||||
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
||||
|
||||
await browser.close();
|
||||
server.close();
|
||||
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const server = await startServer();
|
||||
await runTests(server);
|
||||
})();
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user