Skip to content

Commit 8096e4f

Browse files
authored
Merge pull request #4005 from github/mbg/test-env/improvements
Improve `TestEnv` for cleaner tests
2 parents 85830a2 + 9a9ab4c commit 8096e4f

4 files changed

Lines changed: 289 additions & 148 deletions

File tree

src/config/file.test.ts

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ test("getConfigFileInput returns undefined by default", async (t) => {
1313
await callee(getConfigFileInput)
1414
.withArgs({})
1515
.withFeatures([Feature.ConfigFileRepositoryProperty])
16-
.passes(async (fn) => t.is(await fn(), undefined));
16+
.passes(t.is, undefined);
1717
});
1818

1919
const repositoryProperties = {
@@ -22,75 +22,48 @@ const repositoryProperties = {
2222

2323
test("getConfigFileInput returns input value", async (t) => {
2424
const testInput = "/some/path";
25-
const target = callee(getConfigFileInput).withFeatures([
26-
Feature.ConfigFileRepositoryProperty,
27-
]);
28-
29-
const actionsEnv = target.getState().actions;
30-
sinon
31-
.stub(actionsEnv, "getOptionalInput")
32-
.withArgs("config-file")
33-
.returns(testInput);
3425

3526
// Even though both an input and repository property are configured,
3627
// we prefer the direct input to the Action.
37-
const targetWithArgs = target
38-
.withActions(actionsEnv)
39-
.withArgs(repositoryProperties);
40-
await targetWithArgs.passes(async (fn) => t.is(await fn(), testInput));
41-
42-
// Check for the expected log message.
43-
t.true(
44-
targetWithArgs
45-
.getLogger()
46-
.hasMessage("Using configuration file input from workflow"),
47-
);
28+
await callee(getConfigFileInput)
29+
.withFeatures([Feature.ConfigFileRepositoryProperty])
30+
.withActions((actionsEnv) => {
31+
sinon
32+
.stub(actionsEnv, "getOptionalInput")
33+
.withArgs("config-file")
34+
.returns(testInput);
35+
})
36+
.withArgs(repositoryProperties)
37+
.logs(t, "Using configuration file input from workflow")
38+
.passes(t.is, testInput);
4839
});
4940

5041
test("getConfigFileInput returns repository property value", async (t) => {
5142
// Since there is no direct input, we should use the repository property.
52-
const target = callee(getConfigFileInput)
43+
await callee(getConfigFileInput)
5344
.withFeatures([Feature.ConfigFileRepositoryProperty])
54-
.withArgs(repositoryProperties);
55-
56-
await target.passes(async (fn) =>
57-
t.is(await fn(), repositoryProperties[RepositoryPropertyName.CONFIG_FILE]),
58-
);
59-
60-
// Check for the expected log message.
61-
t.true(
62-
target
63-
.getLogger()
64-
.hasMessage("Using configuration file input from repository property"),
65-
);
45+
.withArgs(repositoryProperties)
46+
.logs(t, "Using configuration file input from repository property")
47+
.passes(t.is, repositoryProperties[RepositoryPropertyName.CONFIG_FILE]);
6648
});
6749

6850
test("getConfigFileInput ignores empty repository property value", async (t) => {
6951
// Since the repository property value is an empty/whitespace string, we should ignore it.
7052
await callee(getConfigFileInput)
7153
.withFeatures([Feature.ConfigFileRepositoryProperty])
7254
.withArgs({ [RepositoryPropertyName.CONFIG_FILE]: " " })
73-
.passes(async (fn) => t.is(await fn(), undefined));
55+
.passes(t.is, undefined);
7456
});
7557

7658
test("getConfigFileInput ignores repository property value when FF is off", async (t) => {
7759
// Since the FF is off, we should ignore the repository property value.
78-
const target = callee(getConfigFileInput)
60+
await callee(getConfigFileInput)
7961
.withFeatures([])
80-
.withArgs(repositoryProperties);
81-
82-
await target.passes(async (fn) => t.is(await fn(), undefined));
83-
84-
t.false(
85-
target
86-
.getLogger()
87-
.hasMessage("Using configuration file input from repository property"),
88-
);
89-
t.true(
90-
target
91-
.getLogger()
92-
.hasMessage(
93-
"Ignoring configuration file input from repository property, because the corresponding feature flag is disabled.",
94-
),
95-
);
62+
.withArgs(repositoryProperties)
63+
.notLogs(t, "Using configuration file input from repository property")
64+
.logs(
65+
t,
66+
"Ignoring configuration file input from repository property, because the corresponding feature flag is disabled.",
67+
)
68+
.passes(t.is, undefined);
9669
});

src/config/remote-file.test.ts

Lines changed: 46 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import sinon from "sinon";
44
import { ActionsEnvVars } from "../environment";
55
import * as errors from "../error-messages";
66
import { Feature } from "../feature-flags";
7-
import { callee, getTestEnv } from "../testing-utils";
7+
import { callee } from "../testing-utils";
88
import { ConfigurationError } from "../util";
99

1010
import {
@@ -50,7 +50,7 @@ test("parseRemoteFileAddress accepts full remote addresses", async (t) => {
5050
for (const oldFormatInput of oldFormatInputs) {
5151
await target
5252
.withArgs(oldFormatInput.input)
53-
.passes(async (fn) => t.deepEqual(await fn(), oldFormatInput.expected));
53+
.passes(t.deepEqual, oldFormatInput.expected);
5454
}
5555

5656
// New format.
@@ -78,28 +78,23 @@ test("parseRemoteFileAddress accepts full remote addresses", async (t) => {
7878
// Should fail when the FF is not enabled.
7979
await targetWithArgs
8080
.withFeatures([])
81-
.passes(async (fn) =>
82-
t.throwsAsync(fn, { instanceOf: ConfigurationError }),
83-
);
81+
.throws(t, { instanceOf: ConfigurationError });
8482

8583
// And pass when the FF is enabled.
8684
await targetWithArgs
8785
.withFeatures([Feature.NewRemoteFileAddresses])
88-
.passes(async (fn) => t.deepEqual(await fn(), newFormatInput.expected));
86+
.passes(t.deepEqual, newFormatInput.expected);
8987
}
9088
});
9189

9290
test("parseRemoteFileAddress accepts remote address without an owner", async (t) => {
93-
const target = callee(parseRemoteFileAddress);
94-
95-
const env = target.getState().env;
9691
const owner = "test-owner";
97-
const getRequired = sinon.stub(env, "getRequired");
98-
getRequired
99-
.withArgs(ActionsEnvVars.GITHUB_REPOSITORY)
100-
.returns(`${owner}/current-repo`);
101-
102-
const targetWithEnv = target.withEnv(env);
92+
const target = callee(parseRemoteFileAddress).withEnv((env) => {
93+
const getRequired = sinon.stub(env, "getRequired");
94+
getRequired
95+
.withArgs(ActionsEnvVars.GITHUB_REPOSITORY)
96+
.returns(`${owner}/current-repo`);
97+
});
10398

10499
const testCases: ParseRemoteFileAddressTest[] = [
105100
{
@@ -141,33 +136,33 @@ test("parseRemoteFileAddress accepts remote address without an owner", async (t)
141136
];
142137

143138
for (const testCase of testCases) {
144-
const targetWithArgs = targetWithEnv.withArgs(testCase.input);
139+
const targetWithArgs = target.withArgs(testCase.input);
145140

146141
// Should fail when the FF is not enabled.
147142
await targetWithArgs
148143
.withFeatures([])
149-
.passes(async (fn) =>
150-
t.throwsAsync(fn, { instanceOf: ConfigurationError }),
151-
);
144+
.throws(t, { instanceOf: ConfigurationError });
152145

153146
// And pass when the FF is enabled.
154147
await targetWithArgs
155148
.withFeatures([Feature.NewRemoteFileAddresses])
156-
.passes(async (fn) => t.deepEqual(await fn(), testCase.expected));
149+
.passes(t.deepEqual, testCase.expected);
157150
}
158151
});
159152

160153
test("parseRemoteFileAddress throws for invalid `GITHUB_REPOSITORY`", async (t) => {
161-
const target = callee(parseRemoteFileAddress).withArgs("repo@ref");
162-
163-
const env = target.getState().env;
164-
const getRequired = sinon.stub(env, "getRequired");
154+
const getRequired: sinon.SinonStub = sinon.stub();
165155
getRequired.withArgs(ActionsEnvVars.GITHUB_REPOSITORY).returns(`not-valid`);
166156

157+
const target = callee(parseRemoteFileAddress)
158+
.withArgs("repo@ref")
159+
.withEnv((env) => {
160+
sinon.define(env, "getRequired", getRequired);
161+
});
162+
167163
await target
168-
.withEnv(env)
169164
.withFeatures([Feature.NewRemoteFileAddresses])
170-
.passes(async (fn) => t.throwsAsync(fn, { instanceOf: Error }));
165+
.throws(t, { instanceOf: Error });
171166

172167
t.assert(getRequired.calledOnceWith(ActionsEnvVars.GITHUB_REPOSITORY));
173168
});
@@ -202,43 +197,38 @@ test("parseRemoteFileAddress accepts remote address without a path", async (t) =
202197
// Should fail when the FF is not enabled.
203198
await targetWithArgs
204199
.withFeatures([])
205-
.passes(async (fn) =>
206-
t.throwsAsync(fn, { instanceOf: ConfigurationError }),
207-
);
200+
.throws(t, { instanceOf: ConfigurationError });
208201

209202
// And pass when the FF is enabled.
210203
await targetWithArgs
211204
.withFeatures([Feature.NewRemoteFileAddresses])
212-
.passes(async (fn) => t.deepEqual(await fn(), testCase.expected));
205+
.passes(t.deepEqual, testCase.expected);
213206
}
214207
});
215208

216209
test("parseRemoteFileAddress accepts remote address without a ref", async (t) => {
217210
const target = callee(parseRemoteFileAddress).withArgs("owner/repo:path");
218211

219212
// Should only accept the input if the FF is enabled.
220-
await target.withFeatures([]).passes(t.throwsAsync);
213+
await target.withFeatures([]).throws(t);
221214
await target
222215
.withFeatures([Feature.NewRemoteFileAddresses])
223-
.passes(async (fn) =>
224-
t.deepEqual(await fn(), {
225-
owner: "owner",
226-
repo: "repo",
227-
path: "path",
228-
ref: DEFAULT_CONFIG_FILE_REF,
229-
} satisfies RemoteFileAddress),
230-
);
216+
.passes(t.deepEqual, {
217+
owner: "owner",
218+
repo: "repo",
219+
path: "path",
220+
ref: DEFAULT_CONFIG_FILE_REF,
221+
} satisfies RemoteFileAddress);
231222
});
232223

233224
test("parseRemoteFileAddress rejects invalid values", async (t) => {
234-
const env = getTestEnv();
235225
const owner = "owner";
236-
const getRequired = sinon.stub(env, "getRequired");
237-
getRequired
238-
.withArgs(ActionsEnvVars.GITHUB_REPOSITORY)
239-
.returns(`${owner}/current-repo`);
240-
241-
const target = callee(parseRemoteFileAddress).withEnv(env);
226+
const target = callee(parseRemoteFileAddress).withEnv((env) => {
227+
const getRequired = sinon.stub(env, "getRequired");
228+
getRequired
229+
.withArgs(ActionsEnvVars.GITHUB_REPOSITORY)
230+
.returns(`${owner}/current-repo`);
231+
});
242232

243233
const testInputs = [
244234
" ",
@@ -262,21 +252,17 @@ test("parseRemoteFileAddress rejects invalid values", async (t) => {
262252
const targetWithArgs = target.withArgs(testInput);
263253

264254
// Should throw both when the new format is and isn't accepted.
265-
await targetWithArgs.withFeatures([]).passes(async (fn) =>
266-
t.throwsAsync(fn, {
267-
instanceOf: ConfigurationError,
268-
message: errors.getConfigFileRepoOldFormatInvalidMessage(testInput),
269-
}),
270-
);
255+
await targetWithArgs.withFeatures([]).throws(t, {
256+
instanceOf: ConfigurationError,
257+
message: errors.getConfigFileRepoOldFormatInvalidMessage(testInput),
258+
});
271259
await targetWithArgs
272260
.withFeatures([Feature.NewRemoteFileAddresses])
273-
.passes(async (fn) =>
274-
t.throwsAsync(fn, {
275-
// When the new format is accepted, there are some more specific
276-
// errors in some cases. It is sufficient for us to check that
277-
// an exception is thrown.
278-
instanceOf: ConfigurationError,
279-
}),
280-
);
261+
.throws(t, {
262+
// When the new format is accepted, there are some more specific
263+
// errors in some cases. It is sufficient for us to check that
264+
// an exception is thrown.
265+
instanceOf: ConfigurationError,
266+
});
281267
}
282268
});

src/environment.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,12 @@ export enum ActionsEnvVars {
193193
GITHUB_SERVER_URL = "GITHUB_SERVER_URL",
194194
GITHUB_SHA = "GITHUB_SHA",
195195
GITHUB_WORKFLOW = "GITHUB_WORKFLOW",
196+
GITHUB_WORKSPACE = "GITHUB_WORKSPACE",
196197
RUNNER_ENVIRONMENT = "RUNNER_ENVIRONMENT",
197198
RUNNER_NAME = "RUNNER_NAME",
198199
RUNNER_OS = "RUNNER_OS",
199200
RUNNER_TEMP = "RUNNER_TEMP",
201+
RUNNER_TOOL_CACHE = "RUNNER_TOOL_CACHE",
200202
}
201203

202204
/** A type representing all known environment variables. */

0 commit comments

Comments
 (0)