diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 0816e68ac5a..79feb1b92de 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -99,7 +99,7 @@ jobs: # won't increase the time for this workflow to complete. timeout_minutes: 20 command: scripts/registry.sh test ${{ matrix.cli }} ${{steps.get-branch.outputs.result}} - on_retry_command: rm $HOME/bin/agoric + on_retry_command: rm -f $HOME/bin/agoric - name: notify on failure if: > diff --git a/packages/agoric-cli/src/helpers.js b/packages/agoric-cli/src/helpers.js index e8fcad990d1..e140e7eb5bf 100644 --- a/packages/agoric-cli/src/helpers.js +++ b/packages/agoric-cli/src/helpers.js @@ -45,6 +45,7 @@ export const makePspawn = ({ * @param {string | [string, string, string]} [param2.stdio] standard IO * specification * @param {Record} [param2.env] environment + * @param {boolean} [param2.detached] whether the child process should be detached * @returns {Promise & { childProcess: ChildProcess }}} promise for * exit status. The return result has a `childProcess` property to obtain * control over the running process diff --git a/packages/agoric-cli/tools/getting-started.js b/packages/agoric-cli/tools/getting-started.js index dae31eeb298..3dbbdf7a5a7 100644 --- a/packages/agoric-cli/tools/getting-started.js +++ b/packages/agoric-cli/tools/getting-started.js @@ -1,4 +1,5 @@ -/* global process setTimeout setInterval clearInterval */ +// @ts-check +/* global process setTimeout setInterval clearInterval Buffer */ import fs from 'fs'; import path from 'path'; @@ -10,7 +11,13 @@ import { spawn } from 'child_process'; import { makePspawn } from '../src/helpers.js'; -const TIMEOUT_SECONDS = 3 * 60; +const RETRY_BLOCKHEIGHT_SECONDS = 3; +const SOCKET_TIMEOUT_SECONDS = 2; + +// TODO: Set this to `true` when `agoric install $DISTTAG` properly updates the +// getting-started workflow's dependencies to current `@endo/*` and Agoric SDK +// from the local registry. +const AGORIC_INSTALL_DISTTAG = false; const dirname = new URL('./', import.meta.url).pathname; @@ -18,23 +25,59 @@ const dirname = new URL('./', import.meta.url).pathname; // Note that we currently only test: // agoric init dapp-foo -// yarn install +// yarn install (or agoric install $DISTTAG) // yarn start:docker // yarn start:contract // yarn start:ui +/** + * @param {string} url + * @returns {Promise} + */ +const getLatestBlockHeight = url => + new Promise((resolve, reject) => { + const req = request(url, res => { + if (!res) { + reject(Error('no result')); + return; + } + const bodyChunks = []; + res + .on('data', chunk => bodyChunks.push(chunk)) + .on('end', () => { + const body = Buffer.concat(bodyChunks).toString('utf8'); + const { statusCode = 0 } = res; + if (statusCode >= 200 && statusCode < 300) { + const { result: { sync_info: sync = {} } = {} } = JSON.parse(body); + if (sync.catching_up === false) { + resolve(BigInt(sync.latest_block_height)); + return; + } + } + reject(Error(`Cannot get block height: ${statusCode} ${body}`)); + }); + }); + req.setTimeout(SOCKET_TIMEOUT_SECONDS * 1_000); + req.on('error', reject); + req.end(); + }); + export const gettingStartedWorkflowTest = async (t, options = {}) => { - const { init: initOptions = [] } = options; + const { init: initOptions = [], install: installOptions = [] } = options; const pspawn = makePspawn({ spawn }); // Kill an entire process group. const pkill = (cp, signal = 'SIGINT') => process.kill(-cp.pid, signal); + /** @param {Parameters} args */ function pspawnStdout(...args) { const ps = pspawn(...args); - ps.childProcess.stdout.on('data', chunk => { - process.stdout.write(chunk); - }); + const { stdout } = ps.childProcess; + if (stdout) { + stdout.on('data', chunk => { + process.stdout.write(chunk); + }); + } // ps.childProcess.unref(); return ps; } @@ -107,17 +150,51 @@ export const gettingStartedWorkflowTest = async (t, options = {}) => { ); process.chdir('dapp-foo'); - // ============== - // yarn install - t.is(await yarn(['install']), 0, 'yarn install works'); + if (AGORIC_INSTALL_DISTTAG && process.env.AGORIC_INSTALL_OPTIONS) { + // ============== + // agoric install $DISTTAG + const opts = JSON.parse(process.env.AGORIC_INSTALL_OPTIONS); + installOptions.push(...opts); + t.is( + await myMain(['install', ...installOptions]), + 0, + 'agoric install works', + ); + } else { + // ============== + // yarn install + t.is(await yarn(['install', ...installOptions]), 0, 'yarn install works'); + } // ============== // yarn start:docker t.is(await yarn(['start:docker']), 0, 'yarn start:docker works'); - // XXX: use abci_info endpoint to get block height - // sleep to let contract start - await new Promise(resolve => setTimeout(resolve, TIMEOUT_SECONDS)); + // ============== + // wait for the chain to start + let lastKnownBlockHeight = 0n; + for (;;) { + try { + const currentHeight = await getLatestBlockHeight( + 'http://localhost:26657/status', + ); + if (currentHeight > lastKnownBlockHeight) { + const earlierHeight = lastKnownBlockHeight; + lastKnownBlockHeight = currentHeight; + if (earlierHeight > 0n && currentHeight > earlierHeight) { + // We've had at least one block produced. + break; + } + } + } catch (e) { + console.error((e && e.message) || e); + } + + // Wait a bit and try again. + await new Promise(resolve => + setTimeout(resolve, RETRY_BLOCKHEIGHT_SECONDS * 1_000), + ); + } // ============== // yarn start:contract @@ -145,9 +222,9 @@ export const gettingStartedWorkflowTest = async (t, options = {}) => { const req = request('http://localhost:5173/', _res => { resolve('listening'); }); - req.setTimeout(2000); + req.setTimeout(SOCKET_TIMEOUT_SECONDS * 1_000); req.on('error', err => { - if (err.code !== 'ECONNREFUSED') { + if (!('code' in err) || err.code !== 'ECONNREFUSED') { resolve(`Cannot connect to UI server: ${err}`); } }); diff --git a/scripts/get-versions.sh b/scripts/get-versions.sh index 7495a8035c0..a3ede8aff24 100755 --- a/scripts/get-versions.sh +++ b/scripts/get-versions.sh @@ -7,5 +7,5 @@ WORKDIR=${1:-.} cd -- "$WORKDIR" npm query .workspace \ | jq -r '.[].location | "\(.)/package.json"' \ - | xargs jq '{key: .name, value: "^\(.version)"}' \ + | xargs jq 'select(.private | not) | {key: .name, value: "^\(.version)"}' \ | jq --slurp from_entries diff --git a/scripts/registry.sh b/scripts/registry.sh index 4824ac1c3b4..954da1e3f09 100755 --- a/scripts/registry.sh +++ b/scripts/registry.sh @@ -119,7 +119,7 @@ integrationTest() { ;; *) yarn global add "agoric@$DISTTAG" - persistVar AGORIC_CMD '["agoric"]' + persistVar AGORIC_CMD "[\"$(yarn global bin)/agoric\"]" ;; esac diff --git a/scripts/resolve-versions.sh b/scripts/resolve-versions.sh index 98cee14f406..7f3fb050883 100755 --- a/scripts/resolve-versions.sh +++ b/scripts/resolve-versions.sh @@ -8,7 +8,7 @@ set -ueo pipefail DIR=$(dirname -- "${BASH_SOURCE[0]}") -cd -- "$DIR/.." +cd -- "${1-$DIR/..}" override=$(jq 'to_entries | map({ key: ("**/" + .key), value: .value }) | from_entries')