@@ -58,46 +58,66 @@ function codeUnitStr(char) {
5858 return 'U+' + char.charCodeAt(0).toString(16);
5959}
6060
61+ class ReportResult {
62+ constructor(name) {
63+ this.test = name;
64+ this.status = 'OK';
65+ this.subtests = [];
66+ }
67+
68+ addSubtest(name, status, message) {
69+ const subtest = {
70+ status,
71+ // https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L3722
72+ name: sanitizeUnpairedSurrogates(name),
73+ };
74+ if (message) {
75+ // https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L4506
76+ subtest.message = sanitizeUnpairedSurrogates(message);
77+ }
78+ this.subtests.push(subtest);
79+ return subtest;
80+ }
81+
82+ finish(status) {
83+ this.status = status ?? 'OK';
84+ }
85+ }
86+
87+ // Generates a report that can be uploaded to wpt.fyi.
88+ // Checkout https://github.com/web-platform-tests/wpt.fyi/tree/main/api#results-creation
89+ // for more details.
6190class WPTReport {
6291 constructor(path) {
6392 this.filename = `report-${path.replaceAll('/', '-')}.json`;
64- this.results = [];
93+ /** @type {Map<string, ReportResult>} */
94+ this.results = new Map();
6595 this.time_start = Date.now();
6696 }
6797
68- addResult(name, status) {
69- const result = {
70- test: name,
71- status,
72- subtests: [],
73- addSubtest(name, status, message) {
74- const subtest = {
75- status,
76- // https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L3722
77- name: sanitizeUnpairedSurrogates(name),
78- };
79- if (message) {
80- // https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L4506
81- subtest.message = sanitizeUnpairedSurrogates(message);
82- }
83- this.subtests.push(subtest);
84- return subtest;
85- },
86- };
87- this.results.push(result);
98+ /**
99+ * Get or create a ReportResult for a test spec.
100+ * @param {WPTTestSpec} spec
101+ */
102+ getResult(spec) {
103+ const name = `/${spec.getRelativePath()}${spec.variant}`;
104+ if (this.results.has(name)) {
105+ return this.results.get(name);
106+ }
107+ const result = new ReportResult(name);
108+ this.results.set(name, result);
88109 return result;
89110 }
90111
91112 write() {
92113 this.time_end = Date.now();
93- this.results = this.results.filter((result) => {
94- return result.status === 'SKIP' || result.subtests.length !== 0;
95- }).map((result) => {
96- const url = new URL(result.test, 'http://wpt');
97- url.pathname = url.pathname.replace(/\.js$/, '.html');
98- result.test = url.href.slice(url.origin.length);
99- return result;
100- });
114+ const results = Array.from(this.results.values())
115+ .map((result) => {
116+ const url = new URL(result.test, 'http://wpt');
117+ url.pathname = url.pathname.replace(/\.js$/, '.html');
118+ result.test = url.href.slice(url.origin.length);
119+ return result;
120+ });
101121
102122 /**
103123 * Return required and some optional properties
@@ -110,7 +130,12 @@ class WPTReport {
110130 os: getOs(),
111131 };
112132
113- fs.writeFileSync(`out/wpt/${this.filename}`, JSON.stringify(this));
133+ fs.writeFileSync(`out/wpt/${this.filename}`, JSON.stringify({
134+ time_start: this.time_start,
135+ time_end: this.time_end,
136+ run_info: this.run_info,
137+ results: results,
138+ }));
114139 }
115140}
116141
@@ -642,14 +667,13 @@ class WPTRunner {
642667 this.inProgress.add(spec);
643668 this.workers.set(spec, worker);
644669
645- let reportResult;
670+ const reportResult = this.report?.getResult(spec) ;
646671 worker.on('message', (message) => {
647672 switch (message.type) {
648673 case 'result':
649- reportResult ||= this.report?.addResult(`/${relativePath}${spec.variant}`, 'OK');
650674 return this.resultCallback(spec, message.result, reportResult);
651675 case 'completion':
652- return this.completionCallback(spec, message.status);
676+ return this.completionCallback(spec, message.status, reportResult );
653677 default:
654678 throw new Error(`Unexpected message from worker: ${message.type}`);
655679 }
@@ -661,6 +685,8 @@ class WPTRunner {
661685 // This can happen normally, for example in timers tests.
662686 return;
663687 }
688+ // Generate a subtest failure for visibility.
689+ // No need to record this synthetic failure with wpt.fyi.
664690 this.fail(
665691 spec,
666692 {
@@ -671,6 +697,8 @@ class WPTRunner {
671697 },
672698 kUncaught,
673699 );
700+ // Mark the whole test as failed in wpt.fyi report.
701+ reportResult?.finish('ERROR');
674702 this.inProgress.delete(spec);
675703 });
676704
@@ -680,7 +708,11 @@ class WPTRunner {
680708
681709 process.on('exit', () => {
682710 for (const spec of this.inProgress) {
711+ // No need to record this synthetic failure with wpt.fyi.
683712 this.fail(spec, { name: 'Incomplete' }, kIncomplete);
713+ // Mark the whole test as failed in wpt.fyi report.
714+ const reportResult = this.report?.getResult(spec);
715+ reportResult?.finish('ERROR');
684716 }
685717 inspect.defaultOptions.depth = Infinity;
686718 // Sorts the rules to have consistent output
@@ -780,6 +812,7 @@ class WPTRunner {
780812 * in one test file).
781813 * @param {WPTTestSpec} spec
782814 * @param {Test} test The Test object returned by WPT harness
815+ * @param {ReportResult} reportResult The report result object
783816 */
784817 resultCallback(spec, test, reportResult) {
785818 const status = this.getTestStatus(test.status);
@@ -794,13 +827,19 @@ class WPTRunner {
794827 * Report the status of each WPT test (one per file)
795828 * @param {WPTTestSpec} spec
796829 * @param {object} harnessStatus - The status object returned by WPT harness.
830+ * @param {ReportResult} reportResult The report result object
797831 */
798- completionCallback(spec, harnessStatus) {
832+ completionCallback(spec, harnessStatus, reportResult ) {
799833 const status = this.getTestStatus(harnessStatus.status);
800834
801835 // Treat it like a test case failure
802836 if (status === kTimeout) {
837+ // No need to record this synthetic failure with wpt.fyi.
803838 this.fail(spec, { name: 'WPT testharness timeout' }, kTimeout);
839+ // Mark the whole test as TIMEOUT in wpt.fyi report.
840+ reportResult?.finish('TIMEOUT');
841+ } else {
842+ reportResult?.finish();
804843 }
805844 this.inProgress.delete(spec);
806845 // Always force termination of the worker. Some tests allocate resources
0 commit comments