@sdeverywhere/plugin-check 0.3.35 → 0.3.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -1
- package/package.json +3 -7
- package/bin/sde-check.js +0 -54
package/README.md
CHANGED
|
@@ -22,7 +22,172 @@ yarn add -D @sdeverywhere/plugin-check
|
|
|
22
22
|
|
|
23
23
|
## Usage
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
_Note:_ If you followed the "Quick Start" instructions above and/or are using one of the standard project templates provided by SDEverywhere, the `sde.config.js` file should already be set up to use `plugin-check`.
|
|
26
|
+
Reading these instructions can still be helpful if you are setting up a project manually or want to understand how `plugin-check` can be integrated into your project.
|
|
27
|
+
|
|
28
|
+
### Why use this plugin?
|
|
29
|
+
|
|
30
|
+
Each time your model is built, this plugin:
|
|
31
|
+
|
|
32
|
+
1. Generates a _bundle_ (`check-bundle.js`), which packages up your generated model together with metadata about its inputs, outputs, and graphs.
|
|
33
|
+
2. Runs **check tests**, which assert that a single version of your model behaves as expected (for example, that a stock never goes negative).
|
|
34
|
+
3. Runs **comparison tests**, which run two different versions ("bundles") of your model side by side and flag any differences. This step is skipped if no `baseline` bundle is configured.
|
|
35
|
+
4. Builds an interactive **model-check report** that summarizes the results.
|
|
36
|
+
|
|
37
|
+
In development mode (`sde dev`), the report is served from a local dev server and re-runs automatically as you edit your model and tests.
|
|
38
|
+
In production mode (`sde bundle`), the report is written to disk, and the build fails if any check test fails.
|
|
39
|
+
|
|
40
|
+
### Steps
|
|
41
|
+
|
|
42
|
+
1. Add `@sdeverywhere/plugin-check` as a project "dev" dependency:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
cd your-model-project
|
|
46
|
+
npm install --save-dev @sdeverywhere/plugin-check
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
2. Update your `sde.config.js` file to use `checkPlugin`. This plugin runs as a `postBuild` step, so it should generally appear near the end of the `plugins` array (but before `deployPlugin`, if you use it):
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { checkPlugin } from '@sdeverywhere/plugin-check'
|
|
53
|
+
|
|
54
|
+
export async function config() {
|
|
55
|
+
return {
|
|
56
|
+
modelFiles: ['model/example.mdl'],
|
|
57
|
+
|
|
58
|
+
// Re-run checks when the model or the test definitions change
|
|
59
|
+
watchPaths: ['model/**'],
|
|
60
|
+
|
|
61
|
+
// ...
|
|
62
|
+
|
|
63
|
+
plugins: [
|
|
64
|
+
// ...
|
|
65
|
+
|
|
66
|
+
// Build or serve the model-check report
|
|
67
|
+
checkPlugin({
|
|
68
|
+
// There are no required options; see below for optional configuration
|
|
69
|
+
})
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
3. Write check tests in one or more YAML files (see "Defining tests" below).
|
|
76
|
+
|
|
77
|
+
4. Run `sde dev` to work on your model with the report open in a browser, or `sde bundle` to build the report as part of a production build.
|
|
78
|
+
|
|
79
|
+
### Defining tests
|
|
80
|
+
|
|
81
|
+
By default, the plugin discovers test definitions by scanning your project directory for YAML files:
|
|
82
|
+
|
|
83
|
+
- Check tests: files matching `**/checks/*.yaml`.
|
|
84
|
+
- Comparison tests: files matching `**/comparisons/*.yaml`.
|
|
85
|
+
|
|
86
|
+
A common layout is to keep them next to the model:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
model/
|
|
90
|
+
├── example.mdl
|
|
91
|
+
├── checks/
|
|
92
|
+
| └── checks.yaml
|
|
93
|
+
└── comparisons/
|
|
94
|
+
└── comparisons.yaml
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
A simple `checks.yaml` file looks like this:
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
# yaml-language-server: $schema=../../node_modules/@sdeverywhere/plugin-check/node_modules/@sdeverywhere/check-core/schema/check.schema.json
|
|
101
|
+
|
|
102
|
+
- describe: Total inventory
|
|
103
|
+
tests:
|
|
104
|
+
- it: should be in the range [1000,1300] for all input scenarios
|
|
105
|
+
scenarios:
|
|
106
|
+
- preset: matrix
|
|
107
|
+
datasets:
|
|
108
|
+
- name: Total inventory
|
|
109
|
+
predicates:
|
|
110
|
+
- gte: 1000
|
|
111
|
+
lte: 1300
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The `yaml-language-server` comment at the top is optional but recommended; it enables autocompletion and validation of the test definitions in editors that support the YAML language server.
|
|
115
|
+
|
|
116
|
+
For the full set of scenarios, datasets, and predicates, refer to [Testing and Comparing Your Model](https://github.com/climateinteractive/SDEverywhere/wiki/Testing-and-Comparing-Your-Model) in the [SDEverywhere wiki](https://github.com/climateinteractive/SDEverywhere/wiki).
|
|
117
|
+
|
|
118
|
+
If you prefer to define tests programmatically instead of in YAML, use the `testConfigPath` option to point at a JS file that exports a `getConfigOptions` function; when that option is set, the YAML files are not scanned.
|
|
119
|
+
|
|
120
|
+
### Working locally with `sde dev`
|
|
121
|
+
|
|
122
|
+
When you run `sde dev`, the plugin starts a local dev server (at `http://localhost:8081` by default) that serves the model-check report.
|
|
123
|
+
As you edit your model or your test YAML files, the tests are re-run in the browser and the report refreshes automatically.
|
|
124
|
+
|
|
125
|
+
Each time the model is rebuilt in development mode, the previous bundle is copied to `bundles/previous.js` in your project directory, so you can immediately compare your latest changes against the version you had a moment ago.
|
|
126
|
+
Any other bundle you place in the `bundles` directory is also available for selection in the report, which makes it easy to compare against a known-good version of your model.
|
|
127
|
+
|
|
128
|
+
Use the `serverPort` option to use a custom port:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
checkPlugin({
|
|
132
|
+
serverPort: 8082
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Comparing against a baseline in production builds
|
|
137
|
+
|
|
138
|
+
For production builds, use the `baseline` and `current` options to tell the plugin which two bundles to compare.
|
|
139
|
+
The `baseline` bundle is usually the bundle that was produced by the last build of your main branch, and can be loaded from a local path or from a URL:
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
const deployBaseUrl = 'https://your-username.github.io/your-repo'
|
|
143
|
+
|
|
144
|
+
let baselineBundle
|
|
145
|
+
let currentBundle
|
|
146
|
+
if (process.env.NODE_ENV !== 'development') {
|
|
147
|
+
baselineBundle = {
|
|
148
|
+
name: 'main',
|
|
149
|
+
url: `${deployBaseUrl}/branch/main/extras/check-bundle.js`
|
|
150
|
+
}
|
|
151
|
+
currentBundle = {
|
|
152
|
+
name: process.env.GITHUB_REF_NAME || 'current'
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ...
|
|
157
|
+
|
|
158
|
+
checkPlugin({
|
|
159
|
+
// The "left" bundle in comparisons; if undefined, comparison tests are skipped
|
|
160
|
+
baseline: baselineBundle,
|
|
161
|
+
|
|
162
|
+
// The "right" bundle in comparisons; if undefined, the bundle generated by this
|
|
163
|
+
// build is used
|
|
164
|
+
current: currentBundle,
|
|
165
|
+
|
|
166
|
+
// The list of bundles that can be selected in the report when running locally
|
|
167
|
+
remoteBundlesUrl: `${deployBaseUrl}/metadata/bundles.json`
|
|
168
|
+
})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Notes on this configuration:
|
|
172
|
+
|
|
173
|
+
- If the `baseline` bundle cannot be loaded (for example, the first time you build, before any bundle has been published), the build logs a warning, runs the check tests, and skips the comparisons.
|
|
174
|
+
- If the `baseline` bundle was produced by a different version of SDEverywhere than the `current` bundle, comparisons are skipped as well.
|
|
175
|
+
- Use `fetchRemoteBundle` if loading the baseline bundle requires custom logic, such as an authorization header.
|
|
176
|
+
|
|
177
|
+
The [`@sdeverywhere/plugin-deploy`](https://github.com/climateinteractive/SDEverywhere/tree/main/packages/plugin-deploy) package publishes both `check-bundle.js` and `metadata/bundles.json` in the layout assumed above, so the two plugins are designed to be used together.
|
|
178
|
+
|
|
179
|
+
### Build products
|
|
180
|
+
|
|
181
|
+
In a production build (`sde bundle`), the plugin writes the following to the `sde-prep` directory:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
sde-prep/
|
|
185
|
+
├── check-bundle.js # The bundle for the current version of the model
|
|
186
|
+
└── check-report/ # The generated model-check report (a static web app)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Use the `reportPath` option to write the report somewhere else.
|
|
190
|
+
If any check test fails, `sde bundle` exits with a non-zero status, which causes the build to fail in CI.
|
|
26
191
|
|
|
27
192
|
## Documentation
|
|
28
193
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdeverywhere/plugin-check",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.36",
|
|
4
4
|
"files": [
|
|
5
|
-
"bin/**",
|
|
6
5
|
"dist/**",
|
|
7
6
|
"template-bundle/**",
|
|
8
7
|
"template-report/**",
|
|
@@ -19,16 +18,13 @@
|
|
|
19
18
|
"require": "./dist/index.cjs"
|
|
20
19
|
}
|
|
21
20
|
},
|
|
22
|
-
"bin": {
|
|
23
|
-
"sde-check": "bin/sde-check.js"
|
|
24
|
-
},
|
|
25
21
|
"dependencies": {
|
|
26
22
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
27
23
|
"@rollup/plugin-replace": "^6.0.3",
|
|
28
24
|
"@sdeverywhere/check-core": "^0.1.13",
|
|
29
25
|
"@sdeverywhere/check-ui-shell": "^0.2.25",
|
|
30
|
-
"@sdeverywhere/runtime": "^0.2.
|
|
31
|
-
"@sdeverywhere/runtime-async": "^0.2.
|
|
26
|
+
"@sdeverywhere/runtime": "^0.2.10",
|
|
27
|
+
"@sdeverywhere/runtime-async": "^0.2.10",
|
|
32
28
|
"assert-never": "^1.2.1",
|
|
33
29
|
"chokidar": "^5.0.0",
|
|
34
30
|
"picocolors": "^1.0.0",
|
package/bin/sde-check.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* This is the entrypoint for the `sde-check` CLI, which provides commands
|
|
5
|
-
* that work in conjunction with `@sdeverywhere/plugin-check`.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { copyFileSync, existsSync, mkdirSync } from 'fs'
|
|
9
|
-
import { join as joinPath } from 'path'
|
|
10
|
-
|
|
11
|
-
function printUsage() {
|
|
12
|
-
console.log()
|
|
13
|
-
console.log('Usage:')
|
|
14
|
-
console.log(' sde-check save-current-bundle\t\tcopy the latest bundle to the `bundles` directory')
|
|
15
|
-
console.log()
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function timestamp() {
|
|
19
|
-
const padL = (nr, len = 2, chr = `0`) => `${nr}`.padStart(len, chr)
|
|
20
|
-
|
|
21
|
-
const d = new Date()
|
|
22
|
-
const year = d.getFullYear()
|
|
23
|
-
const month = padL(d.getMonth() + 1)
|
|
24
|
-
const day = padL(d.getDate())
|
|
25
|
-
const hours = padL(d.getHours())
|
|
26
|
-
const minutes = padL(d.getMinutes())
|
|
27
|
-
const seconds = padL(d.getSeconds())
|
|
28
|
-
|
|
29
|
-
return `${year}-${month}-${day}--${hours}-${minutes}-${seconds}`
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const args = process.argv.slice(2)
|
|
33
|
-
if (args.length !== 2 || args[0] !== 'save-current-bundle') {
|
|
34
|
-
printUsage()
|
|
35
|
-
process.exit(1)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// TODO: For now we make a number of assumptions (e.g., that the bundle will be copied to
|
|
39
|
-
// the `bundles` directory under the current working directory, that the bundle filename
|
|
40
|
-
// will contain the current timestamp); we should make these configurable
|
|
41
|
-
const prepDir = joinPath(process.cwd(), 'sde-prep')
|
|
42
|
-
const srcBundleFile = joinPath(prepDir, 'check-bundle.js')
|
|
43
|
-
if (!existsSync(srcBundleFile)) {
|
|
44
|
-
console.error(`ERROR: No 'check-bundle.js' file found in 'sde-prep' directory`)
|
|
45
|
-
process.exit(1)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const bundlesDir = joinPath(process.cwd(), 'bundles')
|
|
49
|
-
if (!existsSync(bundlesDir)) {
|
|
50
|
-
mkdirSync(bundlesDir, { recursive: true })
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const dstBundleFile = joinPath(bundlesDir, `${timestamp()}.js`)
|
|
54
|
-
copyFileSync(srcBundleFile, dstBundleFile)
|