Skip to content

Commit 0fd6555

Browse files
add ENV variables to simplify testing (e.g.: force to run in non interactive env)
1 parent a3202f1 commit 0fd6555

File tree

6 files changed

+146
-199
lines changed

6 files changed

+146
-199
lines changed

v-next/hardhat/src/internal/cli/telemetry/analytics/analytics-subprocess.ts

-15
This file was deleted.

v-next/hardhat/src/internal/cli/telemetry/analytics/analytics.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,18 @@ async function sendAnalytics(
7979
async function createSubprocessToSendAnalytics(
8080
payload: TelemetryConsentPayload | Payload,
8181
): Promise<void> {
82-
// The file extension is 'js' because the 'ts' file will be compiled
83-
const analyticsSubprocessFilePath = `${import.meta.dirname}/analytics-subprocess.js`;
82+
const fileExt =
83+
process.env.HARDHAT_TEST_SUBPROCESS_RESULT_PATH !== undefined ? "ts" : "js";
84+
const subprocessFile = `${import.meta.dirname}/subprocess.${fileExt}`;
85+
86+
const env: Record<string, string> = {};
87+
if (process.env.HARDHAT_TEST_SUBPROCESS_RESULT_PATH !== undefined) {
88+
// ATTENTION: only for testing
89+
env.HARDHAT_TEST_SUBPROCESS_RESULT_PATH =
90+
process.env.HARDHAT_TEST_SUBPROCESS_RESULT_PATH;
91+
}
8492

85-
await spawnDetachedSubProcess(analyticsSubprocessFilePath, [
86-
JSON.stringify(payload),
87-
]);
93+
await spawnDetachedSubProcess(subprocessFile, [JSON.stringify(payload)], env);
8894
}
8995

9096
async function buildPayload(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { writeJsonFile } from "@ignored/hardhat-vnext-utils/fs";
2+
import { postJsonRequest } from "@ignored/hardhat-vnext-utils/request";
3+
4+
// These keys are expected to be public
5+
const ANALYTICS_URL = "https://www.google-analytics.com/mp/collect";
6+
const API_SECRET = "iXzTRik5RhahYpgiatSv1w";
7+
const MEASUREMENT_ID = "G-ZFZWHGZ64H";
8+
9+
async function main(): Promise<void> {
10+
const payload = JSON.parse(process.argv[2]);
11+
12+
if (process.env.HARDHAT_TEST_SUBPROCESS_RESULT_PATH === undefined) {
13+
await postJsonRequest(ANALYTICS_URL, payload, {
14+
queryParams: {
15+
api_secret: API_SECRET,
16+
measurement_id: MEASUREMENT_ID,
17+
},
18+
});
19+
20+
return;
21+
}
22+
23+
// ATTENTION: only for testing
24+
await writeJsonFile(process.env.HARDHAT_TEST_SUBPROCESS_RESULT_PATH, payload);
25+
}
26+
27+
await main();

v-next/hardhat/src/internal/cli/telemetry/telemetry-permissions.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ export async function isTelemetryAllowed(): Promise<boolean> {
4848
}
4949

5050
const consent = await getTelemetryConsent();
51+
52+
// ATTENTION: only for testing
53+
if (process.env.HARDHAT_TEST_TELEMETRY_CONSENT_VALUE !== undefined) {
54+
return process.env.HARDHAT_TEST_TELEMETRY_CONSENT_VALUE === "true"
55+
? true
56+
: false;
57+
}
58+
5159
return consent !== undefined ? consent : false;
5260
}
5361

@@ -64,7 +72,7 @@ export function isTelemetryAllowedInEnvironment(): boolean {
6472
(!isCi() &&
6573
process.stdout.isTTY === true &&
6674
process.env.HARDHAT_DISABLE_TELEMETRY_PROMPT !== "true") ||
67-
process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST === "true" // Used in tests to force telemetry execution
75+
process.env.HARDHAT_TEST_INTERACTIVE_ENV === "true" // Used in tests to force telemetry execution
6876
);
6977
}
7078

v-next/hardhat/test/internal/cli/telemetry/analytics/analytics.ts

+93-172
Original file line numberDiff line numberDiff line change
@@ -4,213 +4,134 @@ import assert from "node:assert/strict";
44
import path from "node:path";
55
import { after, afterEach, before, beforeEach, describe, it } from "node:test";
66

7-
import { getConfigDir } from "@ignored/hardhat-vnext-core/global-dir";
8-
import {
9-
copy,
10-
exists,
11-
readJsonFile,
12-
remove,
13-
writeJsonFile,
14-
} from "@ignored/hardhat-vnext-utils/fs";
7+
import { readJsonFile, remove } from "@ignored/hardhat-vnext-utils/fs";
158

169
import {
1710
sendTaskAnalytics,
1811
sendTelemetryConsentAnalytics,
1912
} from "../../../../../src/internal/cli/telemetry/analytics/analytics.js";
2013
import { getHardhatVersion } from "../../../../../src/internal/utils/package.js";
14+
import {
15+
checkIfSubprocessWasExecuted,
16+
ROOT_PATH_TO_FIXTURE,
17+
} from "../helpers.js";
2118

22-
// The analytics logic uses a detached subprocess to send the payload via HTTP call.
23-
// We cannot test the HTTP call directly, but we can use a test subprocess to verify if the payload is correctly created.
24-
// This is possible because the analytics code attempts to execute a subprocess file of type 'JS'. JS files are only available after compilation.
25-
// During the tests, no JS file is available, so the expected subprocess does not exist. Therefore, we can copy a test subprocess file
26-
// to the expected location instead of the original one and check if it receives the correct payload.
27-
28-
const PATH_TO_FIXTURE = path.join(
29-
process.cwd(),
30-
"test",
31-
"fixture-projects",
32-
"cli",
33-
"telemetry",
34-
"analytics",
35-
);
36-
37-
const SOURCE_PATH_TEST_SUBPROCESS_FILE = path.join(
38-
PATH_TO_FIXTURE,
39-
"analytics-subprocess.js",
40-
);
41-
42-
const DEST_PATH_TEST_SUBPROCESS_FILE = path.join(
43-
process.cwd(),
44-
"src",
45-
"internal",
46-
"cli",
47-
"telemetry",
48-
"analytics",
49-
"analytics-subprocess.js",
50-
);
51-
19+
const PATH_TO_FIXTURE = path.join(ROOT_PATH_TO_FIXTURE, "analytics");
5220
const RESULT_FILE_PATH = path.join(PATH_TO_FIXTURE, "result.json");
5321

54-
async function copyTestSubprocessFile() {
55-
await copy(SOURCE_PATH_TEST_SUBPROCESS_FILE, DEST_PATH_TEST_SUBPROCESS_FILE);
56-
}
57-
58-
async function removeTestSubprocessFile() {
59-
remove(DEST_PATH_TEST_SUBPROCESS_FILE);
60-
}
61-
62-
async function setTelemetryConsentFile(consent: boolean) {
63-
const configDir = await getConfigDir();
64-
const filePath = path.join(configDir, "telemetry-consent.json");
65-
await writeJsonFile(filePath, { consent });
66-
}
67-
68-
async function checkIfSubprocessWasExecuted() {
69-
// Checks if the subprocess was executed by waiting for a file to be created.
70-
// Uses an interval to periodically check for the file. If the file isn't found
71-
// within a specified number of attempts, an error is thrown, indicating a failure in subprocess execution.
72-
const MAX_COUNTER = 20;
73-
74-
return new Promise((resolve, reject) => {
75-
let counter = 0;
76-
77-
const intervalId = setInterval(async () => {
78-
counter++;
79-
80-
if (await exists(RESULT_FILE_PATH)) {
81-
clearInterval(intervalId);
82-
resolve(true);
83-
} else if (counter > MAX_COUNTER) {
84-
clearInterval(intervalId);
85-
reject("Subprocess was not executed in the expected time");
86-
}
87-
}, 100);
88-
});
89-
}
90-
9122
describe("analytics", () => {
92-
before(async () => {
93-
copyTestSubprocessFile();
23+
beforeEach(async () => {
24+
process.env.HARDHAT_TEST_TELEMETRY_CONSENT_VALUE = "true";
9425
});
9526

96-
after(async () => {
97-
await removeTestSubprocessFile();
27+
afterEach(async () => {
28+
delete process.env.HARDHAT_TEST_TELEMETRY_CONSENT_VALUE;
9829
});
9930

100-
beforeEach(async () => {
101-
await remove(RESULT_FILE_PATH);
102-
});
31+
describe("running in non interactive environment", () => {
32+
it("should not send consent because the environment is non interactive", async () => {
33+
const wasSent = await sendTelemetryConsentAnalytics(true);
34+
assert.equal(wasSent, false);
35+
});
10336

104-
afterEach(async () => {
105-
await remove(RESULT_FILE_PATH);
37+
it("should not send analytics because the environment is not interactive", async () => {
38+
const wasSent = await sendTaskAnalytics(["task", "subtask"]);
39+
assert.equal(wasSent, false);
40+
});
10641
});
10742

108-
describe("analytics payload", async () => {
109-
const ORIGINAL_PROCESS_ENV = { ...process };
43+
describe("running in an interactive environment (simulated with ENV variables)", () => {
44+
before(() => {
45+
process.env.HARDHAT_TEST_INTERACTIVE_ENV = "true";
46+
process.env.HARDHAT_TEST_SUBPROCESS_RESULT_PATH = RESULT_FILE_PATH;
47+
});
48+
49+
after(() => {
50+
delete process.env.HARDHAT_TEST_INTERACTIVE_ENV;
51+
});
11052

111-
describe("not running in CI", () => {
112-
before(() => {
113-
// Force Ci to not be detected as Ci so the test can run (Ci is blocked for analytics)
114-
process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST = "true";
115-
});
53+
beforeEach(async () => {
54+
await remove(RESULT_FILE_PATH);
55+
});
11656

117-
after(() => {
118-
delete process.env.HARDHAT_ENABLE_TELEMETRY_IN_TEST;
119-
process = ORIGINAL_PROCESS_ENV;
120-
});
57+
afterEach(async () => {
58+
await remove(RESULT_FILE_PATH);
59+
});
12160

122-
it("should create the correct payload for the telemetry consent (positive consent)", async () => {
123-
await sendTelemetryConsentAnalytics(true);
61+
it("should create the correct payload for the telemetry consent (positive consent)", async () => {
62+
await sendTelemetryConsentAnalytics(true);
12463

125-
await checkIfSubprocessWasExecuted();
64+
await checkIfSubprocessWasExecuted(RESULT_FILE_PATH);
12665

127-
const result = await readJsonFile(RESULT_FILE_PATH);
66+
const result = await readJsonFile(RESULT_FILE_PATH);
12867

129-
assert.deepEqual(result, {
130-
client_id: "hardhat_telemetry_consent",
131-
user_id: "hardhat_telemetry_consent",
132-
user_properties: {},
133-
events: [
134-
{
135-
name: "TelemetryConsentResponse",
136-
params: {
137-
userConsent: "yes",
138-
},
68+
assert.deepEqual(result, {
69+
client_id: "hardhat_telemetry_consent",
70+
user_id: "hardhat_telemetry_consent",
71+
user_properties: {},
72+
events: [
73+
{
74+
name: "TelemetryConsentResponse",
75+
params: {
76+
userConsent: "yes",
13977
},
140-
],
141-
});
78+
},
79+
],
14280
});
81+
});
14382

144-
it("should create the correct payload for the telemetry consent (negative consent)", async () => {
145-
await sendTelemetryConsentAnalytics(false);
83+
it("should create the correct payload for the telemetry consent (negative consent)", async () => {
84+
await sendTelemetryConsentAnalytics(false);
14685

147-
await checkIfSubprocessWasExecuted();
86+
await checkIfSubprocessWasExecuted(RESULT_FILE_PATH);
14887

149-
const result = await readJsonFile(RESULT_FILE_PATH);
88+
const result = await readJsonFile(RESULT_FILE_PATH);
15089

151-
assert.deepEqual(result, {
152-
client_id: "hardhat_telemetry_consent",
153-
user_id: "hardhat_telemetry_consent",
154-
user_properties: {},
155-
events: [
156-
{
157-
name: "TelemetryConsentResponse",
158-
params: {
159-
userConsent: "no",
160-
},
90+
assert.deepEqual(result, {
91+
client_id: "hardhat_telemetry_consent",
92+
user_id: "hardhat_telemetry_consent",
93+
user_properties: {},
94+
events: [
95+
{
96+
name: "TelemetryConsentResponse",
97+
params: {
98+
userConsent: "no",
16199
},
162-
],
163-
});
164-
});
165-
166-
it("should create the correct payload for the task analytics", async () => {
167-
await setTelemetryConsentFile(true);
168-
169-
const wasSent = await sendTaskAnalytics(["task", "subtask"]);
170-
171-
await checkIfSubprocessWasExecuted();
172-
173-
const result: Payload = await readJsonFile(RESULT_FILE_PATH);
174-
175-
assert.equal(wasSent, true);
176-
177-
// Check payload properties
178-
assert.notEqual(result.client_id, undefined);
179-
assert.notEqual(result.user_id, undefined);
180-
assert.equal(result.user_properties.projectId.value, "hardhat-project");
181-
assert.equal(
182-
result.user_properties.hardhatVersion.value,
183-
await getHardhatVersion(),
184-
);
185-
assert.notEqual(
186-
result.user_properties.operatingSystem.value,
187-
undefined,
188-
);
189-
assert.notEqual(result.user_properties.nodeVersion.value, undefined);
190-
assert.equal(result.events[0].name, "task");
191-
assert.equal(result.events[0].params.engagement_time_msec, "10000");
192-
assert.notEqual(result.events[0].params.session_id, undefined);
193-
assert.equal(result.events[0].params.task, "task, subtask");
100+
},
101+
],
194102
});
103+
});
195104

196-
it("should not send analytics because the consent is not given", async () => {
197-
await setTelemetryConsentFile(false);
198-
const wasSent = await sendTaskAnalytics(["task", "subtask"]);
199-
assert.equal(wasSent, false);
200-
});
105+
it("should create the correct payload for the task analytics", async () => {
106+
const wasSent = await sendTaskAnalytics(["task", "subtask"]);
107+
108+
await checkIfSubprocessWasExecuted(RESULT_FILE_PATH);
109+
110+
const result: Payload = await readJsonFile(RESULT_FILE_PATH);
111+
112+
assert.equal(wasSent, true);
113+
114+
// Check payload properties
115+
assert.notEqual(result.client_id, undefined);
116+
assert.notEqual(result.user_id, undefined);
117+
assert.equal(result.user_properties.projectId.value, "hardhat-project");
118+
assert.equal(
119+
result.user_properties.hardhatVersion.value,
120+
await getHardhatVersion(),
121+
);
122+
assert.notEqual(result.user_properties.operatingSystem.value, undefined);
123+
assert.notEqual(result.user_properties.nodeVersion.value, undefined);
124+
assert.equal(result.events[0].name, "task");
125+
assert.equal(result.events[0].params.engagement_time_msec, "10000");
126+
assert.notEqual(result.events[0].params.session_id, undefined);
127+
assert.equal(result.events[0].params.task, "task, subtask");
201128
});
202129

203-
describe("running in non interactive environment", () => {
204-
it("should not send consent because the environment is non interactive", async () => {
205-
const wasSent = await sendTelemetryConsentAnalytics(true);
206-
assert.equal(wasSent, false);
207-
});
130+
it("should not send analytics because the consent is not given", async () => {
131+
process.env.HARDHAT_TEST_TELEMETRY_CONSENT_VALUE = "false";
208132

209-
it("should not send analytics because the environment is not interactive", async () => {
210-
await setTelemetryConsentFile(true);
211-
const wasSent = await sendTaskAnalytics(["task", "subtask"]);
212-
assert.equal(wasSent, false);
213-
});
133+
const wasSent = await sendTaskAnalytics(["task", "subtask"]);
134+
assert.equal(wasSent, false);
214135
});
215136
});
216137
});

0 commit comments

Comments
 (0)