Skip to content

Commit 3ab1b18

Browse files
committed
Introduce the ping testing API
This commit adds testBeforeNextSubmit to execute a validation function synchronously before the target ping is collected.
1 parent b269670 commit 3ab1b18

File tree

1 file changed

+61
-1
lines changed

1 file changed

+61
-1
lines changed

glean/src/core/pings/index.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import collectAndStorePing from "../pings/maker.js";
88
import type CommonPingData from "./common_ping_data.js";
99
import { Context } from "../context.js";
1010

11+
type ValidatorFunction = (reason?: string) => Promise<void>;
12+
1113
/**
1214
* Stores information about a ping.
1315
*
@@ -20,11 +22,22 @@ class PingType implements CommonPingData {
2022
readonly sendIfEmpty: boolean;
2123
readonly reasonCodes: string[];
2224

25+
// The functions and promises required for the test API to
26+
// execute and synchronize with the submission API.
27+
private testResolutionFunction?: (value: void | PromiseLike<void>) => void;
28+
private testValidator?: ValidatorFunction;
29+
private testValidatorPromise: Promise<void>;
30+
2331
constructor (meta: CommonPingData) {
2432
this.name = meta.name;
2533
this.includeClientId = meta.includeClientId;
2634
this.sendIfEmpty = meta.sendIfEmpty;
2735
this.reasonCodes = meta.reasonCodes ?? [];
36+
37+
// This promise should always be resolved to account for the
38+
// fact that no testing API might be called. In production the
39+
// `submit` function will await on this (no-op).
40+
this.testValidatorPromise = Promise.resolve();
2841
}
2942

3043
private isDeletionRequest(): boolean {
@@ -44,6 +57,31 @@ class PingType implements CommonPingData {
4457
* `ping_info.reason` part of the payload.
4558
*/
4659
submit(reason?: string): void {
60+
// **** Read this before changing the following code! ****
61+
//
62+
// The Dispatcher does not allow dispatched tasks to await on
63+
// other dispatched tasks. Unfortunately, this causes a deadlock.
64+
// In order to work around that problem, we kick off validation
65+
// right before the actual submission takes place, through another
66+
// async function (and not through the dispatcher). We then await
67+
// in the dispatched submission task on the related promise, which
68+
// will always resolve due to the use of `finally`.
69+
if (this.testValidator) {
70+
this.testValidatorPromise = this.testValidator(reason)
71+
.then(() => {
72+
if (this.testResolutionFunction) {
73+
this.testResolutionFunction();
74+
}
75+
})
76+
.catch(
77+
e => console.error(`There was an error validating ${this.name} (${reason ?? "no reason"}). Ignoring.`, e)
78+
)
79+
.finally(() => {
80+
this.testResolutionFunction = undefined;
81+
this.testValidator = undefined;
82+
});
83+
}
84+
4785
Context.dispatcher.launch(async () => {
4886
if (!Context.initialized) {
4987
console.info("Glean must be initialized before submitting pings.");
@@ -60,12 +98,34 @@ class PingType implements CommonPingData {
6098
console.error(`Invalid reason code ${reason} from ${this.name}. Ignoring.`);
6199
correctedReason = undefined;
62100
}
63-
101+
102+
await this.testValidatorPromise;
103+
64104
const identifier = generateUUIDv4();
65105
await collectAndStorePing(identifier, this, correctedReason);
66106
return;
67107
});
68108
}
109+
110+
/**
111+
* **Test-only API**
112+
*
113+
* Runs a validation function before the ping is collected.
114+
*
115+
* TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
116+
*
117+
* @param validatorFn The asynchronous validation function to run in order to validate
118+
* the ping content.
119+
*
120+
* @returns A `Promise` resolved when the ping is collected and the validation function
121+
* is executed.
122+
*/
123+
async testBeforeNextSubmit(validatorFn: ValidatorFunction): Promise<void> {
124+
return new Promise((resolve) => {
125+
this.testResolutionFunction = resolve;
126+
this.testValidator = validatorFn;
127+
});
128+
}
69129
}
70130

71131
export default PingType;

0 commit comments

Comments
 (0)