apify-test-tools 0.10.2-beta.19 → 0.10.2-beta.20
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/.github/cli/apify-test-tools.mjs +114 -114
- package/.github/scripts/run-with-apify-tokens.mjs +6 -6
- package/.github/workflows/public_platform-tests-claude-investigate-and-fix.yaml +1 -1
- package/.github/workflows/public_platform-tests.yaml +6 -8
- package/.github/workflows/public_pr-build-test.yaml +11 -11
- package/.github/workflows/public_push-build-latest.yaml +1 -0
- package/CHANGELOG.md +1 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +10 -6
- package/bin/affected-actors.ts +20 -7
- package/bin/diff-changes.ts +40 -16
- package/bin/main.ts +12 -19
- package/bin/run-tests.ts +29 -4
- package/bin/test-files-glob.ts +126 -0
- package/bin/utils/config/load-config.ts +4 -1
- package/bin/utils/config/parser.ts +24 -1
- package/dist/bin/affected-actors.d.ts +13 -4
- package/dist/bin/affected-actors.d.ts.map +1 -1
- package/dist/bin/affected-actors.js +11 -4
- package/dist/bin/affected-actors.js.map +1 -1
- package/dist/bin/diff-changes.d.ts +12 -3
- package/dist/bin/diff-changes.d.ts.map +1 -1
- package/dist/bin/diff-changes.js +25 -12
- package/dist/bin/diff-changes.js.map +1 -1
- package/dist/bin/main.js +11 -17
- package/dist/bin/main.js.map +1 -1
- package/dist/bin/run-tests.d.ts +3 -3
- package/dist/bin/run-tests.d.ts.map +1 -1
- package/dist/bin/run-tests.js +24 -2
- package/dist/bin/run-tests.js.map +1 -1
- package/dist/bin/test-files-glob.d.ts +18 -0
- package/dist/bin/test-files-glob.d.ts.map +1 -0
- package/dist/bin/test-files-glob.js +108 -0
- package/dist/bin/test-files-glob.js.map +1 -0
- package/dist/bin/utils/config/load-config.d.ts +3 -0
- package/dist/bin/utils/config/load-config.d.ts.map +1 -1
- package/dist/bin/utils/config/load-config.js +3 -1
- package/dist/bin/utils/config/load-config.js.map +1 -1
- package/dist/bin/utils/config/parser.d.ts +9 -0
- package/dist/bin/utils/config/parser.d.ts.map +1 -1
- package/dist/bin/utils/config/parser.js +13 -1
- package/dist/bin/utils/config/parser.js.map +1 -1
- package/dist/test/bundle/cli-bundle.test.js +1 -1
- package/dist/test/bundle/cli-bundle.test.js.map +1 -1
- package/dist/test/e2e/cli-bundle.e2e.test.js +1 -1
- package/dist/test/e2e/cli-bundle.e2e.test.js.map +1 -1
- package/dist/test/unit/bin/actor-filtering.test.js +7 -0
- package/dist/test/unit/bin/actor-filtering.test.js.map +1 -1
- package/dist/test/unit/bin/affected-actors-cli.test.js +59 -2
- package/dist/test/unit/bin/affected-actors-cli.test.js.map +1 -1
- package/dist/test/unit/bin/affected-actors.test.js +18 -6
- package/dist/test/unit/bin/affected-actors.test.js.map +1 -1
- package/dist/test/unit/bin/diff-changes.test.js +44 -19
- package/dist/test/unit/bin/diff-changes.test.js.map +1 -1
- package/dist/test/unit/bin/test-cli.test.js +34 -3
- package/dist/test/unit/bin/test-cli.test.js.map +1 -1
- package/dist/test/unit/bin/test-files-glob.test.d.ts +2 -0
- package/dist/test/unit/bin/test-files-glob.test.d.ts.map +1 -0
- package/dist/test/unit/bin/test-files-glob.test.js +102 -0
- package/dist/test/unit/bin/test-files-glob.test.js.map +1 -0
- package/dist/test/unit/bin/utils/config/parser.test.js +16 -1
- package/dist/test/unit/bin/utils/config/parser.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/cli.md +46 -29
- package/docs/workflows.md +53 -20
- package/package.json +1 -1
- package/test/bundle/cli-bundle.test.ts +1 -1
- package/test/e2e/cli-bundle.e2e.test.ts +9 -5
- package/test/unit/bin/actor-filtering.test.ts +8 -0
- package/test/unit/bin/affected-actors-cli.test.ts +73 -3
- package/test/unit/bin/affected-actors.test.ts +49 -10
- package/test/unit/bin/diff-changes.test.ts +48 -19
- package/test/unit/bin/test-cli.test.ts +41 -3
- package/test/unit/bin/test-files-glob.test.ts +118 -0
- package/test/unit/bin/utils/config/parser.test.ts +24 -1
|
@@ -22,7 +22,7 @@ name: 'Platform tests: Claude failure investigation and fix'
|
|
|
22
22
|
# platformTestsCore:
|
|
23
23
|
# uses: apify/apify-test-tools/.github/workflows/public_platform-tests.yaml@workflows-v0
|
|
24
24
|
# with:
|
|
25
|
-
#
|
|
25
|
+
# test-files-glob: 'test/platform/hourly/**'
|
|
26
26
|
# report-slack-channel: '#notif-my-repo'
|
|
27
27
|
# secrets:
|
|
28
28
|
# TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
|
|
@@ -3,15 +3,10 @@ name: Platform tests
|
|
|
3
3
|
on:
|
|
4
4
|
workflow_call:
|
|
5
5
|
inputs:
|
|
6
|
-
#
|
|
7
|
-
subtest:
|
|
8
|
-
type: string
|
|
9
|
-
default: ''
|
|
10
|
-
required: false
|
|
11
|
-
# Specify what tests you want to run, relative to `test/platform`
|
|
6
|
+
# Glob of the test files to run, from the repo root, e.g. `test/platform/{hourly,hourly-without-pr}/**`
|
|
12
7
|
test-files-glob:
|
|
13
8
|
type: string
|
|
14
|
-
default: ''
|
|
9
|
+
default: 'test/platform/**'
|
|
15
10
|
required: false
|
|
16
11
|
# Slack channel for failed tests. Empty skips Slack.
|
|
17
12
|
report-slack-channel:
|
|
@@ -64,9 +59,12 @@ jobs:
|
|
|
64
59
|
shell: bash
|
|
65
60
|
env:
|
|
66
61
|
TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
|
|
62
|
+
# Passed through the environment and quoted, so the shell doesn't expand the glob itself
|
|
63
|
+
# An empty input, e.g. from an expression that picks the glob, runs everything, like the default
|
|
64
|
+
TEST_FILES_GLOB: ${{ inputs.test-files-glob || 'test/platform/**' }}
|
|
67
65
|
run: |
|
|
68
66
|
apify-test-tools get-actor-configs \
|
|
69
|
-
| apify-test-tools test
|
|
67
|
+
| apify-test-tools test --test-files-glob "$TEST_FILES_GLOB" \
|
|
70
68
|
--reporter github-actions --vitest-output-file ./test-output.json > test-report.json
|
|
71
69
|
|
|
72
70
|
# Posts only when the tests failed, but checks the Slack token on every run that has a channel. Runs
|
|
@@ -7,10 +7,11 @@ on:
|
|
|
7
7
|
type: boolean
|
|
8
8
|
default: false
|
|
9
9
|
required: false
|
|
10
|
-
#
|
|
10
|
+
# Glob of the test files to run, from the repo root, e.g. `test/platform/{daily,hourly}/**`. Which changed
|
|
11
|
+
# files count as test changes is `platformTestsGlob` in apify-test-tools.config.json, not this.
|
|
11
12
|
test-files-glob:
|
|
12
13
|
type: string
|
|
13
|
-
default: ''
|
|
14
|
+
default: 'test/platform/**'
|
|
14
15
|
required: false
|
|
15
16
|
# Fail the build unless the env var names of version 0.99 match the default version's
|
|
16
17
|
require-env-vars-in-sync:
|
|
@@ -86,16 +87,12 @@ jobs:
|
|
|
86
87
|
run: |
|
|
87
88
|
base_commit=$(cat base_commit.txt 2>/dev/null || echo "")
|
|
88
89
|
echo "Last validated (base) commit to be used for build & test: $base_commit"
|
|
89
|
-
args=(
|
|
90
|
-
--base "origin/$BASE_REF"
|
|
91
|
-
# TODO: Temporary hotfix until build glob matching aligns with Vitest's test selection glob.
|
|
92
|
-
--test-files-glob 'test/platform/**'
|
|
93
|
-
)
|
|
90
|
+
args=(--base "origin/$BASE_REF")
|
|
94
91
|
[ -n "$base_commit" ] && args+=(--since "$base_commit")
|
|
95
92
|
apify-test-tools get-affected-actors "${args[@]}" > affected.json
|
|
96
93
|
|
|
97
|
-
# Cosmetic-only changes need no tests, so only the
|
|
98
|
-
# test version 0.99 that users never run.
|
|
94
|
+
# Cosmetic-only changes need no tests, so only the Actors with functional or platform test changes are
|
|
95
|
+
# built, into the test version 0.99 that users never run.
|
|
99
96
|
# run-with-apify-tokens.mjs reads the token names from in apify-test-tools.config.json
|
|
100
97
|
# and hands the build those secrets and nothing else, so the tokens stay in this one command instead of the whole job.
|
|
101
98
|
# The library never sees secrets it doesn't need
|
|
@@ -107,7 +104,7 @@ jobs:
|
|
|
107
104
|
SCRIPTS_PATH: ${{ steps.setup.outputs.scripts-path }}
|
|
108
105
|
REQUIRE_ENV_VARS_IN_SYNC: ${{ inputs.require-env-vars-in-sync }}
|
|
109
106
|
run: |
|
|
110
|
-
jq '{ actors: .functionallyChangedActors }' affected.json \
|
|
107
|
+
jq '{ actors: (.functionallyChangedActors + .testChangedActors) }' affected.json \
|
|
111
108
|
| node "$SCRIPTS_PATH/run-with-apify-tokens.mjs" apify-test-tools build --version-number 0.99 \
|
|
112
109
|
--require-env-vars-in-sync="$REQUIRE_ENV_VARS_IN_SYNC" \
|
|
113
110
|
| tee builds.json
|
|
@@ -116,9 +113,12 @@ jobs:
|
|
|
116
113
|
- name: Test
|
|
117
114
|
env:
|
|
118
115
|
TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
|
|
116
|
+
# Passed through the environment and quoted, so the shell doesn't expand the glob itself
|
|
117
|
+
# An empty input, e.g. from an expression that picks the glob, runs everything, like the default
|
|
118
|
+
TEST_FILES_GLOB: ${{ inputs.test-files-glob || 'test/platform/**' }}
|
|
119
119
|
# The github-actions reporter annotates the PR with the failing tests
|
|
120
120
|
run: |
|
|
121
|
-
apify-test-tools test
|
|
121
|
+
apify-test-tools test --test-files-glob "$TEST_FILES_GLOB" --reporter github-actions \
|
|
122
122
|
< builds.json > test-report.json
|
|
123
123
|
|
|
124
124
|
# NOTE: This is an optimization that if we did functional changes and later only cosmetic changes (e.g. dev readme), we will compare changes files only for the last commit. This must run after tests because we want to cache the latest commit only if the tests are successful
|
|
@@ -52,6 +52,7 @@ jobs:
|
|
|
52
52
|
|
|
53
53
|
# A release ships cosmetic changes too (README, input schema descriptions), so both lists are built,
|
|
54
54
|
# into each Actor's default version: the one its default build tag points to, which users run.
|
|
55
|
+
# testChangedActors is left out: a change to platform tests alone needs no release.
|
|
55
56
|
# run-with-apify-tokens.mjs reads the token names from in apify-test-tools.config.json
|
|
56
57
|
# and hands the build those secrets and nothing else, so the tokens stay in this one command instead of the whole job.
|
|
57
58
|
# The library never sees secrets it doesn't need
|
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file.
|
|
|
18
18
|
- Add impact & priority assessment to claude investigator ([#216](https://github.com/apify/apify-test-tools/pull/216)) ([fd16622](https://github.com/apify/apify-test-tools/commit/fd16622b910ec6e9b5515ea0bd26b0a41c22bd23)) by [@foxt451](https://github.com/foxt451), closes [#208](https://github.com/apify/apify-test-tools/issues/208)
|
|
19
19
|
- Stale issue/pr workflow ([#236](https://github.com/apify/apify-test-tools/pull/236)) ([aff1fa7](https://github.com/apify/apify-test-tools/commit/aff1fa778adb846a60fdacdd26aa13a71729613d)) by [@JuanGalilea](https://github.com/JuanGalilea), closes [#35](https://github.com/apify/apify-test-tools/issues/35)
|
|
20
20
|
- **workflows:** [**breaking**] Skip Slack when report-slack-channel is empty ([#238](https://github.com/apify/apify-test-tools/pull/238)) ([9a15b56](https://github.com/apify/apify-test-tools/commit/9a15b56de9ca42fb5ea753dc478116f9cd9c2c94)) by [@metalwarrior665](https://github.com/metalwarrior665)
|
|
21
|
+
- [**breaking**] Repo-root test-files-glob, platformTestsGlob in the config, and drop subtest ([#239](https://github.com/apify/apify-test-tools/pull/239)) ([640bc97](https://github.com/apify/apify-test-tools/commit/640bc97d9f16cfa07501bc4eae400cd2b1bd0c9c)) by [@metalwarrior665](https://github.com/metalwarrior665), closes [#192](https://github.com/apify/apify-test-tools/issues/192)
|
|
21
22
|
|
|
22
23
|
### 🐛 Bug Fixes
|
|
23
24
|
|
package/CONTRIBUTING.md
CHANGED
|
@@ -172,7 +172,7 @@ brought:
|
|
|
172
172
|
env:
|
|
173
173
|
WORKFLOW_SECRETS: ${{ toJSON(secrets) }}
|
|
174
174
|
run: |
|
|
175
|
-
jq '{ actors: .functionallyChangedActors }' affected.json \
|
|
175
|
+
jq '{ actors: (.functionallyChangedActors + .testChangedActors) }' affected.json \
|
|
176
176
|
| node "${{ steps.setup.outputs.scripts-path }}/run-with-apify-tokens.mjs" \
|
|
177
177
|
apify-test-tools build --version-number 0.99
|
|
178
178
|
```
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Build, test and release Apify Actors from a Git repository or local source code.
|
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
npx apify-test-tools get-affected-actors --base origin/master \
|
|
19
|
-
| jq '{ actors: .functionallyChangedActors }' \
|
|
19
|
+
| jq '{ actors: (.functionallyChangedActors + .testChangedActors) }' \
|
|
20
20
|
| npx apify-test-tools build --version-number 0.99
|
|
21
21
|
```
|
|
22
22
|
|
|
@@ -180,14 +180,15 @@ group needs a `tokenEnvVar` and at least one Actor. Add another group for Actors
|
|
|
180
180
|
Actors owned by another account.
|
|
181
181
|
|
|
182
182
|
The orchestrator sets `overrideActorContext` because its tests live outside its own folder: without it, a change to
|
|
183
|
-
only `test/platform/orchestrator` wouldn't
|
|
184
|
-
|
|
183
|
+
only `test/platform/orchestrator` wouldn't test it. The other two already have the whole repo, tests included, as
|
|
184
|
+
their context.
|
|
185
185
|
|
|
186
186
|
| Field | Where | Required | Description |
|
|
187
187
|
| ---------------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
188
188
|
| `folder` | Actor | yes | Relative path from repo root to the Actor's own project directory — the folder that directly contains `.actor/actor.json` (i.e. `<folder>/.actor/actor.json`), the Actor's README/CHANGELOG, and its source. Use `"."` for a single-Actor repo where `.actor/` is at the root. |
|
|
189
189
|
| `actorFullName` | Actor | yes | Full Actor identifier in `owner/name` format (e.g. `"apify/web-scraper"`). This is the source of truth for the Actor name — the `name` field in `actor.json` is not used. |
|
|
190
190
|
| `tokenEnvVar` | group or Actor | yes | Name of the environment variable holding the Apify API token that builds the group's Actors. No fallback — if the env var is not set at build time, the build fails. |
|
|
191
|
+
| `platformTestsGlob` | top level | no | Glob from the repo root of the platform tests and their helpers. Default `test/platform/**`. A changed file that matches it is a test change, also when `.dockerignore` lists it: the PR workflow builds and tests the Actor whose context holds it, and a release skips it. See [Change detection](docs/cli.md#change-detection). |
|
|
191
192
|
| `overrideActorContext` | group or Actor | no | Array of paths (relative to repo root) that define which files are relevant to the Actor. When set, replaces the `dockerContextDir` from `actor.json` for change detection. Useful when an Actor depends on shared packages outside its Docker build context. Entries must not be prefixes of one another (e.g. `["", "code"]` or `["actors", "actors/foo"]` are rejected). The Actor's own `folder` is always part of its context — if none of the listed entries reach it, it's added automatically. |
|
|
192
193
|
|
|
193
194
|
A value set on an Actor replaces the group's for that Actor only. Values are replaced, not merged: if the `myteam`
|
|
@@ -259,7 +260,8 @@ jobs:
|
|
|
259
260
|
|
|
260
261
|
### Scheduled tests (optional)
|
|
261
262
|
|
|
262
|
-
How often to run which tests is up to you.
|
|
263
|
+
How often to run which tests is up to you. `test-files-glob` is a glob from the repo root, see
|
|
264
|
+
[Splitting the tests](docs/workflows.md#splitting-the-tests). For example, you can run the daily tests once a day:
|
|
263
265
|
|
|
264
266
|
```yaml
|
|
265
267
|
name: Platform tests - Daily
|
|
@@ -273,12 +275,14 @@ on:
|
|
|
273
275
|
jobs:
|
|
274
276
|
platformTestsDaily:
|
|
275
277
|
uses: apify/apify-test-tools/.github/workflows/public_platform-tests.yaml@workflows-v0
|
|
278
|
+
with:
|
|
279
|
+
test-files-glob: 'test/platform/{daily,daily-without-pr}/**'
|
|
276
280
|
secrets:
|
|
277
281
|
TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
|
|
278
282
|
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
|
279
283
|
```
|
|
280
284
|
|
|
281
|
-
and a few critical tests
|
|
285
|
+
and a few critical tests every hour:
|
|
282
286
|
|
|
283
287
|
```yaml
|
|
284
288
|
name: Platform tests - Hourly
|
|
@@ -293,7 +297,7 @@ jobs:
|
|
|
293
297
|
platformTestsHourly:
|
|
294
298
|
uses: apify/apify-test-tools/.github/workflows/public_platform-tests.yaml@workflows-v0
|
|
295
299
|
with:
|
|
296
|
-
test-files-glob: hourly
|
|
300
|
+
test-files-glob: 'test/platform/{hourly,hourly-without-pr}/**'
|
|
297
301
|
secrets:
|
|
298
302
|
TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
|
|
299
303
|
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
package/bin/affected-actors.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from './git.js';
|
|
11
11
|
import { logger } from './logger.js';
|
|
12
12
|
import type { ActorConfig, Commit, GitRange } from './types.js';
|
|
13
|
-
import { readConfigFile } from './utils/config/load-config.js';
|
|
13
|
+
import { readConfigFile, readRepoSettings } from './utils/config/load-config.js';
|
|
14
14
|
|
|
15
15
|
// The range always ends at the checked-out commit: check out what you want to inspect
|
|
16
16
|
const HEAD = 'HEAD';
|
|
@@ -28,8 +28,6 @@ interface AffectedActorsOptions {
|
|
|
28
28
|
* counts, and it must be an ancestor of HEAD (e.g. the last release).
|
|
29
29
|
*/
|
|
30
30
|
since?: string;
|
|
31
|
-
/** Glob relative to the repo root. Matching files count as changes even when .dockerignore lists them. */
|
|
32
|
-
testFilesGlob?: string;
|
|
33
31
|
actors: string[];
|
|
34
32
|
ignore: string[];
|
|
35
33
|
}
|
|
@@ -47,8 +45,17 @@ interface AffectedActors {
|
|
|
47
45
|
* stays the source of truth for the rest.
|
|
48
46
|
*/
|
|
49
47
|
functionallyChangedActors: { actorFullName: string }[];
|
|
50
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Actors with cosmetic changes (README, CHANGELOG, cosmetic .actor JSON) and no functional one. They only need a
|
|
50
|
+
* release build.
|
|
51
|
+
*/
|
|
51
52
|
cosmeticallyChangedActors: { actorFullName: string }[];
|
|
53
|
+
/**
|
|
54
|
+
* Actors whose platform tests (`platformTestsGlob` in the config) changed in their context, whatever else
|
|
55
|
+
* changed. Their tests need to run, but a test change alone needs no release. An Actor can also be in one of the
|
|
56
|
+
* lists above.
|
|
57
|
+
*/
|
|
58
|
+
testChangedActors: { actorFullName: string }[];
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
const names = (configs: ActorConfig[]) => configs.map(({ actorFullName }) => ({ actorFullName }));
|
|
@@ -114,7 +121,12 @@ const findValidatedSha = async (since: string, headSha: string): Promise<string
|
|
|
114
121
|
* base's change, which base already validated.
|
|
115
122
|
*/
|
|
116
123
|
const intersectChangedInBranchVsSince = (branch: ChangedActors, sinceValidated: ChangedActors): ChangedActors => {
|
|
117
|
-
const narrowed: ChangedActors = {
|
|
124
|
+
const narrowed: ChangedActors = {
|
|
125
|
+
functional: [],
|
|
126
|
+
cosmetic: [],
|
|
127
|
+
// The same rule for tests: the branch changed them, and they changed again since they were last validated
|
|
128
|
+
test: branch.test.filter((actor) => sinceValidated.test.includes(actor)),
|
|
129
|
+
};
|
|
118
130
|
// All checks are done for each Actor separately, that's cleanest and most accurate
|
|
119
131
|
for (const actor of [...branch.functional, ...branch.cosmetic]) {
|
|
120
132
|
// base...HEAD
|
|
@@ -145,7 +157,6 @@ const intersectChangedInBranchVsSince = (branch: ChangedActors, sinceValidated:
|
|
|
145
157
|
export const getAffectedActors = async ({
|
|
146
158
|
base,
|
|
147
159
|
since: sinceOption,
|
|
148
|
-
testFilesGlob,
|
|
149
160
|
actors,
|
|
150
161
|
ignore,
|
|
151
162
|
}: AffectedActorsOptions): Promise<AffectedActors> => {
|
|
@@ -156,6 +167,7 @@ export const getAffectedActors = async ({
|
|
|
156
167
|
}
|
|
157
168
|
|
|
158
169
|
const actorConfigs = await readConfigFile({ actors, ignore });
|
|
170
|
+
const { platformTestsGlob } = await readRepoSettings();
|
|
159
171
|
const getChangedInRange = async (range: GitRange) => {
|
|
160
172
|
const changedFiles = await getChangedFilesBetween(range.fromRef, range.toRef);
|
|
161
173
|
return {
|
|
@@ -164,7 +176,7 @@ export const getAffectedActors = async ({
|
|
|
164
176
|
filepathsChanged: changedFiles,
|
|
165
177
|
actorConfigs,
|
|
166
178
|
range,
|
|
167
|
-
|
|
179
|
+
platformTestsGlob,
|
|
168
180
|
}),
|
|
169
181
|
};
|
|
170
182
|
};
|
|
@@ -194,5 +206,6 @@ export const getAffectedActors = async ({
|
|
|
194
206
|
changelog: await getChangelogAdditions(rangeVsBase.fromRef, rangeVsBase.toRef, changedFiles),
|
|
195
207
|
functionallyChangedActors: names(changedActors.functional),
|
|
196
208
|
cosmeticallyChangedActors: names(changedActors.cosmetic),
|
|
209
|
+
testChangedActors: names(changedActors.test),
|
|
197
210
|
};
|
|
198
211
|
};
|
package/bin/diff-changes.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import picomatch from 'picomatch';
|
|
2
|
-
|
|
3
1
|
import { isCosmeticOnlyJsonSchemaChange } from './diff-json-schema.js';
|
|
4
2
|
import { type DockerIgnoreMatcher, loadDockerIgnore } from './dockerignore.js';
|
|
5
3
|
import { logger } from './logger.js';
|
|
4
|
+
import { createTestFilesMatcher } from './test-files-glob.js';
|
|
6
5
|
import type { ActorConfig, GitRange } from './types.js';
|
|
7
6
|
import { type RelativeDir, RelativeFile } from './utils/path/repo-relative.js';
|
|
8
7
|
|
|
@@ -11,8 +10,11 @@ interface ShouldBuildAndTestOptions {
|
|
|
11
10
|
actorConfigs: ActorConfig[];
|
|
12
11
|
/** The range the files changed in, to compare `.actor` JSON files across. */
|
|
13
12
|
range: GitRange;
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Glob of the platform test files, from the repo root (`platformTestsGlob` in the config). A change to a matching
|
|
15
|
+
* file is a test change, even when .dockerignore lists it.
|
|
16
|
+
*/
|
|
17
|
+
platformTestsGlob?: string;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
const IGNORED_TOP_LEVEL_FILES = [
|
|
@@ -34,6 +36,7 @@ type FileChangeForActor =
|
|
|
34
36
|
| { impact: 'ignored' }
|
|
35
37
|
| { impact: 'outside-context' }
|
|
36
38
|
| { impact: 'cosmetic'; semanticallyVerified: boolean }
|
|
39
|
+
| { impact: 'test' }
|
|
37
40
|
| { impact: 'functional' };
|
|
38
41
|
|
|
39
42
|
/**
|
|
@@ -44,11 +47,13 @@ type FileChangeForActor =
|
|
|
44
47
|
* not one per actor, so it applies to every actor regardless of context/folder.
|
|
45
48
|
* 2. Context matching (actorConfig.contextPaths) → outside-context if no match
|
|
46
49
|
* 3. Hardcoded ignore list, checked against the path hoisted relative to the matched context entry → ignored
|
|
47
|
-
* 4.
|
|
48
|
-
*
|
|
49
|
-
* 5.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
50
|
+
* 4. Platform test files (`platformTestsGlob`) → test, even when .dockerignore lists them. A test change needs the
|
|
51
|
+
* tests to run, but no release.
|
|
52
|
+
* 5. .dockerignore filtering (patterns relative to dockerContextDir), skipped for the actor's own `.actor/`
|
|
53
|
+
* dir → ignored if matched
|
|
54
|
+
* 6. README.md by filename → cosmetic if inside the actor's own folder, otherwise ignored
|
|
55
|
+
* 7. .json inside the actor's own `.actor/` dir with only cosmetic schema diffs → cosmetic (semantically verified)
|
|
56
|
+
* 8. Everything else → functional
|
|
52
57
|
*/
|
|
53
58
|
const classifyFileChange = async (
|
|
54
59
|
changedFile: RelativeFile,
|
|
@@ -75,13 +80,17 @@ const classifyFileChange = async (
|
|
|
75
80
|
return { impact: 'ignored' };
|
|
76
81
|
}
|
|
77
82
|
|
|
83
|
+
// Test files are often in .dockerignore, but a change to them must still run the tests
|
|
84
|
+
if (isTestFile(changedFile.path)) {
|
|
85
|
+
return { impact: 'test' };
|
|
86
|
+
}
|
|
87
|
+
|
|
78
88
|
const isUnderActorDotDir = actorConfig.actorJson.file.parent.contains(changedFile);
|
|
79
89
|
|
|
80
90
|
// .actor/ can legitimately be listed in .dockerignore (the Apify platform evaluates it before
|
|
81
91
|
// the Docker build, so excluding it from the build context is a valid caching optimization) —
|
|
82
92
|
// that shouldn't cause changes to .actor/ itself to be ignored here.
|
|
83
|
-
|
|
84
|
-
if (!isUnderActorDotDir && !isTestFile(changedFile.path) && dockerIgnoreMatcher(changedFile.path)) {
|
|
93
|
+
if (!isUnderActorDotDir && dockerIgnoreMatcher(changedFile.path)) {
|
|
85
94
|
return { impact: 'ignored' };
|
|
86
95
|
}
|
|
87
96
|
|
|
@@ -113,6 +122,8 @@ type ActorChangeEntry = {
|
|
|
113
122
|
actorConfig: ActorConfig;
|
|
114
123
|
files: string[];
|
|
115
124
|
hasFunctionalChange: boolean;
|
|
125
|
+
hasCosmeticChange: boolean;
|
|
126
|
+
hasTestChange: boolean;
|
|
116
127
|
};
|
|
117
128
|
|
|
118
129
|
type ChangeGroup = { actors: string[]; files: string[] };
|
|
@@ -168,8 +179,13 @@ const logChangeGroups = (groups: ChangeGroup[]): void => {
|
|
|
168
179
|
export interface ChangedActors {
|
|
169
180
|
/** Actors with at least one functional change. They need a new build and tests. */
|
|
170
181
|
functional: ActorConfig[];
|
|
171
|
-
/** Actors
|
|
182
|
+
/** Actors with cosmetic changes and no functional one. They only need a new build when released. */
|
|
172
183
|
cosmetic: ActorConfig[];
|
|
184
|
+
/**
|
|
185
|
+
* Actors with at least one platform test change, whatever else changed. Their tests need to run, but test changes
|
|
186
|
+
* alone need no release. An Actor can be in this list and in one of the others.
|
|
187
|
+
*/
|
|
188
|
+
test: ActorConfig[];
|
|
173
189
|
}
|
|
174
190
|
|
|
175
191
|
/** Finds the Actors the changed files belong to, split by the most impactful change each Actor has. */
|
|
@@ -177,11 +193,11 @@ export const getChangedActors = async ({
|
|
|
177
193
|
filepathsChanged,
|
|
178
194
|
actorConfigs,
|
|
179
195
|
range,
|
|
180
|
-
|
|
196
|
+
platformTestsGlob,
|
|
181
197
|
}: ShouldBuildAndTestOptions): Promise<ChangedActors> => {
|
|
182
198
|
const actorsChangedMap = new Map<string, ActorChangeEntry>();
|
|
183
199
|
const testFilesInContext = new Set<string>();
|
|
184
|
-
const isTestFile =
|
|
200
|
+
const isTestFile = createTestFilesMatcher(platformTestsGlob);
|
|
185
201
|
|
|
186
202
|
for (const actorConfig of actorConfigs) {
|
|
187
203
|
const dockerIgnoreMatcher = loadDockerIgnore(actorConfig.dockerContextDir);
|
|
@@ -204,9 +220,13 @@ export const getChangedActors = async ({
|
|
|
204
220
|
actorConfig,
|
|
205
221
|
files: [],
|
|
206
222
|
hasFunctionalChange: false,
|
|
223
|
+
hasCosmeticChange: false,
|
|
224
|
+
hasTestChange: false,
|
|
207
225
|
};
|
|
208
226
|
entry.files.push(originalFilePath);
|
|
209
227
|
entry.hasFunctionalChange ||= change.impact === 'functional';
|
|
228
|
+
entry.hasCosmeticChange ||= change.impact === 'cosmetic';
|
|
229
|
+
entry.hasTestChange ||= change.impact === 'test';
|
|
210
230
|
actorsChangedMap.set(actorConfig.folder.path, entry);
|
|
211
231
|
}
|
|
212
232
|
}
|
|
@@ -226,11 +246,15 @@ export const getChangedActors = async ({
|
|
|
226
246
|
|
|
227
247
|
const entries = Array.from(actorsChangedMap.values());
|
|
228
248
|
const functional = entries.filter((entry) => entry.hasFunctionalChange).map((entry) => entry.actorConfig);
|
|
229
|
-
const cosmetic = entries
|
|
249
|
+
const cosmetic = entries
|
|
250
|
+
.filter((entry) => !entry.hasFunctionalChange && entry.hasCosmeticChange)
|
|
251
|
+
.map((entry) => entry.actorConfig);
|
|
252
|
+
const test = entries.filter((entry) => entry.hasTestChange).map((entry) => entry.actorConfig);
|
|
230
253
|
|
|
231
254
|
const names = (configs: ActorConfig[]) => configs.map((config) => config.actorFullName).join(', ') || 'none';
|
|
232
255
|
logger.info(`[DIFF]: Actors with functional changes: ${names(functional)}`);
|
|
233
256
|
logger.info(`[DIFF]: Actors with only cosmetic changes: ${names(cosmetic)}`);
|
|
257
|
+
logger.info(`[DIFF]: Actors with platform test changes: ${names(test)}`);
|
|
234
258
|
|
|
235
|
-
return { functional, cosmetic };
|
|
259
|
+
return { functional, cosmetic, test };
|
|
236
260
|
};
|
package/bin/main.ts
CHANGED
|
@@ -56,10 +56,6 @@ const affectedRangeOptions = {
|
|
|
56
56
|
type: 'string',
|
|
57
57
|
describe: 'Only count commits after this one. Ignored with --base if it is not on the branch',
|
|
58
58
|
},
|
|
59
|
-
'test-files-glob': {
|
|
60
|
-
type: 'string',
|
|
61
|
-
describe: 'Glob (from the repo root) of test files that count as changes even when .dockerignore lists them',
|
|
62
|
-
},
|
|
63
59
|
} as const satisfies Record<string, Options>;
|
|
64
60
|
|
|
65
61
|
/**
|
|
@@ -103,15 +99,19 @@ const envVarsSyncOptions = {
|
|
|
103
99
|
|
|
104
100
|
/** Flags of `test`, which runs the repository's vitest on the Actors piped in, e.g. the builds from `build`. */
|
|
105
101
|
const testOptions = {
|
|
102
|
+
'test-files-glob': {
|
|
103
|
+
type: 'string',
|
|
104
|
+
default: 'test/platform/**',
|
|
105
|
+
describe: 'Glob (from the repo root) of the test files to run. Quote it, so the shell does not expand it',
|
|
106
|
+
},
|
|
106
107
|
'vitest-output-file': { type: 'string', describe: "Also keep vitest's own JSON report there, e.g. to upload it" },
|
|
107
108
|
reporter: {
|
|
108
109
|
type: 'string',
|
|
109
110
|
describe:
|
|
110
111
|
'Vitest reporter (https://vitest.dev/guide/reporters) to add to `default` and `json`, which are always on, ' +
|
|
111
112
|
'e.g. github-actions. Repeat it for more',
|
|
112
|
-
// A plain string, not `array: true`: yargs would fill an array option with the arguments after it too
|
|
113
|
-
//
|
|
114
|
-
// still comes as an array, a single one as a string, so both are normalized to an array.
|
|
113
|
+
// A plain string, not `array: true`: yargs would fill an array option with the arguments after it too. A
|
|
114
|
+
// repeated flag still comes as an array, a single one as a string, so both are normalized to an array.
|
|
115
115
|
coerce: (value: string | string[] | undefined) => (value === undefined ? [] : [value].flat()),
|
|
116
116
|
},
|
|
117
117
|
} as const satisfies Record<string, Options>;
|
|
@@ -183,20 +183,12 @@ await yargs()
|
|
|
183
183
|
},
|
|
184
184
|
)
|
|
185
185
|
.command(
|
|
186
|
-
'test
|
|
186
|
+
'test',
|
|
187
187
|
'Run the tests of the Actors listed on stdin (builds from build, or get-actor-configs for default builds), and print a report for notify',
|
|
188
|
-
(y) =>
|
|
189
|
-
y
|
|
190
|
-
.positional('filters', {
|
|
191
|
-
type: 'string',
|
|
192
|
-
array: true,
|
|
193
|
-
default: ['test/platform'],
|
|
194
|
-
describe: 'Test files to run, as vitest filters',
|
|
195
|
-
})
|
|
196
|
-
.options(testOptions),
|
|
188
|
+
(y) => y.options(testOptions),
|
|
197
189
|
async (args) => {
|
|
198
190
|
const report = await runTests({
|
|
199
|
-
|
|
191
|
+
testFilesGlob: args.testFilesGlob,
|
|
200
192
|
targets: parseTestTargets(await readStdin()),
|
|
201
193
|
vitestOutputFile: args.vitestOutputFile,
|
|
202
194
|
reporters: args.reporter,
|
|
@@ -229,7 +221,8 @@ await yargs()
|
|
|
229
221
|
async (args) => {
|
|
230
222
|
const input = parseBuildInput(await readStdin());
|
|
231
223
|
const names = input.actors.map(({ actorFullName }) => actorFullName);
|
|
232
|
-
// An empty --actors selects every Actor, but an empty list here means there is nothing to build
|
|
224
|
+
// An empty --actors selects every Actor, but an empty list here means there is nothing to build. The configs
|
|
225
|
+
// are picked by name, so an Actor listed twice (e.g. in two lists of get-affected-actors) is built once
|
|
233
226
|
const actorConfigs = names.length > 0 ? await readConfigFile({ actors: names, ignore: [] }) : [];
|
|
234
227
|
const builds = await buildActors({
|
|
235
228
|
actorConfigs,
|
package/bin/run-tests.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { createInterface } from 'node:readline';
|
|
|
9
9
|
import { withActorIds } from './actor-ids.js';
|
|
10
10
|
import { logger } from './logger.js';
|
|
11
11
|
import type { TestTarget } from './stdin-input.js';
|
|
12
|
+
import { resolveTestFilesGlob } from './test-files-glob.js';
|
|
12
13
|
import { failedRunReport, logTestReport, type TestReport, toTestReport, type VitestJsonReport } from './test-report.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -29,9 +30,32 @@ const resolveVitestBin = async (cwd: string): Promise<string> => {
|
|
|
29
30
|
}
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Turns the glob into vitest filters. Vitest filters are parts of a file path, not globs, so the glob is resolved here.
|
|
35
|
+
* The filters are the folders that hold only matched files and the other matched files one by one, so the command
|
|
36
|
+
* line stays short. They go to vitest as absolute paths. Vitest also matches their path relative to the root anywhere
|
|
37
|
+
* in a test file's path, so a copy at `dist/test/platform/a.test.ts` would run too: vitest's own `exclude` keeps such
|
|
38
|
+
* copies out.
|
|
39
|
+
*/
|
|
40
|
+
const resolveVitestFilters = async (testFilesGlob: string, cwd: string): Promise<string[]> => {
|
|
41
|
+
const { files, paths } = await resolveTestFilesGlob(testFilesGlob, cwd);
|
|
42
|
+
// Without filters vitest would run every test of the repo
|
|
43
|
+
if (files.length === 0) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`No files match --test-files-glob "${testFilesGlob}". The glob is relative to the repo root, e.g. "test/platform/**".`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
logger.info(`Running the ${files.length} file(s) that match --test-files-glob "${testFilesGlob}"`);
|
|
49
|
+
logger.debug(`Files: ${files.join(', ')}`);
|
|
50
|
+
// Vitest compares against real paths, and cwd can be a symlink (e.g. /tmp on macOS). join keeps a folder's
|
|
51
|
+
// trailing slash, which stops `test/platform/daily/` from also matching `test/platform/daily-without-pr/`.
|
|
52
|
+
const root = await fs.realpath(cwd);
|
|
53
|
+
return paths.map((filePath) => path.join(root, filePath));
|
|
54
|
+
};
|
|
55
|
+
|
|
32
56
|
interface RunTestsOptions {
|
|
33
|
-
/**
|
|
34
|
-
|
|
57
|
+
/** Glob from the repo root (`cwd`) of the test files to run, e.g. `test/platform/**` */
|
|
58
|
+
testFilesGlob: string;
|
|
35
59
|
/** The Actors to test, each with its build or, without one, its default build */
|
|
36
60
|
targets: TestTarget[];
|
|
37
61
|
/** Keeps vitest's own JSON report there, e.g. to upload it */
|
|
@@ -47,7 +71,7 @@ interface RunTestsOptions {
|
|
|
47
71
|
* an error: the report is how the failure gets to `notify`.
|
|
48
72
|
*/
|
|
49
73
|
export const runTests = async ({
|
|
50
|
-
|
|
74
|
+
testFilesGlob,
|
|
51
75
|
targets,
|
|
52
76
|
vitestOutputFile,
|
|
53
77
|
reporters = [],
|
|
@@ -58,6 +82,7 @@ export const runTests = async ({
|
|
|
58
82
|
throw new Error('TESTER_APIFY_TOKEN is not set. The platform tests need it to run the Actors.');
|
|
59
83
|
}
|
|
60
84
|
const vitestBin = await resolveVitestBin(cwd);
|
|
85
|
+
const vitestFilters = await resolveVitestFilters(testFilesGlob, cwd);
|
|
61
86
|
// A test only runs when the ID it names its Actor by is listed, so each Actor gets both its full name and raw ID
|
|
62
87
|
const actorBuilds = await withActorIds(targets);
|
|
63
88
|
|
|
@@ -81,7 +106,7 @@ export const runTests = async ({
|
|
|
81
106
|
const args = [
|
|
82
107
|
vitestBin,
|
|
83
108
|
'run',
|
|
84
|
-
...
|
|
109
|
+
...vitestFilters,
|
|
85
110
|
'--reporter=default',
|
|
86
111
|
'--reporter=json',
|
|
87
112
|
...reporters.map((reporter) => `--reporter=${reporter}`),
|