Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"guard-for-in": "error",
"import/no-duplicates": "error",
"import/no-self-import": "error",
"max-lines": ["error", { "max": 400 }],
"no-bitwise": "error",
"no-case-declarations": "error",
"no-duplicate-imports": "error",
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@
"scripts": {
"lint": "oxlint . && tsc && oxfmt --check",
"fmt": "oxfmt",
"benchmark:arithmetic-chains": "node scripts/benchmark-arithmetic-chains.js",
"benchmark:nested-fallbacks": "node scripts/benchmark-nested-fallbacks.js",
"benchmark:corpus": "node scripts/benchmark.js",
"benchmark:serialization": "node scripts/benchmark-serialization.js",
"benchmark:arithmetic-chains": "node scripts/benchmark/benchmark-arithmetic-chains.js",
"benchmark:nested-fallbacks": "node scripts/benchmark/benchmark-nested-fallbacks.js",
"benchmark:corpus": "node scripts/benchmark/benchmark-corpus.js",
"benchmark:serialization": "node scripts/benchmark/benchmark-serialization.js",
"test:benchmark": "node --test 'test/unit/benchmark-*.test.js' test/unit/compare-parser-benchmarks.test.js test/unit/corpus-benchmark.test.js",
"test:benchmark:simulation": "node test/benchmark/statistical-simulation.js",
"benchmark:reanalyze": "node scripts/compare-parser-benchmarks.js",
"benchmark:reanalyze": "node scripts/benchmark/compare-parser-benchmarks.js",
"test": "node --test --test-reporter=dot 'test/**/*.test.js' 'test/**/*.test.cjs'",
"test:mutation:corpus": "node test/mutation/corpus-selection.js",
"test:corpus:full": "POSTCSS_CALC_FULL_CORPUS=1 node --test test/conformance/corpus.test.js"
Expand Down
19 changes: 11 additions & 8 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Benchmark scripts

These scripts are deliberately outside the ordinary test suite. Run them on a
controlled machine with `node scripts/<name>.js` (or the corresponding pnpm
controlled machine with `node scripts/benchmark/<name>.js` (or the corresponding pnpm
command). Benchmark artifacts are schema-v2 JSON files and retain raw
observations, configuration, provenance, and enough information for offline
reanalysis.

- **`benchmark-arithmetic-chains.js`** — runs the fresh-process, paired parser
benchmark for arithmetic shapes. `benchmark-nested-fallbacks.js` does the
For a detailed explanation of the statistical methodology, experiment design,
and software architecture, see [BENCHMARKS.md](../BENCHMARKS.md).

- **`benchmark/benchmark-arithmetic-chains.js`** — runs the fresh-process, paired parser
benchmark for arithmetic shapes. `benchmark/benchmark-nested-fallbacks.js` does the
same for nested `var()` fallbacks. Both accept `--baseline`, `--blocks`,
`--max-attempts`, `--seed`, and `--output`, and write schema-v2 artifacts under
`reports/benchmarks/`. The default arithmetic grid uses four logarithmically
Expand All @@ -24,20 +27,20 @@ reanalysis.
requires every gated runtime, slope, and growth endpoint to meet its
predeclared precision target; the requested block count is never increased
from an observed effect during a run.
- **`compare-parser-benchmarks.js`** — reanalyzes one schema-v2 parser
- **`benchmark/compare-parser-benchmarks.js`** — reanalyzes one schema-v2 parser
artifact and applies the uncertainty-aware runtime, slope, and growth gates.
- **`benchmark-serialization.js`** — measures buffered serializer scaling for wide sums/products, nested calls, and nested opaque fallbacks.
- **`benchmark/benchmark-serialization.js`** — measures buffered serializer scaling for wide sums/products, nested calls, and nested opaque fallbacks.
- **`harvest-github.js`** — scrapes real-world `calc()` expressions from
public GitHub into `test/corpus/github/expressions.txt`.
- **`split-corpus.js`** — splits that file into `github-pure.txt` (feeds
`benchmark.js`/`show-divergences.js` below), `preprocessor.txt`, and
`benchmark/benchmark-corpus.js`/`show-divergences.js` below), `preprocessor.txt`, and
`invalid.txt` (the latter two are used by real CI resilience tests).
- **`lib/corpus.js`** — shared loader for `github-pure.txt`.
- **`benchmark.js`** — (`pnpm benchmark:corpus`) validates and times our
- **`benchmark/benchmark-corpus.js`** — (`pnpm benchmark:corpus`) validates and times our
pipeline against `@csstools/css-calc` over the pure corpus in fresh
processes. It is report-only for speed; correctness and infrastructure
failures are nonzero.
- **`benchmark-plugin.js`** — measures PostCSS processing; awaiting
- **`benchmark/benchmark-plugin.js`** — measures PostCSS processing; awaiting
`.process(...)` already includes result serialization, so the benchmark does
not add a redundant `result.css` read.
- **`show-divergences.js`** — buckets where our output disagrees with
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { runParserBenchmark } from './lib/parser-benchmark.js';
import { runParserBenchmark } from './parser-benchmark.js';

try {
const result = await runParserBenchmark({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Correctness-aware fresh-process corpus benchmark against @csstools/css-calc.
import { runCorpusBenchmark, parseArgs } from './lib/corpus-benchmark.js';
import { runCorpusBenchmark, parseArgs } from './corpus-benchmark.js';

try {
const result = runCorpusBenchmark(parseArgs(process.argv.slice(2)));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { runParserBenchmark } from './lib/parser-benchmark.js';
import { runParserBenchmark } from './parser-benchmark.js';

try {
const result = await runParserBenchmark({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// adapter overhead as well as the calculation pipeline; benchmark.js keeps
// the parser/expression benchmark separate.
import postcss from 'postcss';
import plugin from '../src/index.js';
import plugin from '../../src/index.js';

const WARMUP_RUNS = 3;
// Keep an even count; the common harness migration uses these as paired blocks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
import { serialize as serializeWorktree } from '../src/lib/serialize.js';
import { serialize as serializeWorktree } from '../../src/lib/serialize.js';
import {
num,
dim,
Expand All @@ -16,7 +16,7 @@ import {
opaqueCall,
mkSum,
mkProduct,
} from '../src/lib/node.js';
} from '../../src/lib/node.js';

const WIDE_SIZES = [1_024, 16_384, 65_536];
const OTHER_SIZES = [128, 256, 512];
Expand All @@ -33,7 +33,7 @@ function median(values) {
return sorted[Math.floor(sorted.length / 2)];
}

/** @param {number} size @return {import('../src/lib/node.js').Node} */
/** @param {number} size @return {import('../../src/lib/node.js').Node} */
function wideSum(size) {
return mkSum(
Array.from({ length: size }, (_, index) => ({
Expand All @@ -43,7 +43,7 @@ function wideSum(size) {
);
}

/** @param {number} size @return {import('../src/lib/node.js').Node} */
/** @param {number} size @return {import('../../src/lib/node.js').Node} */
function wideProduct(size) {
return mkProduct(
Array.from({ length: size }, (_, index) => ({
Expand All @@ -53,7 +53,7 @@ function wideProduct(size) {
);
}

/** @param {number} size @return {import('../src/lib/node.js').Node} */
/** @param {number} size @return {import('../../src/lib/node.js').Node} */
function nestedCalls(size) {
let node = ident('--x');
for (let i = 0; i < Math.max(2, Math.ceil(size / 4)); i++) {
Expand All @@ -62,7 +62,7 @@ function nestedCalls(size) {
return node;
}

/** @param {number} size @return {import('../src/lib/node.js').Node} */
/** @param {number} size @return {import('../../src/lib/node.js').Node} */
function nestedOpaqueFallbacks(size) {
let node = opaqueCall('var', [ident('--x'), ', ', dim(1, 'px')]);
const depth = Math.max(2, Math.ceil(size / 4));
Expand All @@ -73,8 +73,8 @@ function nestedOpaqueFallbacks(size) {
}

/**
* @param {(node: import('../src/lib/node.js').Node, opts: {precision: false}) => string} serialize
* @param {import('../src/lib/node.js').Node} node
* @param {(node: import('../../src/lib/node.js').Node, opts: {precision: false}) => string} serialize
* @param {import('../../src/lib/node.js').Node} node
* @param {number} repetitions
* @param {boolean} materialize
* @return {number} elapsed milliseconds
Expand All @@ -98,9 +98,9 @@ function clampRepetitions(repetitions) {
* serializers use the same repetition count for each sample, so their times
* are exposed to the same short-lived runtime effects.
*
* @param {(node: import('../src/lib/node.js').Node, opts: {precision: false}) => string} worktreeSerializer
* @param {(node: import('../src/lib/node.js').Node, opts: {precision: false}) => string} headSerializer
* @param {import('../src/lib/node.js').Node} node
* @param {(node: import('../../src/lib/node.js').Node, opts: {precision: false}) => string} worktreeSerializer
* @param {(node: import('../../src/lib/node.js').Node, opts: {precision: false}) => string} headSerializer
* @param {import('../../src/lib/node.js').Node} node
* @param {boolean} materialize
* @return {{worktree: number, head: number}}
*/
Expand Down Expand Up @@ -141,7 +141,7 @@ function benchmarkPair(worktreeSerializer, headSerializer, node, materialize) {
return { worktree: median(worktreeSamples), head: median(headSamples) };
}

/** @return {Promise<typeof import('../src/lib/serialize.js')>} */
/** @return {Promise<typeof import('../../src/lib/serialize.js')>} */
async function loadHeadSerializer() {
const root = mkdtempSync(join(tmpdir(), 'postcss-calc-serialize-'));
try {
Expand Down
63 changes: 63 additions & 0 deletions scripts/benchmark/benchmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export {
TARGET_BATCH_MS,
MIN_WARMUPS,
MAX_WARMUPS,
MEASURED_BATCHES,
DRIFT_THRESHOLD,
BOOTSTRAP_RESAMPLES,
NON_REGRESSION_MARGIN,
CORPUS_EQUIVALENCE_MARGIN,
GROWTH_THRESHOLD,
MIN_VALID_BLOCKS,
DECISION_CONFIG_VERSION,
PRECISION_METHOD,
DECISION_INTERVAL_METHOD,
CORPUS_INTERVAL_METHOD,
DECISION_CONFIG_KEYS,
validateDecisionConfig,
migrateLegacyDecisionConfig,
decisionConfigForArtifact,
} from './config.js';

export {
normalizeSeed,
seededRandom,
seededShuffle,
balancedOrder,
balancedSchedule,
bootstrapIndices,
} from './random.js';

export {
median,
percentile,
variationMetrics,
geometricMean,
logRatio,
ordinaryInterval,
oneSidedInterval,
pairedRatioSummary,
linearRegression,
normalQuantile,
} from './statistics.js';

export {
bootstrapMeanInterval,
bootstrapRatioInterval,
bootstrapPairedIntervals,
bootstrapStratifiedMaxT,
bootstrapCorpusInterval,
bootstrapPairedReplicateInterval,
} from './bootstrap.js';

export {
sha256File,
sourceTreeHash,
benchmarkHarnessHash,
collectBenchmarkProvenance,
collectEnvironment,
materializeBaseline,
runChild,
} from './provenance.js';

export { validateSchemaV2Artifact } from './validate.js';
Loading
Loading