diff --git a/PROD_NOTES.md b/PROD_NOTES.md index fec46c8..1ddbd75 100644 --- a/PROD_NOTES.md +++ b/PROD_NOTES.md @@ -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)` diff --git a/_webi/test-installer-resolve.js b/_webi/test-installer-resolve.js new file mode 100644 index 0000000..528e426 --- /dev/null +++ b/_webi/test-installer-resolve.js @@ -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); +});