Skip to content

Commit

Permalink
Refactor plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Feb 13, 2022
1 parent b050d05 commit c6ed2c2
Show file tree
Hide file tree
Showing 39 changed files with 427 additions and 499 deletions.
17 changes: 9 additions & 8 deletions benchmark/spyd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ inputs:
one: 1
two: 2
select: not network slow uniform exponential ushaped growing
runnerConfig:
cli:
tasks: tasks.yml
node:
runner:
- id: node
tasks: tasks.js
# - id: cli
# tasks: tasks.yml
showPrecision: true
showSystem: false
system:
machine: one
cluster: purple
reporter: [debug, history, histogram, boxplot]
reporterConfig:
histogram:
reporter:
- debug
- history
- id: histogram
mini: true
boxplot:
- id: boxplot
mini: true
2 changes: 1 addition & 1 deletion src/bin/config/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The configuration property must then be an object where:
- The value is the configuration value to apply for those combinations.
You can also define variations, i.e. separate combinations for multiple values of
some configuration properties ("inputs", "runnerConfig").
some configuration properties ("inputs", "runner.*").
The configuration property must then be an object where:
- The key is any identifier for that variation
- The value is the configuration value to apply
Expand Down
17 changes: 10 additions & 7 deletions src/bin/config/combinations.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ Can be a globbing pattern.`,
describe: `Tasks' programming language or platform.
Can be specified several times.
Built-in runners: node, cli
This is an object with:
- An "id" property with the runner's id, such as "node"
- Any configuration property passed to that runner, such as "version: 8"
- The "tasks" configuration property can be specified to apply it only to
that runner
A string can be used instead as a shortcut if the object has a single "id"
property.
Built-in runners: "node", "cli"
Custom runners can be installed from npm.
Default: "node"`,
},
runnerConfig: {
group: TASKS,
describe: `Runners' configuration.
Each runner has its own configuration namespaced by its identifier.
For example: --runnerConfig.node.version=8`,
},
inputs: {
group: TASKS,
Expand Down
29 changes: 13 additions & 16 deletions src/bin/config/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,22 @@ export const REPORT_CONFIG = {
describe: `Modules to report the result.
Can be empty, if no reporters should be used.
Can be specified several times.
To use the same reporter several times but with different configurations, append
an underscore followed by any characters to the identifier.
For example: --reporter=debug_tty --runner=debug_ci
Can be specified several times. The same reporter can be used several times but
with different configurations.
Built-in reporters: histogram
This is an object with:
- An "id" property with the reporter's id, such as "histogram"
- Any configuration property passed to that reporter, such as "mini: true"
- The "quiet", "output", "colors", "showTitles", "showSystem", "showMetadata",
"showPrecision" and "showDiff" configuration properties can be specified to
apply them only to that reporter
A string can be used instead as a shortcut if the object has a single "id"
property.
Built-in reporters: "debug", "histogram", "boxplot", "history"
Custom reporters can also be installed from npm.
Default: "debug"`,
},
reporterConfig: {
group: REPORT,
describe: `Reporters' configuration.
Each reporter has its own configuration namespaced by its identifier.
For example: --reporterConfig.json.output=8
The following configuration properties can be set for any reporter: quiet,
output, colors, showTitles, showSystem, showMetadata, showPrecision, showDiff.
For example --reporterConfig.json.output is like --output but only for the json
reporter.`,
},
output: {
group: REPORT,
Expand Down
6 changes: 3 additions & 3 deletions src/combination/tasks/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { findTasks } from './find.js'
import { loadRunner } from './load.js'

// The tasks file for each runner is selected using either the `tasks` or
// `runnerConfig.{runnerId}.tasks` configuration properties.
// `runner.tasks` configuration properties.
// We allow it as a positional CLI flag:
// - This is what many users would expect
// - This allows users to do on-the-fly benchmarks without pre-existing setup
Expand Down Expand Up @@ -48,7 +48,7 @@ import { loadRunner } from './load.js'
// - runner might handle different file extensions differently
// (e.g. *.ts transpiling)
// - We do not provide inline
// `conf.[runnerConfig.{runnerId}.]inline[.{taskId}[.{stepId}]]` "BODY"
// `config.[runner.]inline[.{taskId}[.{stepId}]]` "BODY"
// since this would:
// - Not allow defining content outside function body (e.g. imports) nor
// function declaration (e.g. async keyword)
Expand Down Expand Up @@ -112,7 +112,7 @@ const getDimensionsTasks = async function ({

// When two task files export the same task id, we only keep one based on the
// following priority:
// - `config.runnerConfig.{runnerId}.tasks` over `config.tasks`
// - `config.runner.tasks` over `config.tasks`
// - `tasks` array order (last has priority)
// This allows overridding tasks when using shared configurations.
// This only applies when the task files are using the same runner.
Expand Down
10 changes: 7 additions & 3 deletions src/config/merge/tasks.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Custom merging logic for tasks.
// `tasks` or `runnnerConfig.{runnerId}.tasks` are concatenated, not overridden
// so that shared configurations consumers can add tasks.
// `tasks` or `runner.tasks` are concatenated, not overridden so that shared
// configurations consumers can add tasks.
export const isTasks = function (keys) {
return isTopTasks(keys) || isRunnerTasks(keys)
}
Expand All @@ -10,7 +10,11 @@ const isTopTasks = function (keys) {
}

const isRunnerTasks = function (keys) {
return keys.length === 3 && keys[0] === 'runnerConfig' && keys[2] === 'tasks'
return (
keys[0] === 'runner' &&
keys[keys.length - 1] === 'tasks' &&
(keys.length === 2 || keys.length === 3)
)
}

// Order matters since later `tasks` have priority when merging two task files
Expand Down
2 changes: 1 addition & 1 deletion src/config/normalize/definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {

const configProps = getDummyDefinitions(CONFIG_DEFINITIONS)

// All plugins definitions: `reporter`, `reporterConfig`, `runner`, etc.
// All plugins definitions: `reporter`, `runner`
const plugins = getDummyDefinitionsNames(getPluginsConfigProps())

const cwd = {
Expand Down
8 changes: 6 additions & 2 deletions src/config/normalize/pick.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
// any commands.
// Therefore, we need to filter them out depending on the current command.
export const amongCommands = function (commands) {
return boundAmongCommands.bind(undefined, new Set(commands))
return boundAmongCommands.bind(undefined, commands)
}

const boundAmongCommands = function (
commands,
value,
{ context: { command } },
) {
return commands.has(command)
return isAmongCommands(commands, command)
}

export const isAmongCommands = function (commands, command) {
return commands.includes(command)
}
6 changes: 6 additions & 0 deletions src/config/normalize/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ export const normalizeOptionalArray = function (value = []) {
export const normalizeNumberString = function (value) {
return typeof value === 'number' ? String(value) : value
}

// Some object properties are have a string shortcut property.
// This normalizes them.
export const normalizeObjectOrString = function (propName, value) {
return typeof value === 'string' ? { [propName]: value } : value
}
6 changes: 6 additions & 0 deletions src/config/normalize/validate/complex.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const validateObject = function (value) {
}
}

export const validateObjectOrString = function (value) {
if (typeof value !== 'string' && !isPlainObj(value)) {
throw new Error('must be a string or a plain object.')
}
}

export const validateJson = function (value) {
if (!isJson(value)) {
throw new Error(
Expand Down
107 changes: 39 additions & 68 deletions src/config/plugin/lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,41 @@ import { getDummyDefinitions } from '../../normalize/dummy.js'
import { has } from '../../normalize/lib/prop_path/get.js'
import { normalizeConfig } from '../../normalize/main.js'

// Retrieve plugin configuration object.
// Plugins use both:
// - An array of strings property for selection, for example `reporter`.
// - The name is singular to optimize for the common case.
// - This is useful for including|excluding specific plugins on-the-fly.
// - A `*Config` object property where each key is the plugin id, for
// configuration, for example `reporterConfig.debug.*`.
// The configuration property is also used by steps and tasks, for consistency.
// Some configuration properties can be overridden by plugins, depending on the
// plugin types.
// - For example `output` can be overridden for a specific reporter using
// `reporterConfig.{reporterId}.output`.
// Reasons for this format:
// - Works with all of: CLI flags, programmatic and config file
// - Optimized for patching configuration properties as opposed to replacing,
// which makes more sense
// - Works both for:
// - Selection + configuration (reporter|runner)
// - Configuration-only (steps|tasks)
// - Simple to enable|disable plugins
// - Optimized for the common use cases:
// - Only one plugin
// - No configuration
// - Simple merging of those configuration properties:
// - Deep merging of objects
// - For all of:
// - Array of `config`
// - Parent and child config files
// - CLI|programmatic flags with config file
// - Allows properties to target both:
// - All instances, for example `tasks`
// - Specific instances, for example `runnerConfig.{runnerId}.tasks`
// - Does not rely on top-level configuration properties which name is dynamic
// since those make flags parsing and manipulation harder
// - Does not rely on case or delimiters
// - Which enables using - and _ in user-defined identifiers
// - Works unescaped with YAML, JSON and JavaScript
export const getPluginConfig = async function ({
configPropName,
// Plugins use an array of objects for both selection and configuration.
// This normalizes it.
// Most of the times, a single plugin per type is used. Therefore:
// - A single item can be used instead of an array of items
// - The property name is not pluralized
// This is optimized for configuration-less plugins by providing with a shortcut
// syntax: only the plugin `id` instead of a plugin object.
// Some configuration properties are shared by all plugins of a given type:
// - Top-level properties can be used to configure them for all plugins
// When merging multiple configurations (CLI flags, programmatic, child and
// parent config files):
// - This is optimized for replacing a whole list of plugins of a given type,
// as opposed to patching specific parts of it
// - This is simpler for the majority of cases
// It is possible to use the same plugin twice with different configurations:
// - This is especially useful for using the same reporter but with different
// `output`
// - This is optional, since this might not be wanted for some plugins
// - For example plugins which create combinations (like runners) since
// should use variations instead
export const normalizePluginConfig = async function ({
propName,
sharedConfig,
pluginConfig: unmergedConfig,
plugin,
plugin: { id },
config: {
[configPropName]: { [id]: unmergedPluginConfig = {} },
},
topConfig,
context,
cwd,
pluginConfigDefinitions = [],
sharedProps,
item,
}) {
const pluginConfig = mergeConfigs([topConfig, unmergedPluginConfig])
const prefix = getPrefix.bind(undefined, {
unmergedPluginConfig,
configPropName,
id,
})
const pluginConfig = mergeConfigs([sharedConfig, unmergedConfig])
const prefix = getPrefix.bind(undefined, unmergedConfig, propName)
const pluginConfigA = await normalizeSharedConfig({
pluginConfig,
sharedProps,
item,
pluginConfigDefinitions,
context,
cwd,
Expand All @@ -71,7 +47,7 @@ export const getPluginConfig = async function ({
})
const pluginConfigB = await normalizeSpecificConfig({
pluginConfig: pluginConfigA,
sharedProps,
item,
pluginConfigDefinitions,
context,
cwd,
Expand All @@ -80,42 +56,37 @@ export const getPluginConfig = async function ({
return pluginConfigB
}

const getPrefix = function (
{ unmergedPluginConfig, configPropName, id },
{ path },
) {
const prefix = has(unmergedPluginConfig, path)
? `${configPropName}.${id}.`
: ''
const getPrefix = function (unmergedConfig, propName, { path }) {
const prefix = has(unmergedConfig, path) ? `${propName}.` : ''
return `Configuration property ${prefix}`
}

const normalizeSharedConfig = async function ({
pluginConfig,
sharedProps,
item,
pluginConfigDefinitions,
context,
cwd,
plugin,
prefix,
}) {
const dummyDefinitions = getDummyDefinitions(pluginConfigDefinitions)
return await normalizeConfig(
pluginConfig,
[...sharedProps, ...dummyDefinitions],
{ context: { ...context, plugin }, prefix, cwd },
)
return await normalizeConfig(pluginConfig, [...item, ...dummyDefinitions], {
context: { ...context, plugin },
prefix,
cwd,
})
}

const normalizeSpecificConfig = async function ({
pluginConfig,
sharedProps,
item,
pluginConfigDefinitions,
context,
cwd,
prefix,
}) {
const dummyDefinitions = getDummyDefinitions(sharedProps)
const dummyDefinitions = getDummyDefinitions(item)
return await normalizeConfig(
pluginConfig,
[...dummyDefinitions, ...pluginConfigDefinitions],
Expand Down
38 changes: 0 additions & 38 deletions src/config/plugin/lib/default.js

This file was deleted.

Loading

0 comments on commit c6ed2c2

Please sign in to comment.