Remove generation profile tiering; use explicit runs count
This commit is contained in:
@@ -96,7 +96,7 @@ See [docs/getting-started.md](docs/getting-started.md) for the full walkthrough.
|
|||||||
## Trust and Safety
|
## Trust and Safety
|
||||||
|
|
||||||
- **Deterministic replay**: Every failure includes a seed and a one-command replay.
|
- **Deterministic replay**: Every failure includes a seed and a one-command replay.
|
||||||
- **Generation profile aliases**: Control test budget with `--generation-profile quick|standard|deep`.
|
- **Explicit test budget**: Control how many tests run with `runs: 10` in your preset.
|
||||||
- **CI-safe default path**: `verify` is deterministic and safe for CI pipelines.
|
- **CI-safe default path**: `verify` is deterministic and safe for CI pipelines.
|
||||||
- **Machine-readable output**: `--format json-summary` and `--format ndjson-summary` for CI dashboards.
|
- **Machine-readable output**: `--format json-summary` and `--format ndjson-summary` for CI dashboards.
|
||||||
- **Production-safe observe path**: `observe` is non-blocking by default. Blocking behavior requires explicit break-glass policy.
|
- **Production-safe observe path**: `observe` is non-blocking by default. Blocking behavior requires explicit break-glass policy.
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ app.post('/users', {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await app.ready()
|
await app.ready()
|
||||||
const suite = await app.apophis.contract({ depth: 'standard' })
|
const suite = await app.apophis.contract({ runs: 50 })
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Surface
|
## API Surface
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ Chaos testing applies the invariant-driven verification approach from [Invariant
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const result = await fastify.apophis.contract({
|
const result = await fastify.apophis.contract({
|
||||||
depth: 'standard',
|
runs: 50,
|
||||||
chaos: {
|
chaos: {
|
||||||
probability: 0.1, // 10% of requests get chaos
|
probability: 0.1, // 10% of requests get chaos
|
||||||
delay: { probability: 1, minMs: 100, maxMs: 500 },
|
delay: { probability: 1, minMs: 100, maxMs: 500 },
|
||||||
|
|||||||
-15
@@ -10,7 +10,6 @@ Every command accepts these flags:
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `--config <path>` | Config file path | Auto-detect |
|
| `--config <path>` | Config file path | Auto-detect |
|
||||||
| `--profile <name>` | Profile name from config | First profile |
|
| `--profile <name>` | Profile name from config | First profile |
|
||||||
| `--generation-profile <name>` | Generation budget profile (built-in: quick, standard, deep) | Depth-derived |
|
|
||||||
| `--cwd <path>` | Working directory override | `process.cwd()` |
|
| `--cwd <path>` | Working directory override | `process.cwd()` |
|
||||||
| `--format <mode>` | Output format: `human`, `json`, `ndjson`, `json-summary`, `ndjson-summary` | `human` |
|
| `--format <mode>` | Output format: `human`, `json`, `ndjson`, `json-summary`, `ndjson-summary` | `human` |
|
||||||
| `--color <mode>` | Color mode: `auto`, `always`, `never` | `auto` |
|
| `--color <mode>` | Color mode: `auto`, `always`, `never` | `auto` |
|
||||||
@@ -62,7 +61,6 @@ apophis verify --profile quick --routes "POST /users"
|
|||||||
| Flag | Description |
|
| Flag | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `--profile <name>` | Profile name from config |
|
| `--profile <name>` | Profile name from config |
|
||||||
| `--generation-profile <name>` | Override generation budget for this run |
|
|
||||||
| `--routes <filter>` | Route filter pattern (comma-separated, supports wildcards) |
|
| `--routes <filter>` | Route filter pattern (comma-separated, supports wildcards) |
|
||||||
| `--seed <number>` | Deterministic seed (generated and printed if omitted) |
|
| `--seed <number>` | Deterministic seed (generated and printed if omitted) |
|
||||||
| `--changed` | Filter to git-modified routes only |
|
| `--changed` | Filter to git-modified routes only |
|
||||||
@@ -121,7 +119,6 @@ apophis qualify --profile oauth-nightly --seed 42
|
|||||||
| Flag | Description |
|
| Flag | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `--profile <name>` | Profile name from config |
|
| `--profile <name>` | Profile name from config |
|
||||||
| `--generation-profile <name>` | Override generation budget for this run |
|
|
||||||
| `--seed <number>` | Deterministic seed (generated and printed if omitted) |
|
| `--seed <number>` | Deterministic seed (generated and printed if omitted) |
|
||||||
|
|
||||||
**Examples:**
|
**Examples:**
|
||||||
@@ -129,18 +126,6 @@ apophis qualify --profile oauth-nightly --seed 42
|
|||||||
```bash
|
```bash
|
||||||
apophis qualify --profile oauth-nightly --seed 42
|
apophis qualify --profile oauth-nightly --seed 42
|
||||||
apophis qualify --profile lifecycle-deep
|
apophis qualify --profile lifecycle-deep
|
||||||
apophis qualify --profile oauth-nightly --generation-profile quick
|
|
||||||
```
|
|
||||||
|
|
||||||
You can define aliases in config:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default {
|
|
||||||
generationProfiles: {
|
|
||||||
pr: 'quick',
|
|
||||||
nightly: { base: 'thorough' },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### `apophis replay`
|
### `apophis replay`
|
||||||
|
|||||||
@@ -153,11 +153,11 @@ fastify.delete('/users/:id', {
|
|||||||
await fastify.ready()
|
await fastify.ready()
|
||||||
|
|
||||||
// Run contract tests (all non-utility routes, property-based)
|
// Run contract tests (all non-utility routes, property-based)
|
||||||
const result = await fastify.apophis.contract({ depth: 'standard' })
|
const result = await fastify.apophis.contract({ runs: 50 })
|
||||||
console.log('Contract tests:', result.summary)
|
console.log('Contract tests:', result.summary)
|
||||||
|
|
||||||
// Run stateful tests (constructor→mutator→destructor sequences)
|
// Run stateful tests (constructor→mutator→destructor sequences)
|
||||||
const stateful = await fastify.apophis.stateful({ depth: 'standard', seed: 42 })
|
const stateful = await fastify.apophis.stateful({ runs: 50, seed: 42 })
|
||||||
console.log('Stateful tests:', stateful.summary)
|
console.log('Stateful tests:', stateful.summary)
|
||||||
|
|
||||||
// Validate a single route
|
// Validate a single route
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ fastify.get('/health', {
|
|||||||
await fastify.ready()
|
await fastify.ready()
|
||||||
|
|
||||||
// Run contract tests
|
// Run contract tests
|
||||||
const result = await fastify.apophis.contract({ depth: 'quick' })
|
const result = await fastify.apophis.contract({ runs: 10 })
|
||||||
console.log(result.summary)
|
console.log(result.summary)
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ export default {
|
|||||||
presets: {
|
presets: {
|
||||||
'llm-safe': {
|
'llm-safe': {
|
||||||
name: 'llm-safe',
|
name: 'llm-safe',
|
||||||
depth: 'quick',
|
|
||||||
timeout: 3000,
|
timeout: 3000,
|
||||||
parallel: false,
|
parallel: false,
|
||||||
chaos: false,
|
chaos: false,
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ export default {
|
|||||||
presets: {
|
presets: {
|
||||||
'platform-observe': {
|
'platform-observe': {
|
||||||
name: 'platform-observe',
|
name: 'platform-observe',
|
||||||
depth: 'standard',
|
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
parallel: true,
|
parallel: true,
|
||||||
chaos: false,
|
chaos: false,
|
||||||
|
|||||||
+5
-5
@@ -23,8 +23,8 @@ BENCH_RUNS=12 BENCH_WARMUP=3 npm run benchmark:cli
|
|||||||
# Increase inner-loop work for micro-benchmarks
|
# Increase inner-loop work for micro-benchmarks
|
||||||
BENCH_INNER_ITERS=5000 npm run benchmark:hot
|
BENCH_INNER_ITERS=5000 npm run benchmark:hot
|
||||||
|
|
||||||
# Benchmark generation profile matrix
|
# Benchmark with varying test counts
|
||||||
BENCH_GENERATION_PROFILES=quick,standard,thorough npm run benchmark:all
|
BENCH_RUNS=10,50,200 npm run benchmark:all
|
||||||
```
|
```
|
||||||
|
|
||||||
## Capture CPU Profile for Qualify
|
## Capture CPU Profile for Qualify
|
||||||
@@ -41,10 +41,10 @@ This writes Chrome-compatible CPU profiles to `.profiles/qualify.cpuprofile` and
|
|||||||
- CLI benchmark uses spawned `node dist/cli/index.js` commands so startup costs are included.
|
- CLI benchmark uses spawned `node dist/cli/index.js` commands so startup costs are included.
|
||||||
- Hot path benchmark runs in-process for lower-noise function-level comparisons.
|
- Hot path benchmark runs in-process for lower-noise function-level comparisons.
|
||||||
- Use fixed `--seed` for qualify benchmarks to keep runs deterministic.
|
- Use fixed `--seed` for qualify benchmarks to keep runs deterministic.
|
||||||
- Generation now adapts to depth: `quick` favors bounded payload generation speed, `thorough` keeps broader generation.
|
- Schema generation uses fixed defaults (string≤128, array≤10) regardless of run count.
|
||||||
|
|
||||||
You can override generation per run:
|
You can override runs per preset:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
apophis qualify --profile oauth-nightly --generation-profile quick --seed 42
|
apophis qualify --profile oauth-nightly --seed 42
|
||||||
```
|
```
|
||||||
|
|||||||
+11
-6
@@ -97,7 +97,7 @@ APOPHIS tracks created resources and runs cleanup after test completion.
|
|||||||
Run stateful tests via the API:
|
Run stateful tests via the API:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const stateful = await fastify.apophis.stateful({ depth: 'standard', seed: 42 })
|
const stateful = await fastify.apophis.stateful({ runs: 50, seed: 42 })
|
||||||
console.log('Stateful tests:', stateful.summary)
|
console.log('Stateful tests:', stateful.summary)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ export default {
|
|||||||
presets: {
|
presets: {
|
||||||
'protocol-lab': {
|
'protocol-lab': {
|
||||||
name: 'protocol-lab',
|
name: 'protocol-lab',
|
||||||
depth: 'deep',
|
runs: 200,
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
parallel: false,
|
parallel: false,
|
||||||
chaos: true,
|
chaos: true,
|
||||||
@@ -258,10 +258,15 @@ Run qualify across all packages in a monorepo workspace:
|
|||||||
apophis qualify --workspace --profile oauth-nightly
|
apophis qualify --workspace --profile oauth-nightly
|
||||||
```
|
```
|
||||||
|
|
||||||
## `--generation-profile` Flag
|
## Test Budget
|
||||||
|
|
||||||
Control test data generation depth independently from the qualification profile:
|
The `runs` field in your preset controls how many property-based tests execute per route. Default is 50. Lower for faster CI feedback, higher for deeper exploration:
|
||||||
|
|
||||||
```bash
|
```javascript
|
||||||
apophis qualify --profile oauth-nightly --generation-profile quick
|
presets: {
|
||||||
|
'protocol-lab': {
|
||||||
|
runs: 200,
|
||||||
|
timeout: 15000
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
+17
-5
@@ -165,7 +165,7 @@ export default {
|
|||||||
},
|
},
|
||||||
presets: {
|
presets: {
|
||||||
'safe-ci': {
|
'safe-ci': {
|
||||||
depth: 'quick',
|
runs: 10,
|
||||||
timeout: 5000
|
timeout: 5000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,10 +184,22 @@ apophis verify --workspace --profile quick --format json
|
|||||||
|
|
||||||
Output includes per-package pass/fail summaries. Fails if any package fails.
|
Output includes per-package pass/fail summaries. Fails if any package fails.
|
||||||
|
|
||||||
## `--generation-profile` Flag
|
## Test Budget
|
||||||
|
|
||||||
Control test data generation depth independently from the verification profile:
|
The `runs` field in your preset controls how many property-based tests execute per route. Default is 50. Lower for faster CI feedback, higher for deeper exploration:
|
||||||
|
|
||||||
```bash
|
```javascript
|
||||||
apophis verify --profile quick --generation-profile quick
|
profiles: {
|
||||||
|
quick: {
|
||||||
|
mode: 'verify',
|
||||||
|
preset: 'safe-ci',
|
||||||
|
routes: ['POST /users']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
presets: {
|
||||||
|
'safe-ci': {
|
||||||
|
runs: 10,
|
||||||
|
timeout: 5000
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@
|
|||||||
"benchmark:cli": "npm run build && node scripts/bench/cli.mjs",
|
"benchmark:cli": "npm run build && node scripts/bench/cli.mjs",
|
||||||
"benchmark:hot": "npm run build && node scripts/bench/hot-paths.mjs",
|
"benchmark:hot": "npm run build && node scripts/bench/hot-paths.mjs",
|
||||||
"profile:qualify": "npm run build && mkdir -p .profiles && node --cpu-prof --cpu-prof-dir=.profiles --cpu-prof-name=qualify.cpuprofile dist/cli/index.js qualify --cwd src/cli/__fixtures__/protocol-lab --profile oauth-nightly --seed 42 --quiet",
|
"profile:qualify": "npm run build && mkdir -p .profiles && node --cpu-prof --cpu-prof-dir=.profiles --cpu-prof-name=qualify.cpuprofile dist/cli/index.js qualify --cwd src/cli/__fixtures__/protocol-lab --profile oauth-nightly --seed 42 --quiet",
|
||||||
"profile:qualify:quick": "npm run build && mkdir -p .profiles && node --cpu-prof --cpu-prof-dir=.profiles --cpu-prof-name=qualify-quick.cpuprofile dist/cli/index.js qualify --cwd src/cli/__fixtures__/protocol-lab --profile oauth-nightly --generation-profile quick --seed 42 --quiet",
|
"profile:qualify:quick": "npm run build && mkdir -p .profiles && node --cpu-prof --cpu-prof-dir=.profiles --cpu-prof-name=qualify-quick.cpuprofile dist/cli/index.js qualify --cwd src/cli/__fixtures__/protocol-lab --profile oauth-nightly --seed 42 --quiet",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"apophis:verify": "apophis verify --profile quick",
|
"apophis:verify": "apophis verify --profile quick",
|
||||||
"apophis:doctor": "apophis doctor"
|
"apophis:doctor": "apophis doctor"
|
||||||
|
|||||||
+1
-18
@@ -8,30 +8,13 @@ const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|||||||
const repoRoot = resolve(__dirname, '..', '..')
|
const repoRoot = resolve(__dirname, '..', '..')
|
||||||
|
|
||||||
const options = getBenchOptions()
|
const options = getBenchOptions()
|
||||||
const generationProfiles = (process.env.BENCH_GENERATION_PROFILES ?? 'default,quick,standard,thorough')
|
|
||||||
.split(',')
|
|
||||||
.map((value) => value.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
|
|
||||||
function withGenerationProfile(baseArgs, profile) {
|
|
||||||
if (profile === 'default') {
|
|
||||||
return baseArgs
|
|
||||||
}
|
|
||||||
return [...baseArgs, '--generation-profile', profile]
|
|
||||||
}
|
|
||||||
|
|
||||||
const scenarios = [
|
const scenarios = [
|
||||||
{ name: 'cli.help', args: ['--help'] },
|
{ name: 'cli.help', args: ['--help'] },
|
||||||
{ name: 'cli.version', args: ['--version'] },
|
{ name: 'cli.version', args: ['--version'] },
|
||||||
{ name: 'cli.doctor', args: ['doctor', '--cwd', 'src/cli/__fixtures__/tiny-fastify', '--quiet'] },
|
{ name: 'cli.doctor', args: ['doctor', '--cwd', 'src/cli/__fixtures__/tiny-fastify', '--quiet'] },
|
||||||
{ name: 'cli.observe.check', args: ['observe', '--cwd', 'src/cli/__fixtures__/observe-config', '--profile', 'staging-observe', '--check-config', '--quiet'] },
|
{ name: 'cli.observe.check', args: ['observe', '--cwd', 'src/cli/__fixtures__/observe-config', '--profile', 'staging-observe', '--check-config', '--quiet'] },
|
||||||
...generationProfiles.map((profile) => ({
|
{ name: 'cli.qualify', args: ['qualify', '--cwd', 'src/cli/__fixtures__/protocol-lab', '--profile', 'oauth-nightly', '--seed', '42', '--quiet'] },
|
||||||
name: `cli.qualify.profile[${profile}]`,
|
|
||||||
args: withGenerationProfile(
|
|
||||||
['qualify', '--cwd', 'src/cli/__fixtures__/protocol-lab', '--profile', 'oauth-nightly', '--seed', '42', '--quiet'],
|
|
||||||
profile,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
|
|||||||
Reference in New Issue
Block a user