test: add installer resolution test for serve-installer.js path

Exercises the helper() function that `curl webi.sh/bat` actually hits.
9 passing, 6 known issues documented:
- WATERFALL libc->gnu gap (build-classifier submodule)
- ANYOS/ANYARCH .git source URLs matched before platform binaries
This commit is contained in:
AJ ONeal
2026-03-11 02:19:02 -06:00
parent 77a965bcba
commit 94bc9b0c07
2 changed files with 244 additions and 0 deletions

View File

@@ -46,6 +46,18 @@ The Go cache filters releases more aggressively than the old Node normalize.js:
- These are improvements — the filtered results better reflect what webi can
actually install.
### Known Issues Needing Resolution
- **WATERFALL libc vs gnu**: The Go cache correctly uses `libc='gnu'` for
glibc-linked Linux binaries (Rust projects like bat, rg; also node). The
build-classifier WATERFALL maps `libc` => `['none', 'libc']` but never tries
`gnu`, so Linux glibc hosts can't match these packages. Fix needed in
`host-targets.js`: `libc: ['none', 'gnu', 'libc']`. See QUESTIONS.md in the
Go agent worktree.
- **ANYOS/ANYARCH .git priority**: Packages like caddy and jq have `.git`
source URLs classified as `ANYOS`/`ANYARCH`. The triplet enumeration tries
ANYOS before specific OS, so `.git` matches before platform-specific binaries.
### Known Pre-existing Issues
- **`transform-releases.js` self-test**: The `if (require.main === module)`

View File

@@ -0,0 +1,232 @@
'use strict';
let InstallerServer = require('./serve-installer.js');
let Builds = require('./builds.js');
// Real User-Agent strings sent by webi bootstrap scripts.
//
// Known issues (not regressions from cache-only migration):
//
// 1. WATERFALL libc vs gnu: The WATERFALL maps `libc` => ['none', 'libc']
// but packages with explicit gnu in filenames (Rust projects) get classified
// as libc='gnu'. A glibc host sending `libc` in the UA won't match `gnu`
// triplets. Needs a build-classifier submodule update.
//
// 2. ANYOS/ANYARCH priority: The triplet enumeration tries ANYOS/ANYARCH
// before specific OS/arch. Packages whose Go cache includes source repo
// URLs (`.git`) as releases get matched to ANYOS before platform-specific
// binaries. The Go cache or selectPackage needs to handle this.
let UA_CASES = [
// === macOS (no libc issue — darwin uses libc='none') ===
{
label: 'bat macOS arm64',
pkg: 'bat',
ua: 'aarch64/unknown Darwin/24.2.0 libc',
expectOs: 'darwin',
expectArch: 'aarch64',
expectExt: 'tar.gz',
},
{
label: 'bat macOS amd64',
pkg: 'bat',
ua: 'x86_64/unknown Darwin/23.0.0 libc',
expectOs: 'darwin',
expectArch: 'x86_64',
expectExt: 'tar.gz',
},
{
label: 'go macOS arm64',
pkg: 'go',
ua: 'aarch64/unknown Darwin/24.2.0 libc',
expectOs: 'darwin',
expectArch: 'aarch64',
expectExt: 'tar.gz',
},
{
label: 'node macOS arm64',
pkg: 'node',
ua: 'aarch64/unknown Darwin/24.2.0 libc',
expectOs: 'darwin',
expectArch: 'aarch64',
expectExt: 'tar.gz',
},
{
label: 'rg macOS arm64',
pkg: 'rg',
ua: 'aarch64/unknown Darwin/24.2.0 libc',
expectOs: 'darwin',
expectArch: 'aarch64',
expectExt: 'tar.gz',
},
// === Windows ===
{
label: 'bat Windows amd64',
pkg: 'bat',
ua: 'x86_64/unknown Windows/10.0.19041 msvc',
expectOs: 'windows',
expectArch: 'x86_64',
expectExt: 'zip',
},
{
label: 'go Windows amd64',
pkg: 'go',
ua: 'x86_64/unknown Windows/10.0.19041 msvc',
expectOs: 'windows',
expectArch: 'x86_64',
expectExt: 'zip',
},
// === Linux musl (Alpine/Docker) ===
{
label: 'bat Linux musl',
pkg: 'bat',
ua: 'x86_64/unknown Linux/5.15.0 musl',
expectOs: 'linux',
expectArch: 'x86_64',
expectExt: 'tar.gz',
},
// === Linux glibc — packages with libc='none' in cache ===
{
label: 'go Linux amd64',
pkg: 'go',
ua: 'x86_64/unknown Linux/5.15.0 libc',
expectOs: 'linux',
expectArch: 'x86_64',
expectExt: 'tar.gz',
},
// === Known: WATERFALL libc vs gnu ===
// Packages whose Go cache has libc='gnu' for Linux glibc builds.
// The WATERFALL maps `libc` => ['none', 'libc'] but never tries 'gnu'.
{
label: 'bat Linux amd64 (known: WATERFALL libc vs gnu)',
pkg: 'bat',
ua: 'x86_64/unknown Linux/5.15.0 libc',
known: 'WATERFALL libc->gnu gap',
},
{
label: 'rg Linux amd64 (known: WATERFALL libc vs gnu)',
pkg: 'rg',
ua: 'x86_64/unknown Linux/5.15.0 libc',
known: 'WATERFALL libc->gnu gap',
},
{
label: 'node Linux amd64 (known: WATERFALL libc vs gnu)',
pkg: 'node',
ua: 'x86_64/unknown Linux/5.15.0 libc',
known: 'WATERFALL libc->gnu gap',
},
// === Known: ANYOS/ANYARCH .git priority ===
{
label: 'jq macOS arm64 (known: ANYOS .git priority)',
pkg: 'jq',
ua: 'aarch64/unknown Darwin/24.2.0 libc',
known: 'ANYOS .git matched before platform binary',
},
{
label: 'caddy macOS arm64 (known: ANYOS .git priority)',
pkg: 'caddy',
ua: 'aarch64/unknown Darwin/24.2.0 libc',
known: 'ANYOS .git matched before platform binary',
},
{
label: 'caddy Linux amd64 (known: ANYOS .git priority)',
pkg: 'caddy',
ua: 'x86_64/unknown Linux/5.15.0 libc',
known: 'ANYOS .git matched before platform binary',
},
];
async function main() {
let failures = 0;
let passes = 0;
let knowns = 0;
let errors = 0;
console.log('Initializing build cache...');
await Builds.init();
console.log('');
console.log('=== Installer Resolution Tests ===');
console.log('');
for (let tc of UA_CASES) {
try {
let [pkg, params] = await InstallerServer.helper({
unameAgent: tc.ua,
projectName: tc.pkg,
tag: 'stable',
formats: [],
libc: '',
});
// Known issue — just verify it fails as expected
if (tc.known) {
if (pkg.channel === 'error' || !pkg.download || pkg.download.includes('doesntexist') || pkg.ext === 'git') {
console.log(` KNOWN ${tc.label}`);
knowns++;
} else {
console.log(` PASS ${tc.label} (known issue resolved!): v${pkg.version} .${pkg.ext}`);
passes++;
}
continue;
}
if (pkg.channel === 'error') {
console.log(` FAIL ${tc.label}: resolved to error package`);
failures++;
continue;
}
let diffs = [];
if (tc.expectOs && pkg.os !== tc.expectOs) {
diffs.push(`os: got=${pkg.os} want=${tc.expectOs}`);
}
if (tc.expectArch && pkg.arch !== tc.expectArch) {
diffs.push(`arch: got=${pkg.arch} want=${tc.expectArch}`);
}
if (tc.expectExt && pkg.ext !== tc.expectExt) {
diffs.push(`ext: got=${pkg.ext} want=${tc.expectExt}`);
}
if (!pkg.version || pkg.version === '0.0.0') {
diffs.push('version: missing or zero');
}
if (!pkg.download || pkg.download.includes('doesntexist')) {
diffs.push('download: missing or error');
}
if (diffs.length > 0) {
console.log(` FAIL ${tc.label}: ${diffs.join(', ')}`);
failures++;
} else {
console.log(` PASS ${tc.label}: v${pkg.version} .${pkg.ext} ${pkg.download.split('/').pop()}`);
passes++;
}
} catch (err) {
if (tc.known) {
console.log(` KNOWN ${tc.label} (error: ${err.message})`);
knowns++;
continue;
}
console.log(` ERROR ${tc.label}: ${err.message}`);
errors++;
}
}
console.log('');
console.log(`=== Results: ${passes} passed, ${failures} failed, ${knowns} known, ${errors} errors ===`);
if (failures > 0 || errors > 0) {
process.exit(1);
}
}
main().catch(function (err) {
console.error(err.stack);
process.exit(1);
});