Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/dsh-plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ jobs:
node -e 'require("node:fs").appendFileSync(process.env.GITHUB_ENV, "DSH_ARTIFACT=output/dsh-loopx-plugin-" + require("./package.json").version + ".tgz\n")'
- name: Qualify the real package-name install and uninstall
run: node smoke/dsh-registry-smoke.mjs --tarball "$DSH_ARTIFACT"
- name: Qualify the source installer and installed-version rejection
run: |
pnpm smoke:install
node smoke/dsh-installer-selected-host-smoke.mjs
- name: Qualify packed Client and real web runtime
if: runner.os == 'Linux'
run: |
Expand Down
7 changes: 1 addition & 6 deletions packages/dsh-loopx-plugin/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,7 @@ printf '%s' "$profile_dump" | node -e '
exit 1
}

"$dsh_bin" plugin --profile "$PROFILE_NAME" exec node -e '
const manifest = require("dsh-loopx-plugin/package.json")
if (manifest.name !== "dsh-loopx-plugin" || manifest.version !== process.argv[1]) {
throw new Error(`installed plugin version does not match ${process.argv[1]}`)
}
' "$PACKAGE_VERSION" || {
"$dsh_bin" plugin --profile "$PROFILE_NAME" exec node -e 'const manifest = require("dsh-loopx-plugin/package.json"); if (manifest.name !== "dsh-loopx-plugin" || manifest.version !== process.argv[1]) { throw new Error(`installed plugin version does not match ${process.argv[1]}`); }' "$PACKAGE_VERSION" || {
echo 'install: DSH installed package did not match the built version' >&2
exit 1
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env node

import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import { chmod, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'

const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const temp = await mkdtemp(join(tmpdir(), 'dsh-selected-host-'))
const selectedHost = join(temp, 'selected host')
const calls = join(temp, 'calls.log')
const slash = path => path.replaceAll('\\', '/')

try {
// Delegate every real operation except removal. The complete installer smoke
// must observe this selected host's refusal rather than switch to a local CLI.
await writeFile(selectedHost, `#!/usr/bin/env bash
printf '%s\\n' "$*" >> "$DSH_SELECTED_HOST_CALLS"
if [[ "$1" == plugin && "$4" == remove ]]; then
echo selected-host-removal-refused >&2
exit 37
fi
exec "$DSH_SELECTED_HOST_DELEGATE" "$@"
`)
await chmod(selectedHost, 0o755)
const result = spawnSync(process.execPath, [join(packageRoot, 'smoke/dsh-installer-smoke.mjs')], {
cwd: temp, encoding: 'utf8', shell: false,
timeout: 900_000, maxBuffer: 8 * 1024 * 1024,
env: {
...process.env,
DSH_BIN: slash(selectedHost),
DSH_SELECTED_HOST_CALLS: slash(calls),
DSH_SELECTED_HOST_DELEGATE: slash(process.env.DSH_BIN
|| join(packageRoot, 'node_modules/.bin/dsh')),
},
})
assert.ifError(result.error)
const observed = await readFile(calls, 'utf8')
assert(observed.includes('plugin --profile web remove dsh-loopx-plugin'),
`the selected host did not receive removal (status ${result.status})\n${result.stdout}\n${result.stderr}`)
assert.notEqual(result.status, 0, 'the installer smoke falsely accepted selected-host failure')
assert(result.stderr.includes('selected-host-removal-refused'), result.stderr)
assert(!result.stdout.includes('dsh-loopx installer smoke passed'))
process.stdout.write('selected-host removal refusal correctly fails the complete installer smoke\n')
} finally {
await rm(temp, { recursive: true, force: true })
}
47 changes: 40 additions & 7 deletions packages/dsh-loopx-plugin/smoke/dsh-installer-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@

import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import { chmod, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'

const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const manifest = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8'))
const dshBin = process.env.DSH_BIN || join(packageRoot, 'node_modules', '.bin', 'dsh')
const dshBin = (process.env.DSH_BIN || join(packageRoot, 'node_modules', '.bin', 'dsh'))
.replaceAll('\\', '/')
const installerSource = await readFile(join(packageRoot, 'install.sh'), 'utf8')
const versionVerifier = installerSource.match(/exec node -e '([^']*)' "\$PACKAGE_VERSION"/u)?.[1]
assert(versionVerifier, 'installer must verify the installed package version')
const args = process.argv.slice(2)
assert(args.length === 0 || (args.length === 2 && args[0] === '--previous-tarball'),
'usage: dsh-installer-smoke.mjs [--previous-tarball PATH]')
Expand All @@ -22,7 +27,7 @@ const installedManifest = join(home, 'profiles', 'web', 'node_modules', manifest
function run(file, argv, environment = env) {
return spawnSync(file, argv, {
cwd: temp, env: environment, encoding: 'utf8', shell: false,
timeout: 180_000, maxBuffer: 8 * 1024 * 1024,
timeout: 600_000, maxBuffer: 8 * 1024 * 1024,
})
}

Expand All @@ -31,9 +36,15 @@ function expectSuccess(result) {
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`)
}

function runDsh(argv) {
// Use the same selected executable as install.sh. Bash handles Windows .bin
// shims while "$@" preserves argument boundaries, including paths with spaces.
return run('bash', ['-c', 'exec "$@"', 'dsh-installer-smoke', dshBin, ...argv])
}

try {
if (previousTarball) {
expectSuccess(run(dshBin, ['plugin', '--profile', 'web', 'add', previousTarball,
expectSuccess(runDsh(['plugin', '--profile', 'web', 'add', previousTarball,
'--prefer-offline', '--ignore-scripts']))
const previous = JSON.parse(await readFile(installedManifest, 'utf8'))
assert.equal(previous.name, manifest.name)
Expand All @@ -45,31 +56,53 @@ try {
const installed = run('bash', [join(packageRoot, 'install.sh')])
expectSuccess(installed)
assert(installed.stdout.includes(`installed and verified ${manifest.name}@${manifest.version}`))
process.stdout.write(`${installed.stdout.split(/\r?\n/u)
.find(line => line.includes(`installed and verified ${manifest.name}@${manifest.version}`))}\n`)
const installedText = await readFile(installedManifest, 'utf8')
const readback = JSON.parse(installedText)
assert.equal(readback.name, manifest.name)
assert.equal(readback.version, manifest.version)

const profileBefore = runDsh(['--profile', 'web', '--dump-config'])
expectSuccess(profileBefore)
assert.deepEqual([...profileBefore.stdout.matchAll(/^\s*name:\s+(dsh-loopx-plugin(?:\/\S+)?)\s*$/gmu)]
.map(match => match[1]), [manifest.name, `${manifest.name}/init-command`,
`${manifest.name}/driver`, `${manifest.name}/observer`])
expectSuccess(runDsh(['plugin', '--profile', 'web', 'exec', 'node', '-e',
versionVerifier, manifest.version]))
const staleVersion = '0.0.0-installer-smoke'
const stale = runDsh(['plugin', '--profile', 'web', 'exec', 'node', '-e',
versionVerifier, staleVersion])
assert.ifError(stale.error)
assert.notEqual(stale.status, 0, 'installed-version verifier accepted a stale version')
assert(`${stale.stdout}\n${stale.stderr}`.includes(`installed plugin version does not match ${staleVersion}`))
assert.equal(await readFile(installedManifest, 'utf8'), installedText)
const profileAfter = runDsh(['--profile', 'web', '--dump-config'])
expectSuccess(profileAfter)
assert.equal(profileAfter.stdout, profileBefore.stdout, 'version verification changed the profile')
const profileHash = createHash('sha256').update(profileAfter.stdout).digest('hex')
process.stdout.write(`installed profile unchanged after version checks (sha256 ${profileHash})\n`)

// An incompatible version must fail before any DSH profile operation. The
// shim represents only host version reporting, never a successful runtime.
const unsupported = join(temp, 'unsupported-dsh')
const marker = join(temp, 'unexpected-profile-operation')
await writeFile(unsupported, `#!/bin/sh\nif [ "$1" = '--version' ]; then\n printf '%s\\n' '0.2.1-alpha.1'\nelse\n touch "$LOOPX_INSTALLER_SMOKE_MARKER"\n exit 1\nfi\n`)
await chmod(unsupported, 0o755)
const rejected = run('bash', [join(packageRoot, 'install.sh')], {
...env, DSH_BIN: unsupported, LOOPX_INSTALLER_SMOKE_MARKER: marker,
...env, DSH_BIN: unsupported.replaceAll('\\', '/'), LOOPX_INSTALLER_SMOKE_MARKER: marker,
})
assert.ifError(rejected.error)
assert.equal(rejected.status, 2)
assert(rejected.stderr.includes('outside the supported peer range'))
assert.equal(await stat(marker).then(() => true, () => false), false)
assert.equal(await readFile(installedManifest, 'utf8'), installedText)

expectSuccess(run(dshBin, ['plugin', '--profile', 'web', 'remove', manifest.name]))
const removed = run(dshBin, ['--profile', 'web', '--dump-config'])
expectSuccess(runDsh(['plugin', '--profile', 'web', 'remove', manifest.name]))
const removed = runDsh(['--profile', 'web', '--dump-config'])
expectSuccess(removed)
assert(!removed.stdout.includes('name: dsh-loopx-plugin'))
process.stdout.write(`dsh-loopx installer smoke passed (${previousTarball ? 'upgrade' : 'fresh install'}, incompatible-host rejection, removal)\n`)
process.stdout.write(`dsh-loopx installer smoke passed (${previousTarball ? 'upgrade' : 'fresh install'}, stale-version rejection, incompatible-host rejection, removal)\n`)
} finally {
await rm(temp, { recursive: true, force: true })
}
Loading