UNPKG

artillery

Version:

Cloud-scale load testing. https://www.artillery.io

224 lines (223 loc) 8.88 kB
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import createDebug from 'debug'; // TODO: async-ify this import fs from 'node:fs'; import path from 'node:path'; import A from 'async'; const debug = createDebug('bom'); import Table from 'cli-table3'; import _ from 'lodash'; import { getCustomJsDependencies } from "../platform/aws-ecs/legacy/bom.js"; import { parseScript, readScript } from "../util.js"; import BUILTIN_PLUGINS from "./built-in-plugins.js"; // NOTE: Presumes ALL paths are absolute. async function createBOM(absoluteScriptPath, extraFiles, opts, callback) { A.waterfall([ A.constant(absoluteScriptPath), readScript, parseScript, (scriptData, next) => { return next(null, { opts: { scriptData, absoluteScriptPath }, localFilePaths: [absoluteScriptPath], npmModules: [] }); }, getPlugins, getCustomEngines, getCustomJsDependencies, getVariableDataFiles, // getFileUploadPluginFiles, getExtraFiles // expandDirectories ], (err, context) => { if (err) { return callback(err, null); } context.localFilePaths = context.localFilePaths.concat(extraFiles); // TODO: Entries in localFilePaths may be directories // Handle case with only one entry, where the string itself // will be the common prefix, meaning that when we substring() on it later, we'll // get an empty string, ending up with a manifest like: // { files: // [ { orig: '/Users/h/tmp/artillery/hello.yaml', noPrefix: '' } ], // modules: [] } // let prefix = ''; if (context.localFilePaths.length === 1) { prefix = context.localFilePaths[0].substring(0, context.localFilePaths[0].length - path.basename(context.localFilePaths[0]).length); // This may still be an empty string if the script path is just 'hello.yml': prefix = prefix.length === 0 ? context.localFilePaths[0] : prefix; } else { prefix = commonPrefix(context.localFilePaths); } debug('prefix', prefix); // // include package.json / package-lock.json / yarn.lock // let packageDescriptionFiles = ['.npmrc']; if (opts.packageJsonPath) { packageDescriptionFiles.push(opts.packageJsonPath); } else { packageDescriptionFiles = packageDescriptionFiles.concat([ 'package.json', 'package-lock.json', 'yarn.lock' ]); } const dependencyFiles = packageDescriptionFiles.map((s) => path.join(prefix, s)); debug(dependencyFiles); dependencyFiles.forEach((p) => { try { if (fs.statSync(p)) { context.localFilePaths.push(p); } } catch (_ignoredErr) { } }); const files = context.localFilePaths.map((p) => { return { orig: p, noPrefix: p.substring(prefix.length, p.length) }; }); const pkgPath = _.find(files, (f) => { return f.noPrefix === 'package.json'; }); if (pkgPath) { const pkg = JSON.parse(fs.readFileSync(pkgPath.orig, 'utf8')); const pkgDeps = [].concat(Object.keys(pkg.dependencies || {}), Object.keys(pkg.devDependencies || {})); context.pkgDeps = pkgDeps; context.npmModules = _.uniq(context.npmModules.concat(pkgDeps)).sort(); } else { context.pkgDeps = []; } return callback(null, { files: _.uniqWith(files, _.isEqual), modules: _.uniq(context.npmModules).filter((m) => m !== 'artillery' && m !== 'playwright' && !m.startsWith('@playwright/')), pkgDeps: context.pkgDeps }); }); } function getPlugins(context, next) { const environmentPlugins = _.reduce(_.get(context, 'opts.scriptData.config.environments', {}), function getEnvironmentPlugins(acc, envSpec, _envName) { acc = acc.concat(Object.keys(envSpec.plugins || [])); return acc; }, []); const pluginNames = Object.keys(_.get(context, 'opts.scriptData.config.plugins', {})).concat(environmentPlugins); const pluginPackages = _.uniq(pluginNames .filter((p) => BUILTIN_PLUGINS.indexOf(p) === -1) .map((p) => `artillery-plugin-${p}`)); debug(pluginPackages); context.npmModules = context.npmModules.concat(pluginPackages); return next(null, context); } function getCustomEngines(context, next) { // TODO: Environment-specific engines (see getPlugins()) const engineNames = _.uniq(Object.keys(_.get(context, 'opts.scriptData.config.engines', {}))); const enginePackages = engineNames.map((x) => `artillery-engine-${x}`); context.npmModules = context.npmModules.concat(enginePackages); return next(null, context); } function getVariableDataFiles(context, next) { // NOTE: Presuming that the script has been run through the functions // that normalize the config.payload definition (presume it's an array). // Also assuming that context.opts.scriptData contains both the config and // the scenarios section. // Iterate over environments function resolvePayloadPaths(obj) { const result = []; if (obj.payload) { if (_.isArray(obj.payload)) { obj.payload.forEach((payloadSpec) => { result.push(path.resolve(path.dirname(context.opts.absoluteScriptPath), payloadSpec.path)); }); } else if (_.isObject(obj.payload)) { // NOTE: isObject returns true for arrays, so this branch must // come second. result.push(path.resolve(path.dirname(context.opts.absoluteScriptPath), obj.payload.path)); } } return result; } context.localFilePaths = context.localFilePaths.concat(resolvePayloadPaths(context.opts.scriptData.config)); context.opts.scriptData.config.environments = context.opts.scriptData.config.environments || {}; Object.keys(context.opts.scriptData.config.environments).forEach((envName) => { const envSpec = context.opts.scriptData.config.environments[envName]; context.localFilePaths = context.localFilePaths.concat(resolvePayloadPaths(envSpec)); }); return next(null, context); } function getExtraFiles(context, next) { if (context.opts.scriptData.config?.includeFiles) { const absPaths = _.map(context.opts.scriptData.config.includeFiles, (p) => { const includePath = path.resolve(path.dirname(context.opts.absoluteScriptPath), p); debug('includeFile:', includePath); return includePath; }); context.localFilePaths = context.localFilePaths.concat(absPaths); return next(null, context); } else { return next(null, context); } } function commonPrefix(paths, separator) { if (!paths || paths.length === 0 || paths.filter((s) => typeof s !== 'string').length > 0) { return ''; } if (paths.includes('/')) { return '/'; } const sep = separator ? separator : path.sep; const splitPaths = paths.map((p) => p.split(sep)); const shortestPath = splitPaths.reduce((a, b) => { return a.length < b.length ? a : b; }, splitPaths[0]); let furthestIndex = shortestPath.length; for (const p of splitPaths) { for (let i = 0; i < furthestIndex; i++) { if (p[i] !== shortestPath[i]) { furthestIndex = i; break; } } } const joined = shortestPath.slice(0, furthestIndex).join(sep); if (joined.length > 0) { // Check if joined path already ends with separator which // will happen when input is a root drive on Windows, e.g. "C:\" return joined.endsWith(sep) ? joined : joined + sep; } else { return ''; } } function prettyPrint(manifest) { const t = new Table({ head: ['Name', 'Type', 'Notes'] }); for (const f of manifest.files) { t.push([f.noPrefix, 'file']); } for (const m of manifest.modules) { t.push([ m, 'package', manifest.pkgDeps.indexOf(m) === -1 ? 'not in package.json' : '' ]); } artillery.log(t.toString()); artillery.log(); } export { createBOM, commonPrefix, prettyPrint };