feat(appium): add --show-config (#16207)

* feat(appium): rename --show-config to --show-build-info

* feat(appium): add --show-config

This adds a new CLI flag: `--show-config`.  When called, this will dump the values of a) arguments to the `init()` function of `main.js` _or_ args passed thru CLI, b) any config pulled from a config file, and c) the defaults as defined in schemas.

Each section is separated, and `undefined` values are omitted.

Instead of a JSON blob, the output just uses `console.dir()`.

To make this work, some refactors were needed:

- Use of `store_true` "action" for `argparse` in are now `store_const` with `const: true` because `store_true` sets the value to `false` if the flag is not present.  This essentially assigns the default value of the flag (`false`) before it should; default values must come from the schemas.
- Along those lines, removed default values from the "extra" boolean args in `args.js`
- The CLI parser now ignores `undefined` values of arguments

Other:

- Created some types for extension classes and added the `server` prop to `AppiumDriver`.
- Enabled TS checks on `lib/config.js` (where `showConfig()` lives) and `test/config-specs.js`
- Removed unnecessary call to `validate()` in `preflightChecks()`; CLI arguments are validated individually (defined in `lib/schema/cli-args.js`) and the config file is validated separately (defined in `lib/config-file.js`).
- Add more types to `main.js`; rewrite `getServerUpdaters` in lodashese for fun (which I am happy to revert)
- Fix missing type aliases in `test/cli/cli-helpers.js`
- Misc types/refactors in `config-specs.js`
- Update parser tests to reflect that `undefined` values and empty objects are eliminated from the parsed arguments

Notes:

- The `showBuildInfo()` test in `config-specs.js` (which is ostensibly a unit test) forks `git` and also seems to require network connectivity, at least on my box. This stuff should be stubbed out or moved into an E2E test.

Resolves #15672

* chore(appium): remove unused options in `toParserArgs()`
This commit is contained in:
Christopher Hiller
2021-12-14 11:47:10 -08:00
committed by GitHub
parent 9a85a41b9e
commit af96879cfd
12 changed files with 251 additions and 129 deletions
+25 -1
View File
@@ -53,6 +53,7 @@ class AppiumDriver extends BaseDriver {
// It is not recommended to access this property directly from the outside
this.pendingDrivers = {};
/** @type {PluginExtensionClass[]} */
this.pluginClasses = []; // list of which plugins are active
this.sessionPlugins = {}; // map of sessions to actual plugin instances per session
this.sessionlessPlugins = []; // some commands are sessionless, so we need a set of plugins for them
@@ -61,9 +62,12 @@ class AppiumDriver extends BaseDriver {
updateBuildInfo();
}
/** @type {DriverConfig|undefined} */
/** @type {import('./driver-config').default|undefined} */
driverConfig;
/** @type {import('express').Express|undefined} */
server;
/**
* Cancel commands queueing for the umbrella Appium driver
*/
@@ -670,3 +674,23 @@ export class NoDriverProxyCommandError extends Error {
}
export { AppiumDriver };
/**
* @typedef {Object} StaticExtMembers
* @property {(app: import('express').Express, httpServer: import('http').Server) => import('type-fest').Promisable<void>} [updateServer]
* @property {import('@appium/base-driver').MethodMap} [newMethodMap]
*/
/**
* @typedef {Object} StaticPluginMembers
* @property {string} pluginName
*/
/**
* @typedef {import('type-fest').Class<unknown> & StaticPluginMembers & StaticExtMembers} PluginExtensionClass
*/
/**
* @typedef {import('type-fest').Class<unknown> & StaticExtMembers} DriverExtensionClass
*/
+17 -14
View File
@@ -1,7 +1,6 @@
// @ts-check
// @ts-ignore
import { DEFAULT_BASE_PATH } from '@appium/base-driver';
import _ from 'lodash';
import DriverConfig from '../driver-config';
import { APPIUM_HOME, DRIVER_TYPE, INSTALL_TYPES, PLUGIN_TYPE } from '../extension-config';
@@ -175,13 +174,7 @@ function makeRunArgs (type) {
*/
function getServerArgs () {
return new Map([
...toParserArgs({
overrides: {
basePath: {
default: DEFAULT_BASE_PATH
},
}
}),
...toParserArgs(),
...serverArgsDisallowedInConfig,
]);
}
@@ -195,21 +188,31 @@ const serverArgsDisallowedInConfig = new Map([
['--shell'],
{
required: false,
default: null,
help: 'Enter REPL mode',
action: 'store_true',
action: 'store_const',
const: true,
dest: 'shell',
},
],
[
['--show-build-info'],
{
dest: 'showBuildInfo',
action: 'store_const',
const: true,
required: false,
help: 'Show info about the Appium build and exit',
},
],
[
['--show-config'],
{
default: false,
dest: 'showConfig',
action: 'store_true',
action: 'store_const',
const: true,
required: false,
help: 'Show info about the appium server configuration and exit',
},
help: 'Show the current Appium configuration and exit',
}
],
[
['--config'],
+1 -1
View File
@@ -146,7 +146,7 @@ class ArgParser {
return _.reduce(
args,
(unpacked, value, key) => {
if (hasArgSpec(key)) {
if (!_.isUndefined(value) && hasArgSpec(key)) {
const {dest} = /** @type {import('../schema/arg-spec').ArgSpec} */(getArgSpec(key));
_.set(unpacked, dest, value);
} else {
+65 -13
View File
@@ -1,3 +1,6 @@
// @ts-check
/* eslint-disable no-console */
import _ from 'lodash';
import { mkdirp, system, fs } from '@appium/support';
import axios from 'axios';
@@ -22,7 +25,7 @@ const BUILD_INFO = {
};
function getNodeVersion () {
return semver.coerce(process.version);
return /** @type {import('semver').SemVer} */(semver.coerce(process.version));
}
async function updateBuildInfo (useGithubApiFallback = false) {
@@ -42,7 +45,7 @@ async function updateBuildInfo (useGithubApiFallback = false) {
*
* This is needed because Appium cannot assume `package.json` and `.git` are in the same
* directory. Monorepos, see?
* @returns {string|void} Path to dir or `undefined` if not found
* @returns {Promise<string|undefined>} Path to dir or `undefined` if not found
*/
async function findGitRoot () {
return await findUp(GIT_META_ROOT, {cwd: rootDir, type: 'directory'});
@@ -80,6 +83,11 @@ async function getGitRev (useGithubApiFallback = false) {
return null;
}
/**
* @param {string} commitSha
* @param {boolean} [useGithubApiFallback]
* @returns {Promise<number?>}
*/
async function getGitTimestamp (commitSha, useGithubApiFallback = false) {
const gitRoot = await findGitRoot();
if (gitRoot) {
@@ -144,30 +152,30 @@ function warnNodeDeprecations () {
// }
}
async function showConfig () {
async function showBuildInfo () {
await updateBuildInfo(true);
console.log(JSON.stringify(getBuildInfo())); // eslint-disable-line no-console
}
/**
* Returns k/v pairs of server arguments which are _not_ the defaults.
* @param {import('../types/types').ParsedArgs} args
* @returns {Partial<import('../types/types').ParsedArgs>}
* @param {ParsedArgs} args
* @returns {Partial<ParsedArgs>}
*/
function getNonDefaultServerArgs (args) {
// hopefully these function names are descriptive enough
const typesDiffer = (dest) => typeof args[dest] !== typeof defaultsFromSchema[dest];
const typesDiffer = /** @param {string} dest */(dest) => typeof args[dest] !== typeof defaultsFromSchema[dest];
const defaultValueIsArray = (dest) => _.isArray(defaultsFromSchema[dest]);
const defaultValueIsArray = /** @param {string} dest */(dest) => _.isArray(defaultsFromSchema[dest]);
const argsValueIsArray = (dest) => _.isArray(args[dest]);
const argsValueIsArray = /** @param {string} dest */(dest) => _.isArray(args[dest]);
const arraysDiffer = (dest) => _.size(_.difference(args[dest], defaultsFromSchema[dest])) > 0;
const arraysDiffer = /** @param {string} dest */(dest) => _.gt(_.size(_.difference(args[dest], defaultsFromSchema[dest])), 0);
const valuesDiffer = (dest) => args[dest] !== defaultsFromSchema[dest];
const valuesDiffer = /** @param {string} dest */(dest) => args[dest] !== defaultsFromSchema[dest];
const defaultIsDefined = (dest) => !_.isUndefined(defaultsFromSchema[dest]);
const defaultIsDefined = /** @param {string} dest */(dest) => !_.isUndefined(defaultsFromSchema[dest]);
// note that `_.overEvery` is like an "AND", and `_.overSome` is like an "OR"
@@ -209,6 +217,46 @@ function getNonDefaultServerArgs (args) {
return _.pickBy(args, (__, key) => isNotDefault(key));
}
/**
* Compacts an object for {@link showConfig}:
* 1. Removes `subcommand` key/value
* 2. Removes `undefined` values
* 3. Removes empty objects (but not `false` values)
* Does not operate recursively.
*/
const compactConfig = _.partial(
_.omitBy,
_,
(value, key) => key === 'subcommand' || _.isUndefined(value) || (_.isObject(value) && _.isEmpty(value))
);
/**
* Shows a breakdown of the current config after CLI params, config file loaded & defaults applied.
*
* The actual shape of `preConfigParsedArgs` and `defaults` does not matter for the purposes of this function,
* but it's intended to be called with values of type {@link ParsedArgs} and `DefaultValues<true>`, respectively.
*
* @param {object} preConfigParsedArgs - Parsed CLI args (or param to `init()`) before config & defaults applied
* @param {import('./config-file').ReadConfigFileResult} configResult - Result of attempting to load a config file
* @param {object} defaults - Configuration defaults from schemas
*/
function showConfig (preConfigParsedArgs, configResult, defaults) {
console.log('Appium Configuration\n');
if (configResult.config) {
console.log(`via config file at ${configResult.filepath}:\n`);
console.dir(compactConfig(configResult.config));
} else {
console.log(`(no configuration file loaded)\n`);
}
console.log('via CLI or function call:\n');
console.dir(compactConfig(preConfigParsedArgs));
console.log('\nvia defaults:\n');
console.dir(compactConfig(defaults));
}
/**
* @param {string} tmpDir
*/
async function validateTmpDir (tmpDir) {
try {
await mkdirp(tmpDir);
@@ -219,7 +267,11 @@ async function validateTmpDir (tmpDir) {
}
export {
getBuildInfo, checkNodeOk, showConfig,
getBuildInfo, checkNodeOk, showBuildInfo,
warnNodeDeprecations, validateTmpDir, getNonDefaultServerArgs,
getGitRev, APPIUM_VER, updateBuildInfo
getGitRev, APPIUM_VER, updateBuildInfo, showConfig
};
/**
* @typedef {import('../types/types').ParsedArgs} ParsedArgs
*/
+1 -1
View File
@@ -270,7 +270,7 @@ export default class ExtensionConfig {
/**
* Loads extension and returns its main class
* @param {string} extName
* @returns {(...args: any[]) => object }
* @returns {import('type-fest').Class<unknown>}
*/
require (extName) {
const {mainClass} = this.installedExtensions[extName];
+58 -20
View File
@@ -3,6 +3,7 @@
// transpile:main
// @ts-check
import logger from './logger'; // logger needs to remain first of imports
// @ts-ignore
import { routeConfiguringFunction as makeRouter, server as baseServer } from '@appium/base-driver';
import { logger as logFactory, util } from '@appium/support';
@@ -12,13 +13,12 @@ import { AppiumDriver } from './appium';
import { driverConfig, pluginConfig, USE_ALL_PLUGINS } from './cli/args';
import { runExtensionCommand } from './cli/extension';
import { default as getParser, SERVER_SUBCOMMAND } from './cli/parser';
import { APPIUM_VER, checkNodeOk, getGitRev, getNonDefaultServerArgs, showConfig, validateTmpDir, warnNodeDeprecations } from './config';
import { APPIUM_VER, checkNodeOk, getGitRev, getNonDefaultServerArgs, showBuildInfo, validateTmpDir, warnNodeDeprecations, showConfig } from './config';
import { readConfigFile } from './config-file';
import { DRIVER_TYPE, PLUGIN_TYPE } from './extension-config';
import registerNode from './grid-register';
import logger from './logger'; // logger needs to remain first of imports
import { init as logsinkInit } from './logsink';
import { getDefaultsForSchema, validate } from './schema/schema';
import { getDefaultsForSchema } from './schema/schema';
import { inspect } from './utils';
/**
@@ -32,14 +32,12 @@ async function preflightChecks (args, throwInsteadOfExit = false) {
if (args.longStacktrace) {
require('longjohn').async_trace_limit = -1;
}
if (args.showConfig) {
await showConfig();
if (args.showBuildInfo) {
await showBuildInfo();
process.exit(0);
}
warnNodeDeprecations();
validate(args);
if (args.tmpDir) {
await validateTmpDir(args.tmpDir);
}
@@ -112,7 +110,7 @@ function logServerPort (address, port) {
*
* @param {Object} args - argparser parsed dict
* @param {import('./plugin-config').default} pluginConfig - a plugin extension config
* @returns {({pluginName: string} & ((...args: any[]) => unknown))[]}
* @returns {PluginExtensionClass[]}
*/
function getActivePlugins (args, pluginConfig) {
return _.compact(Object.keys(pluginConfig.installedExtensions).filter((pluginName) =>
@@ -121,7 +119,7 @@ function getActivePlugins (args, pluginConfig) {
).map((pluginName) => {
try {
logger.info(`Attempting to load plugin ${pluginName}...`);
const PluginClass = /** @type {{pluginName: string} & ((...args: any[]) => unknown)} */(pluginConfig.require(pluginName));
const PluginClass = /** @type {PluginExtensionClass} */(pluginConfig.require(pluginName));
PluginClass.pluginName = pluginName; // store the plugin name on the class so it can be used later
return PluginClass;
@@ -156,10 +154,22 @@ function getActiveDrivers (args, driverConfig) {
}));
}
/**
* Gets a list of `updateServer` functions from all extensions
* @param {DriverExtensionClass[]} driverClasses
* @param {PluginExtensionClass[]} pluginClasses
* @returns {StaticExtMembers['updateServer'][]}
*/
function getServerUpdaters (driverClasses, pluginClasses) {
return [...driverClasses, ...pluginClasses].map((klass) => klass.updateServer).filter(Boolean);
return _.compact(_.map([...driverClasses, ...pluginClasses], 'updateServer'));
}
/**
* Makes a big `MethodMap` from all the little `MethodMap`s in the extensions
* @param {DriverExtensionClass[]} driverClasses
* @param {PluginExtensionClass[]} pluginClasses
* @returns {import('@appium/base-driver').MethodMap}
*/
function getExtraMethodMap (driverClasses, pluginClasses) {
return [...driverClasses, ...pluginClasses].reduce(
(map, klass) => ({...map, ...klass.newMethodMap}),
@@ -175,20 +185,27 @@ function getExtraMethodMap (driverClasses, pluginClasses) {
* If `args` contains a non-empty `subcommand` which is not `server`, this function
* will resolve with an empty object.
*
* @todo: Use generics/conditional types to specify return values.
* @param {ParsedArgs} [args] - Parsed args
* @returns {Promise<Partial<{appiumDriver: AppiumDriver, parsedArgs: ParsedArgs}>>}
* @example
* import {init, getSchema} from 'appium';
* const options = {}; // config object
* await init(options);
* const schema = getSchema(); // entire config schema including plugins and drivers
* @param {ParsedArgs} [args] - Parsed args
* @returns {Promise<Partial<{appiumDriver: AppiumDriver, parsedArgs: ParsedArgs}>>}
*/
async function init (args) {
const parser = await getParser();
let throwInsteadOfExit = false;
/** @type {ParsedArgs} */
let preConfigParsedArgs;
/** @type {typeof preConfigParsedArgs & import('type-fest').AsyncReturnType<readConfigFile>['config']} */
let parsedArgs;
/**
* This is a definition (instead of declaration) because TS can't figure out
* the value will be defined when it's used.
* @type {ReturnType<getDefaultsForSchema>}
*/
let defaults = {};
if (args) {
// if we have a containing package instead of running as a CLI process,
// that package might not appreciate us calling 'process.exit' willy-
@@ -198,29 +215,39 @@ async function init (args) {
// but remove it since it's not a real server arg per se
delete args.throwInsteadOfExit;
}
parsedArgs = {...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND};
preConfigParsedArgs = {...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND};
} else {
// otherwise parse from CLI
parsedArgs = parser.parseArgs();
preConfigParsedArgs = parser.parseArgs();
}
const configResult = await readConfigFile(parsedArgs.configFile);
const configResult = await readConfigFile(preConfigParsedArgs.configFile);
if (!_.isEmpty(configResult.errors)) {
throw new Error(`Errors in config file ${configResult.filepath}:\n ${configResult.reason ?? configResult.errors}`);
}
// merge config and apply defaults.
// the order of precendece is:
// 1. command line args
// 2. config file
// 3. defaults from config file.
if (parsedArgs.subcommand === SERVER_SUBCOMMAND) {
if (preConfigParsedArgs.subcommand === SERVER_SUBCOMMAND) {
defaults = getDefaultsForSchema(false);
if (preConfigParsedArgs.showConfig) {
showConfig(preConfigParsedArgs, configResult, defaults);
return {};
}
parsedArgs = _.defaultsDeep(
parsedArgs,
preConfigParsedArgs,
configResult.config?.server,
getDefaultsForSchema(false)
defaults
);
} else {
parsedArgs = preConfigParsedArgs;
}
await logsinkInit(parsedArgs);
@@ -310,7 +337,6 @@ async function main (args) {
'to visit sites which could maliciously try to start Appium ' +
'sessions on your machine');
}
// @ts-ignore
appiumDriver.server = server;
try {
// configure as node on grid, if necessary
@@ -363,3 +389,15 @@ export { readConfigFile } from './config-file';
/**
* @typedef {import('../types/types').ParsedArgs} ParsedArgs
*/
/**
* @typedef {import('./appium').PluginExtensionClass} PluginExtensionClass
*/
/**
* @typedef {import('./appium').DriverExtensionClass} DriverExtensionClass
*/
/**
* @typedef {import('./appium').StaticExtMembers} StaticExtMembers
*/
+6 -30
View File
@@ -98,11 +98,9 @@ function makeDescription (schema) {
* as understood by `argparse`.
* @param {AppiumJSONSchema} subSchema - JSON schema for the option
* @param {ArgSpec} argSpec - Argument spec tuple
* @param {SubSchemaToArgDefOptions} [opts] - Options
* @returns {[string[], import('argparse').ArgumentOptions]} Tuple of flag and options
*/
function subSchemaToArgDef (subSchema, argSpec, opts = {}) {
const {overrides = {}} = opts;
function subSchemaToArgDef (subSchema, argSpec) {
let {
type,
appiumCliAliases,
@@ -110,7 +108,7 @@ function subSchemaToArgDef (subSchema, argSpec, opts = {}) {
enum: enumValues,
} = subSchema;
const {name, arg, dest} = argSpec;
const {name, arg} = argSpec;
const aliases = [
aliasToFlag(argSpec),
@@ -142,7 +140,8 @@ function subSchemaToArgDef (subSchema, argSpec, opts = {}) {
switch (type) {
// booleans do not have a type per `ArgumentOptions`, just an "action"
case TYPENAMES.BOOLEAN: {
argOpts.action = 'store_true';
argOpts.action = 'store_const';
argOpts.const = true;
break;
}
@@ -227,16 +226,6 @@ function subSchemaToArgDef (subSchema, argSpec, opts = {}) {
}
}
// overrides override anything we computed here. usually this involves "custom types",
// which are really just transform functions.
argOpts = _.merge(
argOpts,
/** should the override keys correspond to the prop name or the prop dest?
* the prop dest is computed by {@link aliasToDest}.
*/
overrides[dest] ?? {},
);
return [aliases, argOpts];
}
@@ -244,32 +233,19 @@ function subSchemaToArgDef (subSchema, argSpec, opts = {}) {
* Converts the finalized, flattened schema representation into
* ArgumentDefinitions for handoff to `argparse`.
*
* @param {ToParserArgsOptions} opts - Options
* @throws If schema has not been added to ajv (via `finalizeSchema()`)
* @returns {import('../cli/args').ArgumentDefinitions} A map of arryas of
* aliases to `argparse` arguments; empty if no schema found
*/
export function toParserArgs (opts = {}) {
export function toParserArgs () {
const flattened = flattenSchema().filter(({schema}) => !schema.appiumCliIgnored);
return new Map(
_.map(flattened, ({schema, argSpec}) =>
subSchemaToArgDef(schema, argSpec, opts),
subSchemaToArgDef(schema, argSpec),
),
);
}
/**
* Options for {@link toParserArgs}
* @typedef {SubSchemaToArgDefOptions} ToParserArgsOptions
*/
/**
* Options for {@link subSchemaToArgDef}.
* @typedef {Object} SubSchemaToArgDefOptions
* @property {string} [prefix] - The prefix to use for the flag, if any
* @property {{[key: string]: import('argparse').ArgumentOptions}} [overrides] - An object of key/value pairs to override the default values
*/
/**
* @template T
* @typedef {import('ajv/dist/types').FormatValidator<T>} FormatValidator<T>
+8
View File
@@ -143,6 +143,14 @@ export const installLocalExtension = _.curry(
* @typedef {Error & AppiumRunErrorProps & import('../../lib/cli/npm').TeenProcessExecErrorProps} AppiumRunError
*/
/**
* @typedef {import('../../lib/cli/npm').TeenProcessExecResult} TeenProcessExecResult
*/
/**
* @typedef {import('../../lib/cli/npm').TeenProcessExecError} TeenProcessExecError
*/
/**
* Wraps the error returned by {@link exec}.
* @typedef {Object} AppiumRunErrorProps
+59 -17
View File
@@ -1,22 +1,61 @@
// @ts-check
// transpile:mocha
import _ from 'lodash';
import sinon from 'sinon';
import getParser from '../lib/cli/parser';
import { checkNodeOk, getBuildInfo, getNonDefaultServerArgs, showConfig, validateTmpDir, warnNodeDeprecations } from '../lib/config';
import { checkNodeOk, getBuildInfo, getNonDefaultServerArgs, showBuildInfo, showConfig, validateTmpDir, warnNodeDeprecations } from '../lib/config';
import logger from '../lib/logger';
import { getDefaultsForSchema, resetSchema, registerSchema, finalizeSchema } from '../lib/schema/schema';
describe('Config', function () {
/** @type {import('sinon').SinonSandbox} */
let sandbox;
beforeEach(function () {
sandbox = sinon.createSandbox();
});
afterEach(function () {
sandbox.restore();
});
describe('Appium config', function () {
describe('showConfig', function () {
before(function () {
sinon.spy(console, 'log');
});
it('should log the config to console', async function () {
/** @type {import('sinon').SinonSpy<[message?: any, ...extra: any[]],void>} */
let log;
/** @type {import('sinon').SinonSpy<[message?: any, ...extra: any[]],void>} */
let dir;
beforeEach(function () {
log = sandbox.spy(console, 'log');
dir = sandbox.spy(console, 'dir');
});
describe('showBuildInfo()', function () {
it('should log build info to console', async function () {
const config = getBuildInfo();
await showConfig();
console.log.calledOnce.should.be.true; // eslint-disable-line no-console
console.log.getCall(0).args[0].should.contain(JSON.stringify(config)); // eslint-disable-line no-console
await showBuildInfo();
log.should.have.been.calledOnce;
log.firstCall.args.should.contain(JSON.stringify(config));
});
});
describe('showConfig', function () {
describe('when a config file is present', function () {
it('should dump the current Appium config', function () {
showConfig({foo: 'bar'}, {config: {server: {address: 'quux'}}}, {spam: 'food'});
log.should.have.been.calledWith('Appium Configuration\n');
});
it('should skip empty objects', function () {
showConfig({foo: 'bar', cows: {}, pigs: [], sheep: 0, ducks: false}, {config: {server: {address: 'quux'}}}, {spam: 'food'});
dir.should.have.been.calledWith({foo: 'bar', sheep: 0, ducks: false});
});
});
describe('when a config file is not present', function () {
it('should dump the current Appium config sans config file contents', function () {
showConfig({foo: 'bar'}, {}, {spam: 'food'});
log.should.have.been.calledWith('(no configuration file loaded)\n');
});
});
});
});
@@ -27,11 +66,7 @@ describe('Config', function () {
// need to be able to write to process.version
// but also to have access to process methods
// so copy them over to a writable object
let tempProcess = {};
for (let [prop, value] of _.toPairs(process)) {
tempProcess[prop] = value;
}
process = tempProcess; // eslint-disable-line no-global-assign
process = {...process}; // eslint-disable-line no-global-assign
});
after(function () {
process = _process; // eslint-disable-line no-global-assign
@@ -45,6 +80,7 @@ describe('Config', function () {
];
for (const version of unsupportedVersions) {
it(`should fail if node is ${version}`, function () {
// @ts-expect-error
process.version = version;
checkNodeOk.should.throw();
});
@@ -53,14 +89,17 @@ describe('Config', function () {
describe('supported nodes', function () {
it('should succeed if node is 12+', function () {
// @ts-expect-error
process.version = 'v12.0.1';
checkNodeOk.should.not.throw();
});
it('should succeed if node is 13+', function () {
// @ts-expect-error
process.version = 'v13.6.0';
checkNodeOk.should.not.throw();
});
it('should succeed if node is 14+', function () {
// @ts-expect-error
process.version = 'v14.0.0';
checkNodeOk.should.not.throw();
});
@@ -70,17 +109,19 @@ describe('Config', function () {
describe('warnNodeDeprecations', function () {
let spy;
before(function () {
spy = sinon.spy(logger, 'warn');
spy = sandbox.spy(logger, 'warn');
});
beforeEach(function () {
spy.resetHistory();
});
it('should not log a warning if node is 8+', function () {
// @ts-expect-error
process.version = 'v8.0.0';
warnNodeDeprecations();
logger.warn.callCount.should.equal(0);
});
it('should not log a warning if node is 9+', function () {
// @ts-expect-error
process.version = 'v9.0.0';
warnNodeDeprecations();
logger.warn.callCount.should.equal(0);
@@ -142,6 +183,7 @@ describe('Config', function () {
validateTmpDir('/private/if_you_run_with_sudo_this_wont_fail').should.be.rejectedWith(/could not ensure/);
});
it('should fail to use an undefined tmp dir', function () {
// @ts-expect-error
validateTmpDir().should.be.rejectedWith(/could not ensure/);
});
it('should be able to use a tmp dir with correct permissions', function () {
@@ -165,7 +207,7 @@ describe('Config', function () {
});
it('should not fail if process.argv[1] is undefined', async function () {
process.argv[1] = undefined;
delete process.argv[1];
let args = await getParser();
args.prog.should.be.equal('appium');
});
+3 -3
View File
@@ -94,7 +94,7 @@ describe('parser', function () {
});
it('should parse --allow-insecure correctly', function () {
p.parseArgs([]).should.have.property('allowInsecure', undefined);
p.parseArgs([]).should.not.have.property('allowInsecure');
p.parseArgs(['--allow-insecure', '']).allowInsecure.should.eql([]);
p.parseArgs(['--allow-insecure', 'foo']).allowInsecure.should.eql(['foo']);
p.parseArgs(['--allow-insecure', 'foo,bar']).allowInsecure.should.eql(['foo', 'bar']);
@@ -102,7 +102,7 @@ describe('parser', function () {
});
it('should parse --deny-insecure correctly', function () {
p.parseArgs([]).should.have.property('denyInsecure', undefined);
p.parseArgs([]).should.not.have.property('denyInsecure');
p.parseArgs(['--deny-insecure', '']).denyInsecure.should.eql([]);
p.parseArgs(['--deny-insecure', 'foo']).denyInsecure.should.eql(['foo']);
p.parseArgs(['--deny-insecure', 'foo,bar']).denyInsecure.should.eql(['foo', 'bar']);
@@ -161,7 +161,7 @@ describe('parser', function () {
it('should not yet apply defaults', function () {
const args = p.parseArgs([]);
args.driver.fake.should.eql({sillyWebServerHost: undefined, sillyWebServerPort: undefined});
args.should.not.have.property('driver');
});
it('should nicely handle extensions w/ dashes in them', async function () {
+3 -29
View File
@@ -20,7 +20,7 @@ describe('cli-args', function () {
registerSchema(extType, extName, schema);
}
finalizeSchema();
return _.fromPairs([...toParserArgs(opts)]);
return _.fromPairs([...toParserArgs()]);
}
beforeEach(resetSchema);
@@ -40,10 +40,10 @@ describe('cli-args', function () {
result = getArgs({schema, extName, extType});
});
it('should return options containing `action` prop of `store_true` and no `type`', function () {
it('should return options containing `action` prop of `store_const` and no `type`', function () {
expect(result['--plugin-blob-foo']).to.have.property(
'action',
'store_true',
'store_const',
);
});
@@ -343,32 +343,6 @@ describe('cli-args', function () {
});
});
});
describe('overrides', function () {
// this might be better some other way, IDK. I suspect this will be rarely used.
// since extensions can't actually call `toParserArgs()`, they can't set `overrides` anyway.
it('should set them via "dest" key', function () {
const schema = {
properties: {
foo: {
type: 'string',
},
},
type: 'object',
};
const result = getArgs({
schema,
extName,
extType,
overrides: {'plugin.blob.foo': {enum: ['slug', 'snail']}},
});
expect(result['--plugin-blob-foo']).to.have.deep.property('enum', [
'slug',
'snail',
]);
});
});
});
});
});
+5
View File
@@ -160,6 +160,11 @@ interface MoreArgs {
/**
* If true, show the build info and exit
*/
showBuildInfo: boolean;
/**
* If true, show config and exit
*/
showConfig: boolean;
/**