From 4401d1d478d3a12c8b5fc36799a51651f5fdba52 Mon Sep 17 00:00:00 2001 From: Glen De Cauwsemaecker Date: Wed, 16 Nov 2022 09:52:26 +0100 Subject: [PATCH] feat: add basic browser support (#26) - use shims + browser field to support - add registerTestcaseResult to communicate end of test with a node runner when running in-browser - Expose getEnvParameters to let a client gather and move testground variables (used for browser support). - Supersede #25, having applies all the previous feedback. --- package-lock.json | 4 +-- package.json | 8 +++++- src/env/env.js | 24 +++++++++++++++++ src/env/index.js | 46 +++++++++++++++++++++++++++++++++ src/env/runtime.js | 35 +++++++++++++++++++++++++ src/env/sync.js | 9 +++++++ src/index.js | 27 ++++++++++++++++++-- src/runtime/events.js | 4 +++ src/runtime/index.js | 7 +++-- src/runtime/params.js | 34 +++++++++++++------------ src/shims/env/env.js | 29 +++++++++++++++++++++ src/shims/os.js | 20 +++++++++++++++ src/shims/runtime/logger.js | 51 +++++++++++++++++++++++++++++++++++++ src/sync/socket.js | 15 ++++++++--- 14 files changed, 286 insertions(+), 27 deletions(-) create mode 100644 src/env/env.js create mode 100644 src/env/index.js create mode 100644 src/env/runtime.js create mode 100644 src/env/sync.js create mode 100644 src/shims/env/env.js create mode 100644 src/shims/os.js create mode 100644 src/shims/runtime/logger.js diff --git a/package-lock.json b/package-lock.json index 84b5d9d..47f5e0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@testground/sdk", - "version": "0.0.0", + "version": "0.1.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@testground/sdk", - "version": "0.0.0", + "version": "0.1.2", "license": "MIT", "dependencies": { "emittery": "^0.10.0", diff --git a/package.json b/package.json index ae5b483..9de9c88 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,11 @@ "engines": { "node": ">=14.0.0" }, + "browser": { + "os": "./src/shims/os.js", + "./src/env/env": "./src/shims/env/env.js", + "./src/runtime/logger": "./src/shims/runtime/logger.js" + }, "aegir": { "test": { "target": [ @@ -66,6 +71,7 @@ }, "contributors": [ "Henrique Dias ", - "Laurent Senta " + "Laurent Senta ", + "Glen De Cauwsemaecker " ] } diff --git a/src/env/env.js b/src/env/env.js new file mode 100644 index 0000000..8d04688 --- /dev/null +++ b/src/env/env.js @@ -0,0 +1,24 @@ +'use strict' + +/** + * Gets the environment that can be used by the environment + * to create the runtime. + * + * @returns {Record} + */ +function getProcessEnv () { + return process.env +} + +/** + * @param {unknown} _result + */ +function registerTestcaseResult (_result) { + // function is used in the browser shim + // to gain the ability to wait until invokeMap is finished +} + +module.exports = { + getProcessEnv, + registerTestcaseResult +} diff --git a/src/env/index.js b/src/env/index.js new file mode 100644 index 0000000..ea69e91 --- /dev/null +++ b/src/env/index.js @@ -0,0 +1,46 @@ +'use strict' + +const runtime = require('./runtime') +const sync = require('./sync') +const { getProcessEnv } = require('./env') + +const ENV_TEST_PARAMETERS = [ + runtime.ENV_TEST_BRANCH, + runtime.ENV_TEST_CASE, + runtime.ENV_TEST_GROUP_ID, + runtime.ENV_TEST_GROUP_INSTANCE_COUNT, + runtime.ENV_TEST_INSTANCE_COUNT, + runtime.ENV_TEST_INSTANCE_PARAMS, + runtime.ENV_TEST_INSTANCE_ROLE, + runtime.ENV_TEST_OUTPUTS_PATH, + runtime.ENV_TEST_PLAN, + runtime.ENV_TEST_REPO, + runtime.ENV_TEST_RUN, + runtime.ENV_TEST_SIDECAR, + runtime.ENV_TEST_START_TIME, + runtime.ENV_TEST_SUBNET, + runtime.ENV_TEST_TAG, + + sync.ENV_SYNC_SERVICE_HOST, + sync.ENV_SYNC_SERVICE_PORT +] + +/** + * Gets the parameters from the environment + * that can be used by the environment to create the runtime. + * + * @returns {Record} + */ +function getEnvParameters () { + const env = getProcessEnv() + return Object.keys(env) + .filter(key => ENV_TEST_PARAMETERS.includes(key)) + .reduce((/** @type {Record} */params, key) => { + params[key] = env[key] + return params + }, {}) +} + +module.exports = { + getEnvParameters +} diff --git a/src/env/runtime.js b/src/env/runtime.js new file mode 100644 index 0000000..44d11c9 --- /dev/null +++ b/src/env/runtime.js @@ -0,0 +1,35 @@ +'use strict' + +const ENV_TEST_BRANCH = 'TEST_BRANCH' +const ENV_TEST_CASE = 'TEST_CASE' +const ENV_TEST_GROUP_ID = 'TEST_GROUP_ID' +const ENV_TEST_GROUP_INSTANCE_COUNT = 'TEST_GROUP_INSTANCE_COUNT' +const ENV_TEST_INSTANCE_COUNT = 'TEST_INSTANCE_COUNT' +const ENV_TEST_INSTANCE_PARAMS = 'TEST_INSTANCE_PARAMS' +const ENV_TEST_INSTANCE_ROLE = 'TEST_INSTANCE_ROLE' +const ENV_TEST_OUTPUTS_PATH = 'TEST_OUTPUTS_PATH' +const ENV_TEST_PLAN = 'TEST_PLAN' +const ENV_TEST_REPO = 'TEST_REPO' +const ENV_TEST_RUN = 'TEST_RUN' +const ENV_TEST_SIDECAR = 'TEST_SIDECAR' +const ENV_TEST_START_TIME = 'TEST_START_TIME' +const ENV_TEST_SUBNET = 'TEST_SUBNET' +const ENV_TEST_TAG = 'TEST_TAG' + +module.exports = { + ENV_TEST_BRANCH, + ENV_TEST_CASE, + ENV_TEST_GROUP_ID, + ENV_TEST_GROUP_INSTANCE_COUNT, + ENV_TEST_INSTANCE_COUNT, + ENV_TEST_INSTANCE_PARAMS, + ENV_TEST_INSTANCE_ROLE, + ENV_TEST_OUTPUTS_PATH, + ENV_TEST_PLAN, + ENV_TEST_REPO, + ENV_TEST_RUN, + ENV_TEST_SIDECAR, + ENV_TEST_START_TIME, + ENV_TEST_SUBNET, + ENV_TEST_TAG +} diff --git a/src/env/sync.js b/src/env/sync.js new file mode 100644 index 0000000..d9d87e5 --- /dev/null +++ b/src/env/sync.js @@ -0,0 +1,9 @@ +'use strict' + +const ENV_SYNC_SERVICE_HOST = 'SYNC_SERVICE_HOST' +const ENV_SYNC_SERVICE_PORT = 'SYNC_SERVICE_PORT' + +module.exports = { + ENV_SYNC_SERVICE_HOST, + ENV_SYNC_SERVICE_PORT +} diff --git a/src/index.js b/src/index.js index 519e882..f17ebd8 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,9 @@ const runtime = require('./runtime') const sync = require('./sync') const network = require('./network') +const env = require('./env') + +const { registerTestcaseResult } = require('./env/env') /** @typedef {import('./runtime').RunEnv} RunEnv */ /** @typedef {import('./sync').SyncClient} SyncClient */ @@ -17,12 +20,28 @@ async function invokeMap (cases) { const runenv = runtime.currentRunEnv() if (cases[runenv.testCase]) { - await invokeHelper(runenv, cases[runenv.testCase]) + try { + await invokeHelper(runenv, cases[runenv.testCase]) + } catch (err) { + registerAndMessageTestcaseResult(err, runenv) + throw err + } } else { - throw new Error(`unrecognized test case: ${runenv.testCase}`) + const err = new Error(`unrecognized test case: ${runenv.testCase}`) + registerAndMessageTestcaseResult(err, runenv) + throw err } } +/** + * @param {unknown} result + * @param {RunEnv} runenv + */ +function registerAndMessageTestcaseResult (result, runenv) { + runenv.recordMessage(`registerTestcaseResult: ${result}`) + registerTestcaseResult(result) +} + /** * Runs the passed test-case and reports the result. * @@ -47,15 +66,18 @@ async function invokeHelper (runenv, fn) { await runenv.recordStart() + let /** @type {unknown} */ testResult = true try { await fn(runenv, client) await runenv.recordSuccess() } catch (err) { await runenv.recordFailure(err) + testResult = err } finally { if (client) { client.close() } + registerAndMessageTestcaseResult(testResult, runenv) } } @@ -63,6 +85,7 @@ module.exports = { invoke, invokeMap, + env, network, runtime, sync diff --git a/src/runtime/events.js b/src/runtime/events.js index f5500ca..4e1980c 100644 --- a/src/runtime/events.js +++ b/src/runtime/events.js @@ -47,6 +47,7 @@ function newEvents (runParams, logger, getSignalEmitter) { logger.info('', { event }) return emitEvent(event) + // eslint-disable-next-line no-warning-comments // TODO(metrics): re.metrics.recordEvent(&evt) }, recordSuccess: () => { @@ -58,6 +59,7 @@ function newEvents (runParams, logger, getSignalEmitter) { logger.info('', { event }) return emitEvent(event) + // eslint-disable-next-line no-warning-comments // TODO(metrics): re.metrics.recordEvent(&evt) }, recordFailure: (err) => { @@ -70,6 +72,7 @@ function newEvents (runParams, logger, getSignalEmitter) { logger.info('', { event }) return emitEvent(event) + // eslint-disable-next-line no-warning-comments // TODO(metrics): re.metrics.recordEvent(&evt) }, recordCrash: (err) => { @@ -83,6 +86,7 @@ function newEvents (runParams, logger, getSignalEmitter) { logger.info('', { event }) return emitEvent(event) + // eslint-disable-next-line no-warning-comments // TODO(metrics): re.metrics.recordEvent(&evt) } } diff --git a/src/runtime/index.js b/src/runtime/index.js index bdc80d2..7add39b 100644 --- a/src/runtime/index.js +++ b/src/runtime/index.js @@ -3,6 +3,7 @@ const { getLogger } = require('./logger') const { newEvents } = require('./events') const { parseRunParams } = require('./params') +const { getEnvParameters } = require('../env') /** @typedef {import('./types').RunParams} RunParams */ /** @typedef {import('./types').SignalEmitter} SignalEmitter */ @@ -14,7 +15,8 @@ const { parseRunParams } = require('./params') * @returns {RunEnv} */ function currentRunEnv () { - return parseRunEnv(process.env) + const env = getEnvParameters() + return parseRunEnv(env) } /** @@ -52,5 +54,6 @@ function newRunEnv (params) { module.exports = { newRunEnv, currentRunEnv, - parseRunEnv + parseRunEnv, + getEnvParameters } diff --git a/src/runtime/params.js b/src/runtime/params.js index 4dd2a53..9ec4956 100644 --- a/src/runtime/params.js +++ b/src/runtime/params.js @@ -2,23 +2,25 @@ const ipaddr = require('ipaddr.js') -/** @typedef {import('./types').RunParams} RunParams */ +const { + ENV_TEST_BRANCH, + ENV_TEST_CASE, + ENV_TEST_GROUP_ID, + ENV_TEST_GROUP_INSTANCE_COUNT, + ENV_TEST_INSTANCE_COUNT, + ENV_TEST_INSTANCE_PARAMS, + ENV_TEST_INSTANCE_ROLE, + ENV_TEST_OUTPUTS_PATH, + ENV_TEST_PLAN, + ENV_TEST_REPO, + ENV_TEST_RUN, + ENV_TEST_SIDECAR, + ENV_TEST_START_TIME, + ENV_TEST_SUBNET, + ENV_TEST_TAG +} = require('../env/runtime') -const ENV_TEST_BRANCH = 'TEST_BRANCH' -const ENV_TEST_CASE = 'TEST_CASE' -const ENV_TEST_GROUP_ID = 'TEST_GROUP_ID' -const ENV_TEST_GROUP_INSTANCE_COUNT = 'TEST_GROUP_INSTANCE_COUNT' -const ENV_TEST_INSTANCE_COUNT = 'TEST_INSTANCE_COUNT' -const ENV_TEST_INSTANCE_PARAMS = 'TEST_INSTANCE_PARAMS' -const ENV_TEST_INSTANCE_ROLE = 'TEST_INSTANCE_ROLE' -const ENV_TEST_OUTPUTS_PATH = 'TEST_OUTPUTS_PATH' -const ENV_TEST_PLAN = 'TEST_PLAN' -const ENV_TEST_REPO = 'TEST_REPO' -const ENV_TEST_RUN = 'TEST_RUN' -const ENV_TEST_SIDECAR = 'TEST_SIDECAR' -const ENV_TEST_START_TIME = 'TEST_START_TIME' -const ENV_TEST_SUBNET = 'TEST_SUBNET' -const ENV_TEST_TAG = 'TEST_TAG' +/** @typedef {import('./types').RunParams} RunParams */ /** * @param {Record} env diff --git a/src/shims/env/env.js b/src/shims/env/env.js new file mode 100644 index 0000000..41464be --- /dev/null +++ b/src/shims/env/env.js @@ -0,0 +1,29 @@ +'use strict' + +/** + * Gets the environment that can be used by the environment + * to create the runtime. + * + * @returns {Record} + */ +function getProcessEnv () { + // @ts-ignore + return (window.testground || {}).env +} +/** + * @param {unknown} result + */ +function registerTestcaseResult (result) { + // @ts-ignore + if (!window.testground) { + // @ts-ignore + window.testground = {} + } + // @ts-ignore + window.testground.result = result +} + +module.exports = { + getProcessEnv, + registerTestcaseResult +} diff --git a/src/shims/os.js b/src/shims/os.js new file mode 100644 index 0000000..9d9a1bc --- /dev/null +++ b/src/shims/os.js @@ -0,0 +1,20 @@ +'use strict' + +/** + * @returns {any[]} + */ +function networkInterfaces () { + return [] +} + +/** + * @returns {string} + */ +function hostname () { + return 'browser' +} + +module.exports = { + networkInterfaces, + hostname +} diff --git a/src/shims/runtime/logger.js b/src/shims/runtime/logger.js new file mode 100644 index 0000000..0d378ee --- /dev/null +++ b/src/shims/runtime/logger.js @@ -0,0 +1,51 @@ +/* eslint-disable no-console */ +'use strict' + +// Loosely based on Winston's Logger logic, +// simplified for our minimal needs. +// See license at https://github.com/winstonjs/winston/blob/master/LICENSE +// for original source code. + +function getLogger () { + return { + /** + * @param {any[]} args + */ + debug (...args) { + return this.log(console.debug, ...args) + }, + + /** + * @param {any[]} args + */ + info (...args) { + return this.log(console.info, ...args) + }, + + /** + * @param {any[]} args + */ + warn (...args) { + return this.log(console.warn, ...args) + }, + + /** + * @param {any[]} args + */ + error (...args) { + return this.log(console.error, ...args) + }, + + /** + * @param {CallableFunction} fn + * @param {any[]} args + */ + log (fn, ...args) { + fn(JSON.stringify(args)) + } + } +} + +module.exports = { + getLogger +} diff --git a/src/sync/socket.js b/src/sync/socket.js index 13f2172..46d5f84 100644 --- a/src/sync/socket.js +++ b/src/sync/socket.js @@ -3,8 +3,13 @@ const Emittery = require('emittery') const WebSocket = require('isomorphic-ws') -const ENV_SYNC_SERVICE_HOST = 'SYNC_SERVICE_HOST' -const ENV_SYNC_SERVICE_PORT = 'SYNC_SERVICE_PORT' +const { + ENV_SYNC_SERVICE_HOST, + ENV_SYNC_SERVICE_PORT +} = require('../env/sync') +const { + getEnvParameters +} = require('../env') /** @typedef {import('winston').Logger} Logger */ /** @typedef {import('events').EventEmitter} EventEmitter */ @@ -97,8 +102,10 @@ function createSocket (logger) { } function socketAddress () { - let host = process.env[ENV_SYNC_SERVICE_HOST] - let port = process.env[ENV_SYNC_SERVICE_PORT] + const env = getEnvParameters() + + let host = env[ENV_SYNC_SERVICE_HOST] + let port = env[ENV_SYNC_SERVICE_PORT] if (!port) { port = '5050'