Skip to content

Commit 16d8f79

Browse files
Generate reports on private API usage
This uses the data gathered in f53e514 to generate some CSV reports about private API usage in the test suite. We’ll use these reports as a starting point for deciding how to remove this private API usage when reusing the test suite as a unified test suite for all our client libraries. Resolves ECO-4834.
1 parent f53e514 commit 16d8f79

File tree

15 files changed

+727
-4
lines changed

15 files changed

+727
-4
lines changed

.github/workflows/test-browser.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,15 @@ jobs:
3030
- env:
3131
PLAYWRIGHT_BROWSER: ${{ matrix.browser }}
3232
run: npm run test:playwright
33+
- name: Generate private API usage reports
34+
run: npm run process-private-api-data private-api-usage/*.json
3335
- name: Save private API usage data
3436
uses: actions/upload-artifact@v4
3537
with:
3638
name: private-api-usage-${{ matrix.browser }}
37-
path: private-api-usage
39+
path: |
40+
private-api-usage
41+
private-api-usage-reports
3842
- name: Upload test results
3943
if: always()
4044
uses: ably/test-observability-action@v1

.github/workflows/test-node.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@ jobs:
2828
- run: npm run test:node
2929
env:
3030
CI: true
31+
- name: Generate private API usage reports
32+
run: npm run process-private-api-data private-api-usage/*.json
3133
- name: Save private API usage data
3234
uses: actions/upload-artifact@v4
3335
with:
3436
name: private-api-usage-${{ matrix.node-version }}
35-
path: private-api-usage
37+
path: |
38+
private-api-usage
39+
private-api-usage-reports
3640
- name: Upload test results
3741
if: always()
3842
uses: ably/test-observability-action@v1

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ react/
99
typedoc/generated/
1010
junit/
1111
private-api-usage/
12+
private-api-usage-reports/
1213
test/support/mocha_junit_reporter/build/

package-lock.json

Lines changed: 76 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"chai": "^4.2.0",
8080
"cli-table": "^0.3.11",
8181
"cors": "^2.8.5",
82+
"csv": "^6.3.9",
8283
"dox": "^1.0.0",
8384
"esbuild": "^0.18.10",
8485
"esbuild-plugin-umd-wrapper": "ably-forks/esbuild-plugin-umd-wrapper#1.0.7-optional-amd-named-module",
@@ -155,11 +156,12 @@
155156
"lint": "eslint .",
156157
"lint:fix": "eslint --fix .",
157158
"prepare": "npm run build",
158-
"format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modular.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/**/*.md grunt",
159-
"format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modular.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/**/*.md grunt",
159+
"format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modular.d.ts webpack.config.js Gruntfile.js scripts/**/*.[jt]s docs/**/*.md grunt",
160+
"format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modular.d.ts webpack.config.js Gruntfile.js scripts/**/*.[jt]s docs/**/*.md grunt",
160161
"sourcemap": "source-map-explorer build/ably.min.js",
161162
"modulereport": "tsc --noEmit --esModuleInterop scripts/moduleReport.ts && esr scripts/moduleReport.ts",
162163
"speccoveragereport": "tsc --noEmit --esModuleInterop --target ES2017 --moduleResolution node scripts/specCoverageReport.ts && esr scripts/specCoverageReport.ts",
164+
"process-private-api-data": "tsc --noEmit --esModuleInterop --strictNullChecks scripts/processPrivateApiData/run.ts && esr scripts/processPrivateApiData/run.ts",
163165
"docs": "typedoc"
164166
}
165167
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
export type TestPrivateApiContextDto = {
2+
type: 'test';
3+
title: string;
4+
/**
5+
* null means that either the test isn’t parameterised or that this usage is unique to the specific parameter
6+
*/
7+
parameterisedTestTitle: string | null;
8+
helperStack: string[];
9+
file: string;
10+
suite: string[];
11+
};
12+
13+
export type HookPrivateApiContextDto = {
14+
type: 'hook';
15+
title: string;
16+
helperStack: string[];
17+
file: string;
18+
suite: string[];
19+
};
20+
21+
export type RootHookPrivateApiContextDto = {
22+
type: 'hook';
23+
title: string;
24+
helperStack: string[];
25+
file: null;
26+
suite: null;
27+
};
28+
29+
export type TestDefinitionPrivateApiContextDto = {
30+
type: 'definition';
31+
label: string;
32+
helperStack: string[];
33+
file: string;
34+
suite: string[];
35+
};
36+
37+
export type PrivateApiContextDto =
38+
| TestPrivateApiContextDto
39+
| HookPrivateApiContextDto
40+
| RootHookPrivateApiContextDto
41+
| TestDefinitionPrivateApiContextDto;
42+
43+
export type PrivateApiUsageDto = {
44+
context: PrivateApiContextDto;
45+
privateAPIIdentifier: string;
46+
};
47+
48+
export type TestStartRecord = {
49+
context: TestPrivateApiContextDto;
50+
privateAPIIdentifier: null;
51+
};
52+
53+
export type Record = PrivateApiUsageDto | TestStartRecord;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { PrivateApiUsageDto } from './dto';
2+
3+
type ExclusionRule = {
4+
privateAPIIdentifier: string;
5+
// i.e. only ignore when called from within this helper
6+
helper?: string;
7+
};
8+
9+
/**
10+
* This exclusions mechanism is not currently being used on `main`, but I will use it on a separate unified test suite branch in order to exclude some private API usage that can currently be disregarded in the context of the unified test suite.
11+
*/
12+
export function applyingExclusions(usageDtos: PrivateApiUsageDto[]) {
13+
const exclusionRules: ExclusionRule[] = [];
14+
15+
return usageDtos.filter(
16+
(usageDto) =>
17+
!exclusionRules.some(
18+
(exclusionRule) =>
19+
exclusionRule.privateAPIIdentifier === usageDto.privateAPIIdentifier &&
20+
(!('helper' in exclusionRule) || usageDto.context.helperStack.includes(exclusionRule.helper!)),
21+
),
22+
);
23+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { PrivateApiUsageDto } from './dto';
2+
3+
export type Group<Key, Value> = {
4+
key: Key;
5+
values: Value[];
6+
};
7+
8+
export function grouped<Key, Value>(
9+
values: Value[],
10+
keyForValue: (value: Value) => Key,
11+
areKeysEqual: (key1: Key, key2: Key) => boolean,
12+
) {
13+
const result: Group<Key, Value>[] = [];
14+
15+
for (const value of values) {
16+
const key = keyForValue(value);
17+
18+
let existingGroup = result.find((group) => areKeysEqual(group.key, key));
19+
20+
if (existingGroup === undefined) {
21+
existingGroup = { key, values: [] };
22+
result.push(existingGroup);
23+
}
24+
25+
existingGroup.values.push(value);
26+
}
27+
28+
return result;
29+
}
30+
31+
/**
32+
* Makes sure that each private API is only listed once in a given context.
33+
*/
34+
function dedupeUsages<Key>(contextGroups: Group<Key, PrivateApiUsageDto>[]) {
35+
for (const contextGroup of contextGroups) {
36+
const newUsages: typeof contextGroup.values = [];
37+
38+
for (const usage of contextGroup.values) {
39+
const existing = newUsages.find((otherUsage) => otherUsage.privateAPIIdentifier === usage.privateAPIIdentifier);
40+
if (existing === undefined) {
41+
newUsages.push(usage);
42+
}
43+
}
44+
45+
contextGroup.values = newUsages;
46+
}
47+
}
48+
49+
export function groupedAndDeduped<Key>(
50+
usages: PrivateApiUsageDto[],
51+
keyForUsage: (usage: PrivateApiUsageDto) => Key,
52+
areKeysEqual: (key1: Key, key2: Key) => boolean,
53+
) {
54+
const result = grouped(usages, keyForUsage, areKeysEqual);
55+
dedupeUsages(result);
56+
return result;
57+
}
58+
59+
/**
60+
* Return value is sorted in decreasing order of usage of a given private API identifer
61+
*/
62+
export function groupedAndSortedByPrivateAPIIdentifier<Key>(
63+
groupedByKey: Group<Key, PrivateApiUsageDto>[],
64+
): Group<string, Key>[] {
65+
const flattened = groupedByKey.flatMap((group) => group.values.map((value) => ({ key: group.key, value })));
66+
67+
const groupedByPrivateAPIIdentifier = grouped(
68+
flattened,
69+
(value) => value.value.privateAPIIdentifier,
70+
(id1, id2) => id1 === id2,
71+
).map((group) => ({ key: group.key, values: group.values.map((value) => value.key) }));
72+
73+
groupedByPrivateAPIIdentifier.sort((group1, group2) => group2.values.length - group1.values.length);
74+
75+
return groupedByPrivateAPIIdentifier;
76+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { readFileSync } from 'fs';
2+
import { applyingExclusions } from './exclusions';
3+
import { splittingRecords, stripFilePrefix } from './utils';
4+
import { Record } from './dto';
5+
6+
export function load(jsonFilePath: string) {
7+
let records = JSON.parse(readFileSync(jsonFilePath).toString('utf-8')) as Record[];
8+
9+
stripFilePrefix(records);
10+
11+
let { usageDtos, testStartRecords } = splittingRecords(records);
12+
13+
usageDtos = applyingExclusions(usageDtos);
14+
15+
return { usageDtos, testStartRecords };
16+
}

0 commit comments

Comments
 (0)