From 8868eab5e42cf997d5a2cae2b54ea43121187778 Mon Sep 17 00:00:00 2001 From: rafapaezbas <15270736+rafapaezbas@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:12:02 +0200 Subject: [PATCH] testnet + test fixes (#335) * added dht bootstrap to tests * removed system pear in ci * removed artifact folder in CI * pass bootstrap to pear-updater-bootstraper * clean up * added verbose flag * smoke working * added stream teardown in teardown tests * isolate * pear-updater-bootstap 1.2.0 * only lin * enc test fix * fix * artifact to artefact rename * handle streams in update tests * corrected teardown * corrected teardown * corrected teardown * more fixes * more fixes * more fixes * fix * enable all machines * fix * fix * explicit bump pear-ipc * renamed --bootstrap to --dht-bootstrap * flag parsing in sidecar subsystem instead * default bootstrap fix * refactor default bootstrap fix --------- Co-authored-by: dmc --- .github/workflows/ci.yml | 48 +---------------------- lib/api.js | 18 ++++++--- lib/tryboot.js | 5 +++ package.json | 8 ++-- run/definition.js | 1 + run/index.js | 7 +++- scripts/pretest.js | 20 ---------- scripts/test.js | 13 ------- scripts/test.mjs | 38 ++++++++++++++++++ sidecar.js | 7 +--- state.js | 6 ++- subsystems/sidecar/index.js | 35 +++++++++++++++-- test/01-smoke.test.js | 8 ++-- test/02-shutdown.test.js | 6 ++- test/03-teardown.test.js | 14 ++++--- test/05-encrypted.test.js | 4 +- test/06-updates.test.js | 44 +++++++++++---------- test/fixtures/harness/index.js | 5 ++- test/helper.js | 70 ++++++++++++++++++++++------------ 19 files changed, 200 insertions(+), 157 deletions(-) delete mode 100644 scripts/pretest.js delete mode 100644 scripts/test.js create mode 100644 scripts/test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa7616d48..e4eca576e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,61 +36,17 @@ jobs: with: node-version: lts/* - - name: Determine Local Pear Command per OS - run: echo "PEAR=$( [[ \"$RUNNER_OS\" == \"Windows\" ]] && echo 'pwsh -File ./pear.ps1' || echo './pear.dev' )" >> $GITHUB_ENV - shell: bash - - name: Install w/ dev deps run: npm install - - name: System Pear - run: npm i -g pear && pear - - name: Bootstrap run: npm run bootstrap - - name: Remove node_modules - run: rm -fr node_modules - shell: bash - - - name: Install w/out dev deps - run: npm i --omit=dev - - - name: Self-Stage - run: $PEAR stage ci - shell: bash - - - name: Artifact Pear - run: $PEAR dump $($PEAR info ci --key) $(node -p "fs.realpathSync(os.tmpdir())")/artifact-pear - shell: bash - - - name: Shutdown Local Pear Sidecar - run: $PEAR sidecar shutdown - shell: bash - - - name: Remove node_modules - run: rm -fr node_modules - shell: bash - - - name: Install w/ dev deps - run: npm install - - - name: Install fixture deps - run: | - cd ./test/fixtures/harness - npm install - cd - - cd ./test/fixtures/encrypted - npm install - cd - - shell: bash + - name: Install Bare + run: npm i -g bare-runtime - name: Test run: npm test - - name: Shutdown Local Pear Sidecar - run: $PEAR sidecar shutdown - shell: bash - - name: Lint run: npm run lint diff --git a/lib/api.js b/lib/api.js index 772b5c55b..172a36ff0 100644 --- a/lib/api.js +++ b/lib/api.js @@ -58,12 +58,20 @@ class API { this.#unloading() const MAX_TEARDOWN_WAIT = 5000 - const timeout = new Promise(resolve => setTimeout(() => { + let timeout = null + let timedout = false + const countdown = new Promise((resolve) => { + timeout = setTimeout(() => { + timedout = true + resolve() + }, MAX_TEARDOWN_WAIT) + }) + this.#teardowns.finally(() => { clearTimeout(timeout) }) + await Promise.race([this.#teardowns, countdown]) + if (timedout) { console.error(`Max teardown wait reached after ${MAX_TEARDOWN_WAIT} ms. Exiting...`) - resolve() - }, MAX_TEARDOWN_WAIT)) - - await Promise.race([this.#teardowns, timeout]) + Bare.exit() + } } async #reftrack (promise) { diff --git a/lib/tryboot.js b/lib/tryboot.js index 0004ee9d7..25e38cc87 100644 --- a/lib/tryboot.js +++ b/lib/tryboot.js @@ -6,7 +6,12 @@ const { RUNTIME, PLATFORM_DIR } = require('../constants.js') module.exports = function tryboot () { const verbose = (global.Bare || global.process).argv.includes('--verbose') const args = ['--sidecar'] + const dhtBootstrap = Bare.argv.includes('--dht-bootstrap') ? Bare.argv[Bare.argv.indexOf('--dht-bootstrap') + 1] : null if (verbose) args.push('--verbose') + if (dhtBootstrap) { + args.push('--dht-bootstrap') + args.push(dhtBootstrap) + } const sc = spawn(RUNTIME, args, { detached: !verbose, stdio: verbose ? 'inherit' : 'ignore', diff --git a/package.json b/package.json index 273f4143c..e0f64c9bf 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,7 @@ "private": true, "scripts": { "test:gen": "brittle -r test/index.js test/*.test.js", - "pretest": "pear run -t scripts/pretest.js", - "test": "pear run -t scripts/test.js", + "test": "bare scripts/test.mjs", "lint": "standard", "lint:fix": "standard --fix", "archdump": "node scripts/bootstrap.js --archdump", @@ -98,10 +97,10 @@ "paparam": "^1.4.0", "pear-changelog": "^1.0.1", "pear-interface": "^1.0.0", - "pear-ipc": "^2.2.1", + "pear-ipc": "^2.2.8", "pear-link": "^2.0.1", "pear-updater": "^3.1.0", - "pear-updater-bootstrap": "^1.1.3", + "pear-updater-bootstrap": "^1.2.0", "protomux": "^3.6.0", "pw-to-ek": "^1.0.0", "quickbit-universal": "^2.2.0", @@ -121,6 +120,7 @@ "z32": "^1.1.0" }, "devDependencies": { + "@hyperswarm/testnet": "^3.1.4", "bare-inspector": "^2.3.0", "brittle": "^3.5.2", "graceful-goodbye": "^1.3.0", diff --git a/run/definition.js b/run/definition.js index cab36a3a6..e0bed7e2b 100644 --- a/run/definition.js +++ b/run/definition.js @@ -19,6 +19,7 @@ module.exports = [ flag('--checkout ', 'Run a checkout from version length'), flag('--detached', 'Wakeup existing app or run detached'), flag('--no-ask', 'Suppress permissions dialog'), + hiddenFlag('--dht-bootstrap ', 'DHT boostrap'), hiddenFlag('--encryption-key ', 'Application encryption key'), hiddenFlag('--trusted'), hiddenFlag('--detach'), diff --git a/run/index.js b/run/index.js index 379efc1be..d1bb5261b 100644 --- a/run/index.js +++ b/run/index.js @@ -125,8 +125,13 @@ module.exports = async function run ({ ipc, args, cmdArgs, link, storage, detach await new Promise((resolve, reject) => fs.close(fd, (err) => err ? reject(err) : resolve(fd))) await global.Pear.restart() - }) + }).on('error', () => {}) + global.Pear.teardown(async () => { + if (reloadSubscriber.destroyed) return + reloadSubscriber.end() + return new Promise((resolve) => reloadSubscriber.on('close', resolve)) + }) const protocol = new Module.Protocol({ exists (url) { return Object.hasOwn(bundle.sources, url.href) diff --git a/scripts/pretest.js b/scripts/pretest.js deleted file mode 100644 index c4c2678d8..000000000 --- a/scripts/pretest.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict' -const fs = require('bare-fs') -const path = require('bare-path') -const { spawnSync } = require('bare-subprocess') -const { isWindows } = require('which-runtime') -const { pathname } = new URL(global.Pear.config.applink) -const root = isWindows ? path.normalize(pathname.slice(1)) : pathname -const force = global.Pear.config.args.includes('--force-install') - -const dirs = [ - path.join(root, 'test', 'fixtures', 'harness', 'node_modules'), - path.join(root, 'test', 'fixtures', 'encrypted', 'node_modules') -] -for (const dir of dirs) { - if (force === false && fs.existsSync(dir)) continue - console.log(force ? 'reinstalling node_modules in' : 'node_modules not found in', path.dirname(dir)) - console.log('Running npm install...') - if (isWindows) spawnSync('pwsh', ['-Command', 'npm install'], { cwd: path.dirname(dir), stdio: 'inherit' }) - else spawnSync('npm', ['install'], { cwd: path.dirname(dir), stdio: 'inherit' }) -} diff --git a/scripts/test.js b/scripts/test.js deleted file mode 100644 index c8698c1c2..000000000 --- a/scripts/test.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict' -const path = require('bare-path') -const { spawn } = require('bare-subprocess') -const { isWindows } = require('which-runtime') -const { RUNTIME } = require('../constants') -const { pathname } = new URL(global.Pear.config.applink) -const cwd = isWindows ? path.normalize(pathname.slice(1)) : pathname - -const tests = spawn(RUNTIME, ['run', '-t', 'test', ...global.Pear.config.args], { cwd, stdio: 'inherit' }) - -tests.on('exit', (code) => { - Bare.exitCode = code -}) diff --git a/scripts/test.mjs b/scripts/test.mjs new file mode 100644 index 000000000..d2b1c6a7a --- /dev/null +++ b/scripts/test.mjs @@ -0,0 +1,38 @@ +'use strict' +import url from 'bare-url' +import path from 'bare-path' +import { spawn, spawnSync } from 'bare-subprocess' +import { RUNTIME } from '../constants' +import createTestnet from '@hyperswarm/testnet' +import fs from 'bare-fs' +import { isWindows } from 'which-runtime' + +const filename = url.fileURLToPath(import.meta.url) +const dirname = path.dirname(filename) +const root = path.dirname(dirname) + +const force = Bare.argv.includes('--force-install') + +const dirs = [ + path.join(root, 'test', 'fixtures', 'harness', 'node_modules'), + path.join(root, 'test', 'fixtures', 'encrypted', 'node_modules') +] +for (const dir of dirs) { + if (force === false && fs.existsSync(dir)) continue + console.log(force ? 'reinstalling node_modules in' : 'node_modules not found in', path.dirname(dir)) + console.log('Running npm install...') + if (isWindows) spawnSync('pwsh', ['-Command', 'npm install'], { cwd: path.dirname(dir), stdio: 'inherit' }) + else spawnSync('npm', ['install'], { cwd: path.dirname(dir), stdio: 'inherit' }) +} + +const testnet = await createTestnet(10) +const dhtBootstrap = testnet.nodes.map(e => `${e.host}:${e.port}`).join(',') + +const verbose = Bare.argv.includes('--verbose') ? '--verbose' : '' + +const tests = spawn(RUNTIME, ['run', '--dht-bootstrap', dhtBootstrap, '-t', 'test', verbose], { cwd: root, stdio: 'inherit' }) + +tests.on('exit', async (code) => { + await testnet.destroy() + Bare.exitCode = code +}) diff --git a/sidecar.js b/sidecar.js index 3ce2b5a8e..f273871f9 100644 --- a/sidecar.js +++ b/sidecar.js @@ -19,11 +19,8 @@ const { } = require('./constants.js') const registerUrlHandler = require('./url-handler.js') const gunk = require('./gunk') -const verbose = Bare.argv.includes('--verbose') crasher('sidecar', SWAP) -module.exports = bootSidecar().then(() => { - if (verbose) console.log('- Sidecar booted') -}).catch((err) => { +module.exports = bootSidecar().catch((err) => { console.error(err.stack) Bare.exit(1) }) @@ -45,7 +42,7 @@ async function bootSidecar () { const Sidecar = await subsystem(drive, '/subsystems/sidecar/index.js') const updater = createUpdater() - const sidecar = new Sidecar({ updater, drive, corestore, gunk, verbose }) + const sidecar = new Sidecar({ updater, drive, corestore, gunk }) await sidecar.ipc.ready() registerUrlHandler(WAKEUP) diff --git a/state.js b/state.js index b62d5deb4..3d18a1ac7 100644 --- a/state.js +++ b/state.js @@ -36,6 +36,7 @@ module.exports = class State { entrypoints = null applink = null dht = null + dhtBootstrap = null static injestPackage (state, pkg, overrides = {}) { state.manifest = pkg state.main = pkg?.main || 'index.html' @@ -80,9 +81,9 @@ module.exports = class State { } static configFrom (state) { - const { id, key, links, alias, env, options, checkpoint, flags, dev, tier, stage, storage, trace, name, main, dependencies, args, channel, release, applink, fragment, link, linkData, entrypoint, dir, dht } = state + const { id, key, links, alias, env, options, checkpoint, flags, dev, tier, stage, storage, trace, name, main, dependencies, args, channel, release, applink, fragment, link, linkData, entrypoint, dir, dht, dhtBootstrap } = state const pearDir = PLATFORM_DIR - return { id, key, links, alias, env, options, checkpoint, flags, dev, tier, stage, storage, trace, name, main, dependencies, args, channel, release, applink, fragment, link, linkData, entrypoint, dir, dht, pearDir } + return { id, key, links, alias, env, options, checkpoint, flags, dev, tier, stage, storage, trace, name, main, dependencies, args, channel, release, applink, fragment, link, linkData, entrypoint, dir, dht, dhtBootstrap, pearDir } } static isKeetInvite (segment) { @@ -121,6 +122,7 @@ module.exports = class State { this.#onupdate = onupdate this.startId = startId || null this.dht = dht + this.dhtBootstrap = flags.dhtBootstrap?.split(',').map(e => ({ host: e.split(':')[0], port: Number(e.split(':')[1]) })) this.store = store this.args = args this.appling = appling diff --git a/subsystems/sidecar/index.js b/subsystems/sidecar/index.js index 63b96ad65..c2da2d50b 100644 --- a/subsystems/sidecar/index.js +++ b/subsystems/sidecar/index.js @@ -17,7 +17,8 @@ const sodium = require('sodium-native') const Updater = require('pear-updater') const IPC = require('pear-ipc') const { isMac, isWindows } = require('which-runtime') -const { command } = require('paparam') +const { command, flag, arg, rest } = require('paparam') +const deriveEncryptionKey = require('pw-to-ek') const reports = require('./lib/reports') const Store = require('./lib/store') const Applings = require('./lib/applings') @@ -29,7 +30,6 @@ const registerUrlHandler = require('../../url-handler') const parseLink = require('../../lib/parse-link') const runDefinition = require('../../run/definition') const { version } = require('../../package.json') -const deriveEncryptionKey = require('pw-to-ek') const { PLATFORM_DIR, PLATFORM_LOCK, SOCKET_PATH, CHECKOUT, APPLINGS_PATH, SWAP, RUNTIME, DESKTOP_RUNTIME, ALIASES, SPINDOWN_TIMEOUT, WAKEUP, SALT @@ -79,8 +79,32 @@ class Sidecar extends ReadyResource { teardown () { global.Bare.exit() } - constructor ({ updater, drive, corestore, gunk, verbose = false }) { + #parsePlatformFlags () { + const argv = Bare.argv.slice(1) + let verbose = false + let dhtBootstrap = '' + const dhtBootstrapFlag = flag('--dht-bootstrap ') + try { + const result = command('pear', flag('--sidecar'), flag('--verbose'), dhtBootstrapFlag, arg(''), rest('rest')).parse(argv).flags + verbose = result.verbose + dhtBootstrap = result.dhtBootstrap || undefined + } catch { + const result = command('pear', flag('--sidecar'), flag('--verbose'), arg(''), rest('rest')).parse(argv).flags + verbose = result.verbose + } + return { + verbose, + dhtBootstrap: typeof dhtBootstrap === 'string' ? dhtBootstrap.split(',').map(e => ({ host: e.split(':')[0], port: Number(e.split(':')[1]) })) : dhtBootstrap + } + } + + constructor ({ updater, drive, corestore, gunk }) { super() + + const { verbose, dhtBootstrap } = this.#parsePlatformFlags() + this.verbose = verbose + this.dhtBootstrap = dhtBootstrap + this.bus = new Iambus() this.version = CHECKOUT @@ -125,6 +149,8 @@ class Sidecar extends ReadyResource { this.http = new Http(this) this.running = new Map() + this.dhtBootstrap = dhtBootstrap + const sidecar = this this.App = class App { sidecar = sidecar @@ -282,6 +308,7 @@ class Sidecar extends ReadyResource { await this.applings.set('runtime', DESKTOP_RUNTIME) await this.http.ready() await this.#ensureSwarm() + if (this.verbose) console.log('- Sidecar booted') } get clients () { return this.ipc.clients } @@ -863,7 +890,7 @@ class Sidecar extends ReadyResource { throw err } this.keyPair = await this.corestore.createKeyPair('holepunch') - this.swarm = new Hyperswarm({ keyPair: this.keyPair }) + this.swarm = new Hyperswarm({ keyPair: this.keyPair, bootstrap: this.dhtBootstrap }) this.swarm.once('close', () => { this.swarm = null }) this.swarm.on('connection', (connection) => { this.corestore.replicate(connection) }) if (this.replicator !== null) this.replicator.join(this.swarm, { server: false, client: true }).catch(safetyCatch) diff --git a/test/01-smoke.test.js b/test/01-smoke.test.js index 4b01a0dd7..b185b8440 100644 --- a/test/01-smoke.test.js +++ b/test/01-smoke.test.js @@ -5,11 +5,11 @@ const hypercoreid = require('hypercore-id-encoding') const Helper = require('./helper') const harness = path.join(Helper.localDir, 'test', 'fixtures', 'harness') -test('smoke', async function ({ ok, is, plan, comment, teardown, timeout }) { +test('smoke', async function ({ ok, is, plan, comment, teardown, timeout, end }) { timeout(180000) plan(5) const stager = new Helper() - teardown(() => stager.close()) + teardown(() => stager.close(), { order: Infinity }) await stager.ready() const dir = harness @@ -17,14 +17,16 @@ test('smoke', async function ({ ok, is, plan, comment, teardown, timeout }) { comment('staging') const staging = stager.stage({ channel: `test-${id}`, name: `test-${id}`, dir, dryRun: false, bare: true }) + teardown(() => Helper.teardownStream(staging)) const final = await Helper.pick(staging, { tag: 'final' }) ok(final.success, 'stage succeeded') comment('seeding') const seeder = new Helper() - teardown(() => seeder.close()) + teardown(() => seeder.close(), { order: Infinity }) await seeder.ready() const seeding = seeder.seed({ channel: `test-${id}`, name: `test-${id}`, dir, key: null, cmdArgs: [] }) + teardown(() => Helper.teardownStream(seeding)) const until = await Helper.pick(seeding, [{ tag: 'key' }, { tag: 'announced' }]) const key = await until.key diff --git a/test/02-shutdown.test.js b/test/02-shutdown.test.js index 2eab6c590..032c8e40c 100644 --- a/test/02-shutdown.test.js +++ b/test/02-shutdown.test.js @@ -4,14 +4,16 @@ const fs = require('bare-fs') const path = require('bare-path') const fsext = require('fs-native-extensions') const Helper = require('./helper') -const rig = new Helper.Rig() +const rig = new Helper.Rig({ keepAlive: false }) test.hook('shutdown setup', rig.setup) test('lock released after shutdown', async function ({ ok, plan, comment, teardown }) { plan(1) comment('shutting down sidecar') - await rig.artifact.shutdown() + const helper = new Helper(rig) + await helper.ready() + await helper.shutdown() comment('sidecar shutdown') comment('checking file lock is free') const lock = path.join(rig.platformDir, 'corestores', 'platform', 'primary-key') diff --git a/test/03-teardown.test.js b/test/03-teardown.test.js index b65989cdd..13213d04f 100644 --- a/test/03-teardown.test.js +++ b/test/03-teardown.test.js @@ -11,7 +11,7 @@ test('teardown', async function ({ is, ok, plan, comment, teardown, timeout }) { plan(5) const stager = new Helper() - teardown(() => stager.close()) + teardown(() => stager.close(), { order: Infinity }) await stager.ready() const dir = harness @@ -25,9 +25,10 @@ test('teardown', async function ({ is, ok, plan, comment, teardown, timeout }) { comment('seeding') const seeder = new Helper() - teardown(() => seeder.close()) + teardown(() => seeder.close(), { order: Infinity }) await seeder.ready() const seed = seeder.seed({ channel: `test-${id}`, name: `test-${id}`, dir }) + teardown(() => Helper.teardownStream(seed)) const until = await Helper.pick(seed, [{ tag: 'key' }, { tag: 'announced' }]) const key = await until.key const announced = await until.announced @@ -55,7 +56,7 @@ test('teardown during teardown', async function ({ is, ok, plan, comment, teardo plan(5) const stager = new Helper() - teardown(() => stager.close()) + teardown(() => stager.close(), { order: Infinity }) await stager.ready() const dir = harness @@ -69,9 +70,10 @@ test('teardown during teardown', async function ({ is, ok, plan, comment, teardo comment('seeding') const seeder = new Helper() - teardown(() => seeder.close()) + teardown(() => seeder.close(), { order: Infinity }) await seeder.ready() const seed = seeder.seed({ channel: `test-${id}`, name: `test-${id}`, dir }) + teardown(() => Helper.teardownStream(seed)) const until = await Helper.pick(seed, [{ tag: 'key' }, { tag: 'announced' }]) const key = await until.key const announced = await until.announced @@ -106,7 +108,7 @@ test.skip('exit with non-zero code in teardown', async function ({ is, ok, plan, plan(4) const stager = new Helper() - teardown(() => stager.close()) + teardown(() => stager.close(), { order: Infinity }) await stager.ready() const dir = harness @@ -121,8 +123,10 @@ test.skip('exit with non-zero code in teardown', async function ({ is, ok, plan, comment('seeding') const seeder = new Helper() teardown(() => seeder.close()) + teardown(() => seeder.close(), { order: Infinity }) await seeder.ready() const seed = seeder.seed({ channel: `test-${id}`, name: `test-${id}`, dir }) + teardown(() => Helper.teardownStream(seed)) const until = await Helper.pick(seed, [{ tag: 'key' }, { tag: 'announced' }]) const key = await until.key const announced = await until.announced diff --git a/test/05-encrypted.test.js b/test/05-encrypted.test.js index a0828f63a..38739408f 100644 --- a/test/05-encrypted.test.js +++ b/test/05-encrypted.test.js @@ -15,7 +15,7 @@ test('stage, seed and run encrypted app', async function ({ ok, is, plan, commen plan(7) const helper = new Helper(rig) - teardown(() => helper.close()) + teardown(() => helper.close(), { order: Infinity }) await helper.ready() const dir = encrypted @@ -25,6 +25,7 @@ test('stage, seed and run encrypted app', async function ({ ok, is, plan, commen const name = 'test-encryption-key' const preimage = hypercoreid.encode(crypto.randomBytes(32)) const addEncryptionKey = helper.encryptionKey({ action: 'add', name, value: preimage }) + teardown(() => Helper.teardownStream(addEncryptionKey)) const encryptionKey = await Helper.pick(addEncryptionKey, { tag: 'added' }) is(encryptionKey.name, name) @@ -40,6 +41,7 @@ test('stage, seed and run encrypted app', async function ({ ok, is, plan, commen comment('seeding encrypted app') const seeding = helper.seed({ channel: `test-${id}`, name: `test-${id}`, dir, key: null, encryptionKey: name, cmdArgs: [] }) + teardown(() => Helper.teardownStream(seeding)) const until = await Helper.pick(seeding, [{ tag: 'key' }, { tag: 'announced' }]) const appKey = await until.key const announced = await until.announced diff --git a/test/06-updates.test.js b/test/06-updates.test.js index de41fc285..6250242ce 100644 --- a/test/06-updates.test.js +++ b/test/06-updates.test.js @@ -20,7 +20,7 @@ test('Pear.updates(listener) should notify when restaging and releasing applicat const testId = Math.floor(Math.random() * 100000) const stager1 = new Helper(rig) - teardown(() => stager1.close()) + teardown(() => stager1.close(), { order: Infinity }) await stager1.ready() comment('1. Stage and run app') @@ -45,7 +45,7 @@ test('Pear.updates(listener) should notify when restaging and releasing applicat teardown(() => { try { fs.unlinkSync(path.join(harness, file)) } catch { /* ignore */ } }) comment('\tstaging') const stager2 = new Helper(rig) - teardown(() => stager2.close()) + teardown(() => stager2.close(), { order: Infinity }) await stager2.ready() await Helper.pick(stager2.stage(stageOpts(testId)), { tag: 'final' }) @@ -92,7 +92,7 @@ test('Pear.updates(listener) should notify twice when restaging application twic comment('\tstaging') const stager1 = new Helper(rig) - teardown(() => stager1.close()) + teardown(() => stager1.close(), { order: Infinity }) await stager1.ready() const staging = stager1.stage(stageOpts(testId)) const until = await Helper.pick(staging, [{ tag: 'staging' }, { tag: 'final' }]) @@ -163,7 +163,7 @@ test('Pear.updates should notify Platform stage updates (different pear instance plan(6) timeout(180000) const appStager = new Helper(rig) - teardown(() => appStager.close()) + teardown(() => appStager.close(), { order: Infinity }) await appStager.ready() const channel = 'test-fixture' @@ -175,9 +175,10 @@ test('Pear.updates should notify Platform stage updates (different pear instance comment('seeding app') const appSeeder = new Helper(rig) - teardown(() => appSeeder.close()) + teardown(() => appSeeder.close(), { order: Infinity }) await appSeeder.ready() const appSeeding = appSeeder.seed({ channel, name: channel, dir: harness, key: null, cmdArgs: [] }) + teardown(() => Helper.teardownStream(appSeeding)) const untilApp = await Helper.pick(appSeeding, [{ tag: 'key' }, { tag: 'announced' }]) const appKey = await untilApp.key @@ -207,11 +208,11 @@ test('Pear.updates should notify Platform stage updates (different pear instance const ts = () => new Date().toISOString().replace(/[:.]/g, '-') const file = `${ts()}.tmp` comment(`creating platform test file (${file})`) - fs.writeFileSync(path.join(rig.artifactDir, file), 'test') - teardown(() => { try { fs.unlinkSync(path.join(rig.artifactDir, file)) } catch { /* ignore */ } }, { order: -Infinity }) + fs.writeFileSync(path.join(rig.artefactDir, file), 'test') + teardown(() => { try { fs.unlinkSync(path.join(rig.artefactDir, file)) } catch { /* ignore */ } }, { order: -Infinity }) comment('restaging rig platform') - const staging = rig.local.stage({ channel: `test-${rig.id}`, name: `test-${rig.id}`, dir: rig.artifactDir, dryRun: false, bare: true }) + const staging = rig.local.stage({ channel: `test-${rig.id}`, name: `test-${rig.id}`, dir: rig.artefactDir, dryRun: false, bare: true }) await Helper.pick(staging, { tag: 'final' }) comment('rig platform restaged') comment('waiting for update') @@ -232,7 +233,7 @@ test('Pear.updates should notify Platform stage, Platform release updates (diffe timeout(180000) const appStager = new Helper(rig) - teardown(() => appStager.close()) + teardown(() => appStager.close(), { order: Infinity }) await appStager.ready() const channel = 'test-fixture' @@ -244,10 +245,11 @@ test('Pear.updates should notify Platform stage, Platform release updates (diffe comment('seeding app') const appSeeder = new Helper(rig) - teardown(() => appSeeder.close()) + teardown(() => appSeeder.close(), { order: Infinity }) await appSeeder.ready() const appSeeding = appSeeder.seed({ channel, name: channel, dir: harness, key: null, cmdArgs: [] }) + teardown(() => Helper.teardownStream(appSeeding)) const untilApp = await Helper.pick(appSeeding, [{ tag: 'key' }, { tag: 'announced' }]) const appKey = await untilApp.key @@ -278,11 +280,11 @@ test('Pear.updates should notify Platform stage, Platform release updates (diffe const ts = () => new Date().toISOString().replace(/[:.]/g, '-') const file = `${ts()}.tmp` comment(`creating platform test file (${file})`) - fs.writeFileSync(path.join(rig.artifactDir, file), 'test') - teardown(() => { fs.unlinkSync(path.join(rig.artifactDir, file)) }, { order: -Infinity }) + fs.writeFileSync(path.join(rig.artefactDir, file), 'test') + teardown(() => { fs.unlinkSync(path.join(rig.artefactDir, file)) }, { order: -Infinity }) comment('restaging rig platform') - const staging = rig.local.stage({ channel: `test-${rig.id}`, name: `test-${rig.id}`, dir: rig.artifactDir, dryRun: false, bare: true }) + const staging = rig.local.stage({ channel: `test-${rig.id}`, name: `test-${rig.id}`, dir: rig.artefactDir, dryRun: false, bare: true }) await Helper.pick(staging, { tag: 'final' }) comment('rig platform restaged') comment('waiting for update') @@ -294,7 +296,7 @@ test('Pear.updates should notify Platform stage, Platform release updates (diffe comment('releasing rig platform') const update2Promise = await update2LazyPromise const update2ActualPromise = running.inspector.awaitPromise(update2Promise.objectId) - const releasing = rig.local.release({ channel: `test-${rig.id}`, name: `test-${rig.id}`, dir: rig.artifactDir }) + const releasing = rig.local.release({ channel: `test-${rig.id}`, name: `test-${rig.id}`, dir: rig.artefactDir }) await Helper.pick(releasing, { tag: 'final' }) comment('waiting for platform update notification') @@ -317,7 +319,7 @@ test('Pear.updates should notify App stage updates (different pear instances)', plan(7) timeout(180000) const appStager = new Helper(rig) - teardown(() => appStager.close()) + teardown(() => appStager.close(), { order: Infinity }) await appStager.ready() const channel = 'test-fixture' @@ -328,9 +330,10 @@ test('Pear.updates should notify App stage updates (different pear instances)', comment('seeding app') const appSeeder = new Helper(rig) - teardown(() => appSeeder.close()) + teardown(() => appSeeder.close(), { order: Infinity }) await appSeeder.ready() const appSeeding = appSeeder.seed({ channel, name: channel, dir: harness, key: null, cmdArgs: [] }) + teardown(() => Helper.teardownStream(appSeeding)) const untilApp = await Helper.pick(appSeeding, [{ tag: 'key' }, { tag: 'announced' }]) const appKey = await untilApp.key @@ -366,7 +369,7 @@ test('Pear.updates should notify App stage updates (different pear instances)', comment('restaging app') const appStager2 = new Helper(rig) - teardown(() => appStager2.close()) + teardown(() => appStager2.close(), { order: Infinity }) await appStager2.ready() const appStaging2 = appStager2.stage({ channel, name: channel, dir: harness, dryRun: false, bare: true }) const appFinal2 = await Helper.pick(appStaging2, { tag: 'final' }) @@ -388,7 +391,7 @@ test('Pear.updates should notify App stage, App release updates (different pear plan(9) timeout(180000) const appStager = new Helper(rig) - teardown(() => appStager.close()) + teardown(() => appStager.close(), { order: Infinity }) await appStager.ready() const channel = 'test-fixture' @@ -400,9 +403,10 @@ test('Pear.updates should notify App stage, App release updates (different pear comment('seeding app') const appSeeder = new Helper(rig) - teardown(() => appSeeder.close()) + teardown(() => appSeeder.close(), { order: Infinity }) await appSeeder.ready() const appSeeding = appSeeder.seed({ channel, name: channel, dir: harness, key: null, cmdArgs: [] }) + teardown(() => Helper.teardownStream(appSeeding)) const untilApp = await Helper.pick(appSeeding, [{ tag: 'key' }, { tag: 'announced' }]) const appKey = await untilApp.key @@ -438,7 +442,7 @@ test('Pear.updates should notify App stage, App release updates (different pear comment('restaging app') const appStager2 = new Helper(rig) - teardown(() => appStager2.close()) + teardown(() => appStager2.close(), { order: Infinity }) await appStager2.ready() const appStaging2 = appStager2.stage({ channel, name: channel, dir: harness, dryRun: false, bare: true }) const appFinal2 = await Helper.pick(appStaging2, { tag: 'final' }) diff --git a/test/fixtures/harness/index.js b/test/fixtures/harness/index.js index 71d2b8966..6b196c7f3 100644 --- a/test/fixtures/harness/index.js +++ b/test/fixtures/harness/index.js @@ -21,6 +21,7 @@ class Harness extends ReadyResource { async close () { await this.inspector.disable() await this.sub?.destroy() + if (this._client) await this._client.close() this.inspector = null this.sub = null this.key = null @@ -52,8 +53,8 @@ class Harness extends ReadyResource { async command (argv) { if (this.closed) throw new Error('Harness closed') - const ipc = await this.client() - return this.cmd(ipc, argv) + this._client = await this.client() + return this.cmd(this._client, argv) } } const harness = new Harness() diff --git a/test/helper.js b/test/helper.js index 367454809..53409568a 100644 --- a/test/helper.js +++ b/test/helper.js @@ -23,56 +23,70 @@ const MAX_OP_STEP_WAIT = env.CI ? 360000 : 120000 const tmp = fs.realpathSync(os.tmpdir()) Error.stackTraceLimit = Infinity +const rigPear = path.join(tmp, 'rig-pear') + Pear.teardown(async () => { console.log('# Teardown: Shutting Down Local Sidecar') const local = new Helper() + console.log('# Teardown: Connecting Local Sidecar') await local.ready() + console.log('# Teardown: Triggering Shutdown of Local Sidecar') await local.shutdown() console.log('# Teardown: Local Sidecar Shutdown') }) class Rig { - platformDir = path.join(tmp, 'rig-pear') - artifactDir = env.CI ? path.join(tmp, 'artifact-pear') : Helper.localDir + platformDir = rigPear + artefactDir = Helper.localDir id = Math.floor(Math.random() * 10000) local = new Helper() tmp = tmp - artifactShutdown = false + keepAlive = true + constructor ({ keepAlive = true } = {}) { + this.keepAlive = keepAlive + } + setup = async ({ comment, timeout }) => { timeout(180000) comment('connecting to sidecar') await this.local.ready() comment('connected to sidecar') + comment('staging platform...') - const staging = this.local.stage({ channel: `test-${this.id}`, name: `test-${this.id}`, dir: this.artifactDir, dryRun: false, bare: true }) + const staging = this.local.stage({ channel: `test-${this.id}`, name: `test-${this.id}`, dir: this.artefactDir, dryRun: false, bare: true }) await Helper.pick(staging, { tag: 'final' }) comment('platform staged') - const seeding = await this.local.seed({ channel: `test-${this.id}`, name: `test-${this.id}`, dir: this.artifactDir, key: null, cmdArgs: [] }) - const until = await Helper.pick(seeding, [{ tag: 'key' }, { tag: 'announced' }]) + + comment('seeding platform') + this.seeder = new Helper() + await this.seeder.ready() + this.seeding = this.seeder.seed({ channel: `test-${this.id}`, name: `test-${this.id}`, dir: this.artefactDir, key: null, cmdArgs: [] }) + const until = await Helper.pick(this.seeding, [{ tag: 'key' }, { tag: 'announced' }]) this.key = await until.key await until.announced comment('platform seeding') + comment('bootstrapping rig platform...') await Helper.bootstrap(this.key, this.platformDir) comment('rig platform bootstrapped') - comment('connecting to rig sidecar') - this.artifact = new Helper({ platformDir: this.platformDir }) - Pear.teardown(async () => { - if (this.artifactShutdown) return - console.log('# Teardown: Shutting Down Rig Sidecar [ DIRTY ]') - const helper = this.artifact.closed ? new Helper({ platform: this.platformDir }) : this.artifact - await helper.ready() - await helper.shutdown() - console.log('# Teardown: Rig Sidecar Shutdown [ DIRTY ]') - }) - await this.artifact.ready() + if (this.keepAlive) { + comment('connecting to rig sidecar') + this.rig = new Helper(this) + await this.rig.ready() + comment('connected to rig sidecar') + } } cleanup = async ({ comment }) => { - comment('shutdown rig sidecar') - await this.artifact.shutdown() - this.artifactShutdown = true - comment('rig sidecar closed') + comment('closing seeder client') + await Helper.teardownStream(this.seeding) + await this.seeder.close() + comment('seeder client closed') + if (this.keepAlive) { + comment('shutting down rig sidecar') + await this.rig.shutdown() + comment('rig sidecar shutdown') + } comment('closing local client') await this.local.close() comment('local client closed') @@ -96,7 +110,8 @@ class Helper extends IPC { const verbose = global.Pear.config.args.includes('--verbose') const platformDir = opts.platformDir || PLATFORM_DIR const runtime = path.join(platformDir, 'current', BY_ARCH) - const args = ['--sidecar'] + const dhtBootstrap = Pear.config.dhtBootstrap.map(e => `${e.host}:${e.port}`).join(',') + const args = ['--sidecar', '--dht-bootstrap', dhtBootstrap] if (verbose) args.push('--verbose') const pipeId = (s) => { const buf = b4a.allocUnsafe(32) @@ -119,13 +134,20 @@ class Helper extends IPC { this.verbose = verbose } + static async teardownStream (stream) { + if (stream.destroyed) return + stream.end() + return new Promise((resolve) => stream.on('close', resolve)) + } + // ONLY ADD STATICS, NEVER ADD PUBLIC METHODS OR PROPERTIES (see pear-ipc) static localDir = isWindows ? path.normalize(pathname.slice(1)) : pathname static async open (link, { tags = [] } = {}, opts = {}) { if (!link) throw new Error('Key is missing') - const args = !opts.encryptionKey ? ['run', '-t', link] : ['run', '--encryption-key', opts.encryptionKey, '--no-ask', '-t', link] + const dhtBootstrap = Pear.config.dhtBootstrap.map(e => `${e.host}:${e.port}`).join(',') + const args = !opts.encryptionKey ? ['run', '--dht-bootstrap', dhtBootstrap, '-t', link] : ['run', '--dht-bootstrap', dhtBootstrap, '--encryption-key', opts.encryptionKey, '--no-ask', '-t', link] if (this.verbose) args.push('--verbose') const platformDir = opts.platformDir || PLATFORM_DIR @@ -232,7 +254,7 @@ class Helper extends IPC { await Helper.gc(dir) await fs.promises.mkdir(dir, { recursive: true }) - await updaterBootstrap(key, dir) + await updaterBootstrap(key, dir, { bootstrap: Pear.config.dhtBootstrap }) } static async gc (dir) {