-
Notifications
You must be signed in to change notification settings - Fork 31
feat: add performance-testing package #3024
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JoshuaKGoldberg
wants to merge
18
commits into
main
Choose a base branch
from
perf-comparisons
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
c407a90
[WIP] feat: add performance comparisons package
JoshuaKGoldberg 7b24685
Merge branch 'main'
JoshuaKGoldberg 6153d4c
--skip-formatting
JoshuaKGoldberg d1963eb
Merge branch 'main' into perf-comparisons
JoshuaKGoldberg 7e65b24
feat: make ESLint and Flint measurements directly comparable
JoshuaKGoldberg f2751e3
fix: get performance-testing past CI
JoshuaKGoldberg 51b7e2e
chore: apply performance testing review feedback
JoshuaKGoldberg 04a80b5
initial points
JoshuaKGoldberg ed391cc
Apply suggestions from code review
JoshuaKGoldberg bfb15ab
lil fix: ordering
JoshuaKGoldberg 48373f0
added some basic unit tests
JoshuaKGoldberg 990bb81
Merge branch 'main' into perf-comparisons
JoshuaKGoldberg 89bdba0
fix: get performance-testing past knip and flint after merging main
JoshuaKGoldberg b0e55f0
Apply suggestions from code review
JoshuaKGoldberg 5f2c1ef
path.join in writeStructure
JoshuaKGoldberg dd0863d
also normalize in writeFile.test.ts
JoshuaKGoldberg feddd2e
I thought I removed the results section...
JoshuaKGoldberg 6aaf8f1
Corrected Knip config
JoshuaKGoldberg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,4 +2,5 @@ | |
| packages/*/.astro | ||
| packages/*/dist | ||
| packages/*/lib | ||
| packages/performance-testing/cases | ||
| /pnpm-lock.yaml | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| /cases |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <h1 align="center"><code>@flint.fyi/performance-testing</code></h1> | ||
|
|
||
| <p align="center"> | ||
| Runs performance measurements for Flint and other linters. | ||
| ❤️🔥 | ||
| </p> | ||
|
|
||
| > Requires [hyperfine](https://github.com/sharkdp/hyperfine#installation) to be installed globally. | ||
|
|
||
| ```shell | ||
| pnpm generate | ||
| pnpm measure | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| { | ||
| "name": "@flint.fyi/performance-testing", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "description": "Runs performance measurements for Flint and other linters.", | ||
| "homepage": "https://flint.fyi", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/flint-fyi/flint.git", | ||
| "directory": "packages/performance-testing" | ||
| }, | ||
| "license": "MIT", | ||
| "author": { | ||
| "name": "Flint Team", | ||
| "url": "https://flint.fyi/team" | ||
| }, | ||
| "sideEffects": false, | ||
| "type": "module", | ||
| "scripts": { | ||
| "generate": "node src/generate.ts", | ||
| "measure": "DEBUG=\"*runInHyperfine\" node src/measure.ts", | ||
| "test": "vitest --project performance-testing" | ||
| }, | ||
| "dependencies": { | ||
| "@flint.fyi/rule-data": "workspace:^", | ||
| "console-table-without-index": "^0.1.1", | ||
| "debug-for-file": "^0.4.0", | ||
| "execa": "catalog:dev", | ||
| "flint": "workspace:^", | ||
| "prettier": "catalog:dev" | ||
| }, | ||
| "devDependencies": { | ||
| "@flint.fyi/build": "workspace:^", | ||
| "eslint": "catalog:dev", | ||
| "eslint-plugin-import": "catalog:comparisons", | ||
| "eslint-plugin-regexp": "catalog:comparisons", | ||
| "eslint-plugin-unicorn": "catalog:comparisons", | ||
| "typescript": "catalog:dev", | ||
| "typescript-eslint": "catalog:dev", | ||
| "vitest": "catalog:dev" | ||
| }, | ||
| "engines": { | ||
| "node": ">=26.1.0" | ||
| } | ||
| } |
17 changes: 17 additions & 0 deletions
17
packages/performance-testing/src/createTestCaseSlug.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
|
|
||
| import { createTestCaseSlug } from "./createTestCaseSlug.ts"; | ||
|
|
||
| describe(createTestCaseSlug, () => { | ||
| it("joins the entry labels and values when given a numeric rules count", () => { | ||
| const actual = createTestCaseSlug({ files: 256, rules: 1 }); | ||
|
|
||
| expect(actual).toBe("files-256-rules-1"); | ||
| }); | ||
|
|
||
| it("lowercases values when given a named rules preset", () => { | ||
| const actual = createTestCaseSlug({ files: 1024, rules: "common" }); | ||
|
|
||
| expect(actual).toBe("files-1024-rules-common"); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import type { TestCase } from "./testCases.ts"; | ||
|
|
||
| export function createTestCaseSlug(testCase: TestCase): string { | ||
| return Object.entries(testCase).flat().join("-").toLowerCase(); | ||
| } |
33 changes: 33 additions & 0 deletions
33
packages/performance-testing/src/creators/createCaseFiles.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
|
|
||
| import { countCaseFiles, createCaseFiles } from "./createCaseFiles.ts"; | ||
|
|
||
| describe(countCaseFiles, () => { | ||
| it.for([2, 256, 1024])( | ||
| "counts source files matching the requested count when files is %i", | ||
| (files) => { | ||
| const actual = countCaseFiles({ files, rules: 1 }); | ||
|
|
||
| expect(actual).toBe(files); | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| describe(createCaseFiles, () => { | ||
| it("creates a config file per linter alongside src and tsconfig.json", () => { | ||
| const actual = createCaseFiles({ files: 2, rules: 1 }); | ||
|
|
||
| expect(Object.keys(actual)).toEqual([ | ||
| "eslint.config.js", | ||
| "flint.config.ts", | ||
| "src", | ||
| "tsconfig.json", | ||
| ]); | ||
| }); | ||
|
|
||
| it("nests example directories under src when files is small", () => { | ||
| const actual = createCaseFiles({ files: 2, rules: 1 }); | ||
|
|
||
| expect(Object.keys(actual.src ?? {})).toEqual(["index.ts", "example0"]); | ||
| }); | ||
| }); |
89 changes: 89 additions & 0 deletions
89
packages/performance-testing/src/creators/createCaseFiles.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import type { TestCase } from "../testCases.ts"; | ||
| import type { Structure } from "../writing/writeStructure.ts"; | ||
| import { createESLintConfigFile } from "./files/createESLintConfigFile.ts"; | ||
| import { createFlintConfigFile } from "./files/createFlintConfigFile.ts"; | ||
| import { createStandardTSConfigFile } from "./files/createStandardTSConfigFile.ts"; | ||
| import { range } from "./range.ts"; | ||
|
|
||
| export function countCaseFiles(testCase: TestCase): number { | ||
| return countStructureFiles(createSourceFiles(testCase)); | ||
| } | ||
|
|
||
| export function createCaseFiles(testCase: TestCase): Structure { | ||
| return { | ||
| "eslint.config.js": [createESLintConfigFile(testCase.rules), "typescript"], | ||
| "flint.config.ts": [createFlintConfigFile(testCase.rules), "typescript"], | ||
| src: createSourceFiles(testCase), | ||
| "tsconfig.json": [createStandardTSConfigFile(), "json"], | ||
| }; | ||
| } | ||
|
|
||
| function countStructureFiles(structure: Structure): number { | ||
| return Object.values(structure).reduce( | ||
| (total, value) => | ||
| total + (Array.isArray(value) ? 1 : countStructureFiles(value)), | ||
| 0, | ||
| ); | ||
| } | ||
|
|
||
| function createExampleDirectory(index: number): Structure { | ||
| return { | ||
| "index.ts": [createExampleFile(index), "typescript"], | ||
| ...(index > 2 && | ||
| Object.fromEntries( | ||
| range(1, index).map((i) => [ | ||
| `nested${i}`, | ||
| createExampleDirectory(i - 1), | ||
| ]), | ||
| )), | ||
| }; | ||
| } | ||
|
|
||
| function createExampleFile(index: number) { | ||
| return [ | ||
| index > 1 && | ||
| range(1, index) | ||
| .map((i) => `export * as nested${i} from "./nested${i}/index.ts";`) | ||
| .join("\n\t\t"), | ||
| ` | ||
| export async function example${index}(prefix: string) { | ||
| await Promise.resolve(); | ||
| return [prefix + "", ${index}]; | ||
| } | ||
| `, | ||
| ] | ||
| .filter(Boolean) | ||
| .join("\n\n"); | ||
| } | ||
|
|
||
| function createIndexFile(topLevelWidth: number) { | ||
| const indices = range(0, topLevelWidth); | ||
|
|
||
| return ` | ||
| import { example0 } from "./example0/index.ts"; | ||
|
|
||
| export async function root() { | ||
| // Lint report: ts/forInArrays | ||
| for (const i in await example0("")) {} | ||
|
|
||
| // No lint report | ||
| for (const i of await example0("")) {} | ||
| } | ||
|
|
||
| ${indices.map((index) => `export { example${index} } from "./example${index}/index.ts";`).join("\n\t\t")} | ||
| `; | ||
| } | ||
|
|
||
| function createSourceFiles(testCase: TestCase): Structure { | ||
| const topLevelWidth = Math.max(1, Math.floor(Math.log(testCase.files) * 1.7)); | ||
|
|
||
| return { | ||
| "index.ts": [createIndexFile(topLevelWidth), "typescript"], | ||
| ...Object.fromEntries( | ||
| Array.from({ length: topLevelWidth }, (_, index) => [ | ||
| `example${index}`, | ||
| createExampleDirectory(index), | ||
| ]), | ||
| ), | ||
| }; | ||
| } |
55 changes: 55 additions & 0 deletions
55
packages/performance-testing/src/creators/files/createESLintConfigFile.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
|
|
||
| import { createESLintConfigFile } from "./createESLintConfigFile.ts"; | ||
| import { comparedRules } from "./rules.ts"; | ||
|
|
||
| describe(createESLintConfigFile, () => { | ||
| it("imports no plugin packages when the only rule is preregistered", () => { | ||
| const actual = createESLintConfigFile(1); | ||
|
|
||
| expect(actual).toMatchInlineSnapshot(` | ||
| " | ||
| import { defineConfig, globalIgnores } from "eslint/config"; | ||
|
|
||
| import tseslint from "typescript-eslint"; | ||
|
|
||
| export default defineConfig( | ||
| globalIgnores(["node_modules", "*.config.*"]), | ||
| tseslint.configs.base, | ||
| { | ||
| files: ["src/**/*.ts"], | ||
| languageOptions: { | ||
| parserOptions: { | ||
| projectService: true, | ||
| }, | ||
| }, | ||
| plugins: { | ||
|
|
||
| }, | ||
| rules: { | ||
| "@typescript-eslint/no-for-in-array": "error" | ||
| }, | ||
| }, | ||
| ); | ||
| " | ||
| `); | ||
| }); | ||
|
|
||
| it("imports each plugin package once when given the many rules preset", () => { | ||
| const actual = createESLintConfigFile("many"); | ||
|
|
||
| expect(Array.from(actual.matchAll(/^import unicorn from/gm))).toHaveLength( | ||
| 1, | ||
| ); | ||
| }); | ||
|
|
||
| it("enables every compared rule when given the many rules preset", () => { | ||
| const actual = createESLintConfigFile("many"); | ||
|
|
||
| expect( | ||
| comparedRules.many.every(({ eslint }) => | ||
| actual.includes(`"${eslint}": "error"`), | ||
| ), | ||
| ).toBe(true); | ||
| }); | ||
| }); |
72 changes: 72 additions & 0 deletions
72
packages/performance-testing/src/creators/files/createESLintConfigFile.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import type { TestCaseRules } from "../../testCases.ts"; | ||
| import { comparedRules } from "./rules.ts"; | ||
|
|
||
| interface PluginPackage { | ||
| alias: string; | ||
| specifier: string; | ||
| } | ||
|
|
||
| const preregisteredPlugins = new Set(["@typescript-eslint"]); | ||
|
|
||
| const pluginPackages = new Map<string, PluginPackage>([ | ||
| ["import", { alias: "importPlugin", specifier: "eslint-plugin-import" }], | ||
| ["regexp", { alias: "regexp", specifier: "eslint-plugin-regexp" }], | ||
| ["unicorn", { alias: "unicorn", specifier: "eslint-plugin-unicorn" }], | ||
| ]); | ||
|
|
||
| export function createESLintConfigFile(rules: TestCaseRules): string { | ||
| const enabled = comparedRules[rules]; | ||
| const used = new Map<string, PluginPackage>(); | ||
|
|
||
| for (const { eslint } of enabled) { | ||
| const pluginName = getPluginName(eslint); | ||
|
|
||
| if (pluginName === undefined || preregisteredPlugins.has(pluginName)) { | ||
| continue; | ||
| } | ||
|
|
||
| const pluginPackage = pluginPackages.get(pluginName); | ||
|
|
||
| if (!pluginPackage) { | ||
| throw new Error(`No ESLint plugin package is known for ${eslint}.`); | ||
| } | ||
|
|
||
| used.set(pluginName, pluginPackage); | ||
| } | ||
|
|
||
| const plugins = Array.from(used, ([name, pluginPackage]) => ({ | ||
| ...pluginPackage, | ||
| name, | ||
| })).sort((a, b) => a.name.localeCompare(b.name)); | ||
|
|
||
| return ` | ||
| import { defineConfig, globalIgnores } from "eslint/config"; | ||
| ${plugins.map(({ alias, specifier }) => `import ${alias} from "${specifier}";`).join("\n")} | ||
| import tseslint from "typescript-eslint"; | ||
|
|
||
| export default defineConfig( | ||
| globalIgnores(["node_modules", "*.config.*"]), | ||
| tseslint.configs.base, | ||
| { | ||
| files: ["src/**/*.ts"], | ||
| languageOptions: { | ||
| parserOptions: { | ||
| projectService: true, | ||
| }, | ||
| }, | ||
| plugins: { | ||
| ${plugins.map(({ alias, name }) => `"${name}": ${alias}`).join(",\n")} | ||
| }, | ||
| rules: { | ||
| ${enabled.map(({ eslint }) => `"${eslint}": "error"`).join(",\n")} | ||
| }, | ||
| }, | ||
| ); | ||
| `; | ||
| } | ||
|
|
||
| function getPluginName(ruleName: string) { | ||
| const separator = ruleName.indexOf("/"); | ||
|
|
||
| return separator === -1 ? undefined : ruleName.slice(0, separator); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.