Skip to content

Commit 4c7f8f0

Browse files
feat: support *.worker.js tests
1 parent e70fd6b commit 4c7f8f0

1 file changed

Lines changed: 200 additions & 32 deletions

File tree

moz-webgpu-cts/src/wpt/path.rs

Lines changed: 200 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ use std::{
55
};
66

77
use camino::{Utf8Component, Utf8Path};
8-
98
use clap::ValueEnum;
109
use format::lazy_format;
10+
use itertools::Itertools;
1111
use joinery::JoinableIterator;
12+
use strum::{EnumIter, IntoEnumIterator};
1213

1314
/// A browser supported by [crate::main], used for [`TestEntryPath`]s.
1415
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ValueEnum)]
@@ -63,8 +64,19 @@ pub(crate) struct SpecPath<'a> {
6364
pub r#type: SpecType,
6465
}
6566

67+
/// The type of tests that can be specified in a [`SpecPath`].
6668
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
6769
pub(crate) enum SpecType {
70+
/// A JavaScript test.
71+
///
72+
/// See also:
73+
///
74+
/// * [WPT upstream docs.' "JavaScript Tests (`testharness.js`)" section][upstream] for
75+
/// background.
76+
/// * [`JsExecScope`], which will be set in test entries specified in a file with this type.
77+
///
78+
/// [upstream]: https://web-platform-tests.org/writing-tests/testharness.html
79+
Js(JsSpecType),
6880
/// A catch-all for all `*.html` test spec. files. This is likely incorrect, but it works well
6981
/// enough for now!
7082
Html,
@@ -73,7 +85,7 @@ pub(crate) enum SpecType {
7385

7486
impl SpecType {
7587
fn iter() -> impl Iterator<Item = Self> {
76-
[Self::Html].into_iter()
88+
[Self::Html, Self::Js(JsSpecType::DedicatedWorker)].into_iter()
7789
}
7890

7991
pub fn from_base_name(base_name: &str) -> Option<(Self, &str)> {
@@ -82,12 +94,43 @@ impl SpecType {
8294
})
8395
}
8496

97+
pub fn validate_test_entry_base_name<'a>(
98+
&self,
99+
base_name: &'a str,
100+
) -> Option<(TestEntryType, &'a str)> {
101+
let permitted_test_entry_types = match self {
102+
Self::Js(JsSpecType::DedicatedWorker) => &[TestEntryType::Js {
103+
exec_scope: JsExecScope::DedicatedWorker,
104+
}],
105+
Self::Html => &[TestEntryType::Html],
106+
};
107+
permitted_test_entry_types
108+
.iter()
109+
.copied()
110+
.find_map(|test_entry_type| {
111+
strip_suffix_with_value(
112+
base_name,
113+
test_entry_type.file_extension(),
114+
test_entry_type,
115+
)
116+
})
117+
}
118+
85119
pub fn file_extension(&self) -> &'static str {
86120
match self {
121+
SpecType::Js(JsSpecType::DedicatedWorker) => ".worker.js",
87122
SpecType::Html => ".html",
88123
}
89124
}
90125
}
126+
127+
/// A subtype of [`SpecType::Js`].
128+
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
129+
pub(crate) enum JsSpecType {
130+
/// A `*.worker.js` test.
131+
DedicatedWorker,
132+
}
133+
91134
/// A symbolic path to an executed WPT test entry and its metadata, contained in a test
92135
/// specification (see also [`SpecPath`]). In combination with [`SpecPath`], this is useful for
93136
/// correlating entries from [`ExecutionReport`]s and [`metadata::File`]s.
@@ -96,6 +139,8 @@ impl SpecType {
96139
/// [`metadata::File`]: crate::wpt::metadata::File
97140
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
98141
pub(crate) struct TestEntry<'a> {
142+
/// The type of this entry. Based on it's spec. file's type (see [`SpecPath::type`]).
143+
pub r#type: TestEntryType,
99144
/// The variant of this particular test from this test's source code. If set, you should be
100145
/// able to correlate this with
101146
///
@@ -105,6 +150,69 @@ pub(crate) struct TestEntry<'a> {
105150
pub variant: Option<Cow<'a, str>>,
106151
}
107152

153+
/// The test entry analogue to [`SpecPath::type`].
154+
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
155+
pub(crate) enum TestEntryType {
156+
/// An HTML-authored test with no divergence from the base name of its corresponding spec.
157+
/// file. Corresponds to [`SpecType::Html`].
158+
Html,
159+
/// Corresponds to [`SpecType::Js`]. The test entry will have slightly different naming from
160+
/// its spec. file.
161+
///
162+
/// JS tests are converted to `*.html` tests at test execution time and reported as such.
163+
/// The set of values observable here are determined by this entry's spec.'s
164+
/// [`SpecPath::type`] and its
165+
///
166+
/// See also [WPT upstream's docs.' "Test Features" section][upstream]
167+
///
168+
/// [upstream]: https://web-platform-tests.org/writing-tests/file-names.html#test-features
169+
Js { exec_scope: JsExecScope },
170+
}
171+
172+
impl TestEntryType {
173+
fn iter() -> impl Iterator<Item = Self> {
174+
// NOTE: `Html`'s file extension is less specific than other file extensions, so try
175+
// matching it last.
176+
JsExecScope::iter()
177+
.map(|exec_scope| Self::Js { exec_scope })
178+
.chain([Self::Html])
179+
}
180+
181+
pub fn from_base_name(base_name: &str) -> Option<(Self, &str)> {
182+
Self::iter().find_map(|variant| {
183+
strip_suffix_with_value(base_name, variant.file_extension(), variant)
184+
})
185+
}
186+
187+
pub fn file_extension(self) -> &'static str {
188+
match self {
189+
Self::Html => ".html",
190+
Self::Js { exec_scope } => match exec_scope {
191+
JsExecScope::DedicatedWorker => ".worker.html",
192+
},
193+
}
194+
}
195+
196+
pub fn spec_type(self) -> SpecType {
197+
match self {
198+
Self::Html => SpecType::Html,
199+
Self::Js { exec_scope } => match exec_scope {
200+
JsExecScope::DedicatedWorker => SpecType::Js(JsSpecType::DedicatedWorker),
201+
},
202+
}
203+
}
204+
}
205+
206+
/// An executed JS test entry's test type, viz.,
207+
#[derive(Clone, Copy, Debug, EnumIter, Eq, Hash, Ord, PartialEq, PartialOrd)]
208+
pub(crate) enum JsExecScope {
209+
/// A `*.worker.js` test. See also [WPT upstream docs.' "Dedicated worker test (`.worker.js`)"
210+
/// section][upstream].
211+
///
212+
/// [upstream]: https://web-platform-tests.org/writing-tests/testharness.html#dedicated-worker-tests-worker-js
213+
DedicatedWorker,
214+
}
215+
108216
const ROOT_DIR_FX_MOZILLA_STR: &str = "testing/web-platform/mozilla";
109217
const ROOT_DIR_FX_MOZILLA_COMPONENTS: &[&str] = &["testing", "web-platform", "mozilla"];
110218
const ROOT_DIR_FX_UPSTREAM_STR: &str = "testing/web-platform";
@@ -144,19 +252,22 @@ impl<'a> TestEntryPath<'a> {
144252
None => return Err(err()),
145253
};
146254

147-
let (spec_type, path) = if let Some(path) = path.strip_suffix(".html") {
148-
(SpecType::Html, path)
149-
} else {
150-
return Err(err());
151-
};
255+
let mut path = Cow::<'_, Utf8Path>::from(Utf8Path::new(path));
256+
257+
let (test_entry_type, base_name) =
258+
TestEntryType::from_base_name(path.file_name().ok_or_else(err)?).ok_or_else(err)?;
259+
let spec_type = test_entry_type.spec_type();
260+
261+
path = path.with_file_name(base_name).into();
152262

153263
Ok(Self {
154264
spec_path: SpecPath {
155265
root_dir,
156-
path: Utf8Path::new(path).into(),
266+
path,
157267
r#type: spec_type,
158268
},
159269
test_entry: TestEntry {
270+
r#type: test_entry_type,
160271
variant: variant.map(Into::into),
161272
},
162273
})
@@ -198,9 +309,9 @@ impl<'a> TestEntryPath<'a> {
198309

199310
let (base_name, variant) = Self::split_test_base_name_from_variant(test_name);
200311

201-
let base_name = match spec_type {
202-
SpecType::Html => base_name.strip_suffix(".html").ok_or_else(err)?,
203-
};
312+
let (js_exec_scope, base_name) = spec_type
313+
.validate_test_entry_base_name(base_name)
314+
.ok_or_else(err)?;
204315

205316
if path.components().next_back() != Some(Utf8Component::Normal(base_name)) {
206317
return Err(err());
@@ -213,6 +324,7 @@ impl<'a> TestEntryPath<'a> {
213324
r#type: spec_type,
214325
},
215326
test_entry: TestEntry {
327+
r#type: js_exec_scope,
216328
variant: variant.map(Into::into),
217329
},
218330
})
@@ -236,7 +348,11 @@ impl<'a> TestEntryPath<'a> {
236348
path,
237349
r#type,
238350
},
239-
test_entry: TestEntry { variant },
351+
test_entry:
352+
TestEntry {
353+
r#type: js_exec_scope,
354+
variant,
355+
},
240356
} = self;
241357

242358
TestEntryPath {
@@ -246,6 +362,7 @@ impl<'a> TestEntryPath<'a> {
246362
r#type,
247363
},
248364
test_entry: TestEntry {
365+
r#type: js_exec_scope,
249366
variant: variant.clone().map(|v| v.into_owned().into()),
250367
},
251368
}
@@ -257,12 +374,16 @@ impl<'a> TestEntryPath<'a> {
257374
SpecPath {
258375
root_dir: _,
259376
path,
260-
r#type,
377+
r#type: _,
378+
},
379+
test_entry:
380+
TestEntry {
381+
r#type: js_exec_scope,
382+
variant,
261383
},
262-
test_entry: TestEntry { variant },
263384
} = self;
264385
let base_name = path.file_name().unwrap();
265-
let file_extension = r#type.file_extension();
386+
let file_extension = js_exec_scope.file_extension();
266387

267388
lazy_format!(move |f| {
268389
write!(f, "{base_name}{file_extension}")?;
@@ -279,23 +400,17 @@ impl<'a> TestEntryPath<'a> {
279400
SpecPath {
280401
root_dir,
281402
path,
282-
r#type,
403+
r#type: _,
283404
},
284-
test_entry: TestEntry { variant },
405+
test_entry: _,
285406
} = self;
286-
lazy_format!(move |f| {
287-
write!(
288-
f,
289-
"{}{}{}",
290-
root_dir.url_prefix(),
291-
path.components().join_with('/'),
292-
r#type.file_extension()
293-
)?;
294-
if let Some(variant) = variant.as_ref() {
295-
write!(f, "{}", variant)?;
296-
}
297-
Ok(())
298-
})
407+
lazy_format!(move |f| write!(
408+
f,
409+
"{}{}/{}",
410+
root_dir.url_prefix(),
411+
path.components().dropping_back(1).join_with('/'),
412+
self.test_name(),
413+
))
299414
}
300415

301416
pub(crate) fn rel_metadata_path(&self) -> impl Display + '_ {
@@ -306,7 +421,11 @@ impl<'a> TestEntryPath<'a> {
306421
path,
307422
r#type,
308423
},
309-
test_entry: TestEntry { variant: _ },
424+
test_entry:
425+
TestEntry {
426+
r#type: _,
427+
variant: _,
428+
},
310429
} = self;
311430

312431
let root_dir_dir = root_dir
@@ -450,6 +569,7 @@ fn parse_test_entry_path() {
450569
r#type: SpecType::Html,
451570
},
452571
test_entry: TestEntry {
572+
r#type: TestEntryType::Html,
453573
variant: Some("?stuff=things".into()),
454574
}
455575
}
@@ -468,7 +588,10 @@ fn parse_test_entry_path() {
468588
path: Utf8Path::new("stuff/things/cts.https").into(),
469589
r#type: SpecType::Html,
470590
},
471-
test_entry: TestEntry { variant: None }
591+
test_entry: TestEntry {
592+
r#type: TestEntryType::Html,
593+
variant: None
594+
}
472595
}
473596
);
474597

@@ -486,10 +609,55 @@ fn parse_test_entry_path() {
486609
r#type: SpecType::Html,
487610
},
488611
test_entry: TestEntry {
612+
r#type: TestEntryType::Html,
489613
variant: Some("?stuff=things".into()),
490614
}
491615
}
492616
);
617+
618+
assert_eq!(
619+
TestEntryPath::from_metadata_test(
620+
Browser::Servo,
621+
Path::new("tests/wpt/webgpu/meta/webgpu/do_the_thing.worker.js.ini"),
622+
"do_the_thing.worker.html"
623+
)
624+
.unwrap(),
625+
TestEntryPath {
626+
spec_path: SpecPath {
627+
root_dir: ServoRootDir::WebGpu.into(),
628+
path: Utf8Path::new("webgpu/do_the_thing").into(),
629+
r#type: SpecType::Js(JsSpecType::DedicatedWorker),
630+
},
631+
test_entry: TestEntry {
632+
r#type: TestEntryType::Js {
633+
exec_scope: JsExecScope::DedicatedWorker
634+
},
635+
variant: None,
636+
}
637+
}
638+
);
639+
640+
assert_eq!(
641+
TestEntryPath::from_metadata_test(
642+
Browser::Servo,
643+
Path::new("tests/wpt/webgpu/meta/webgpu/do_the_thing.worker.js.ini"),
644+
"do_the_thing.worker.html?foo=bar"
645+
)
646+
.unwrap(),
647+
TestEntryPath {
648+
spec_path: SpecPath {
649+
root_dir: ServoRootDir::WebGpu.into(),
650+
path: Utf8Path::new("webgpu/do_the_thing").into(),
651+
r#type: SpecType::Js(JsSpecType::DedicatedWorker),
652+
},
653+
test_entry: TestEntry {
654+
r#type: TestEntryType::Js {
655+
exec_scope: JsExecScope::DedicatedWorker
656+
},
657+
variant: Some("?foo=bar".into()),
658+
}
659+
}
660+
);
493661
}
494662

495663
#[test]

0 commit comments

Comments
 (0)