523 lines
14 KiB
Rust
523 lines
14 KiB
Rust
// Copyright 2013-2014 The rust-url developers.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
// option. This file may not be copied, modified, or distributed
|
|
// except according to those terms.
|
|
|
|
//! Data-driven tests imported from web-platform-tests
|
|
|
|
use serde_json::Value;
|
|
use std::collections::HashMap;
|
|
use std::fmt::Write;
|
|
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
|
use std::sync::Mutex;
|
|
use url::Url;
|
|
|
|
// https://rustwasm.github.io/wasm-bindgen/wasm-bindgen-test/usage.html
|
|
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
|
use wasm_bindgen_test::{console_log, wasm_bindgen_test, wasm_bindgen_test_configure};
|
|
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
|
wasm_bindgen_test_configure!(run_in_browser);
|
|
|
|
// wpt has its own test driver, but we shoe-horn this into wasm_bindgen_test
|
|
// which will discard stdout and stderr. So, we make println! go to
|
|
// console.log(), so we see failures that do not result in panics.
|
|
|
|
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
|
static PRINT_BUF: Mutex<Option<String>> = Mutex::new(None);
|
|
|
|
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
|
macro_rules! print {
|
|
($($arg:tt)*) => {
|
|
let v = format!($($arg)*);
|
|
{
|
|
let mut buf = PRINT_BUF.lock().unwrap();
|
|
if let Some(buf) = buf.as_mut() {
|
|
buf.push_str(&v);
|
|
} else {
|
|
*buf = Some(v);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
|
macro_rules! println {
|
|
() => {
|
|
let buf = PRINT_BUF.lock().unwrap().take();
|
|
match buf {
|
|
Some(buf) => console_log!("{buf}"),
|
|
None => console_log!(""),
|
|
}
|
|
};
|
|
($($arg:tt)*) => {
|
|
let buf = PRINT_BUF.lock().unwrap().take();
|
|
match buf {
|
|
Some(buf) => {
|
|
let v = format!($($arg)*);
|
|
console_log!("{buf}{v}");
|
|
},
|
|
None => console_log!($($arg)*),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
struct UrlTest {
|
|
input: String,
|
|
base: Option<String>,
|
|
#[serde(flatten)]
|
|
result: UrlTestResult,
|
|
}
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
#[serde(untagged)]
|
|
#[allow(clippy::large_enum_variant)]
|
|
enum UrlTestResult {
|
|
Ok(UrlTestOk),
|
|
Fail(UrlTestFail),
|
|
}
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
struct UrlTestOk {
|
|
href: String,
|
|
protocol: String,
|
|
username: String,
|
|
password: String,
|
|
host: String,
|
|
hostname: String,
|
|
port: String,
|
|
pathname: String,
|
|
search: String,
|
|
hash: String,
|
|
}
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
struct UrlTestFail {
|
|
failure: bool,
|
|
}
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
struct SetterTest {
|
|
href: String,
|
|
new_value: String,
|
|
expected: SetterTestExpected,
|
|
}
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
struct SetterTestExpected {
|
|
href: Option<String>,
|
|
protocol: Option<String>,
|
|
username: Option<String>,
|
|
password: Option<String>,
|
|
host: Option<String>,
|
|
hostname: Option<String>,
|
|
port: Option<String>,
|
|
pathname: Option<String>,
|
|
search: Option<String>,
|
|
hash: Option<String>,
|
|
}
|
|
|
|
#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)]
|
|
fn main() {
|
|
let mut filter = None;
|
|
let mut args = std::env::args().skip(1);
|
|
while filter.is_none() {
|
|
if let Some(arg) = args.next() {
|
|
if arg == "--test-threads" {
|
|
args.next();
|
|
continue;
|
|
}
|
|
filter = Some(arg);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
let mut expected_failures = include_str!("expected_failures.txt")
|
|
.lines()
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut errors = vec![];
|
|
|
|
// Copied from https://github.com/web-platform-tests/wpt/blob/master/url/
|
|
let url_json: Vec<Value> = serde_json::from_str(include_str!("urltestdata.json"))
|
|
.expect("JSON parse error in urltestdata.json");
|
|
let url_tests = url_json
|
|
.into_iter()
|
|
.filter(|val| val.is_object())
|
|
.map(|val| serde_json::from_value::<UrlTest>(val).expect("parsing failed"))
|
|
.collect::<Vec<_>>();
|
|
|
|
let setter_json: HashMap<String, Value> =
|
|
serde_json::from_str(include_str!("setters_tests.json"))
|
|
.expect("JSON parse error in setters_tests.json");
|
|
let setter_tests = setter_json
|
|
.into_iter()
|
|
.filter(|(k, _)| k != "comment")
|
|
.map(|(k, v)| {
|
|
let test = serde_json::from_value::<Vec<SetterTest>>(v).expect("parsing failed");
|
|
(k, test)
|
|
})
|
|
.collect::<HashMap<_, _>>();
|
|
|
|
for url_test in url_tests {
|
|
let mut name = format!("<{}>", url_test.input.escape_default());
|
|
if let Some(base) = &url_test.base {
|
|
write!(&mut name, " against <{}>", base.escape_default()).unwrap();
|
|
}
|
|
if should_skip(&name, filter.as_deref()) {
|
|
continue;
|
|
}
|
|
print!("{} ... ", name);
|
|
|
|
let res = run_url_test(url_test);
|
|
report(name, res, &mut errors, &mut expected_failures);
|
|
}
|
|
|
|
for (kind, tests) in setter_tests {
|
|
for test in tests {
|
|
let name = format!(
|
|
"<{}> set {} to <{}>",
|
|
test.href.escape_default(),
|
|
kind,
|
|
test.new_value.escape_default()
|
|
);
|
|
if should_skip(&name, filter.as_deref()) {
|
|
continue;
|
|
}
|
|
|
|
print!("{} ... ", name);
|
|
|
|
let res = run_setter_test(&kind, test);
|
|
report(name, res, &mut errors, &mut expected_failures);
|
|
}
|
|
}
|
|
|
|
println!();
|
|
println!("====================");
|
|
println!();
|
|
|
|
if !errors.is_empty() {
|
|
println!("errors:");
|
|
println!();
|
|
|
|
for (name, err) in errors {
|
|
println!(" name: {}", name);
|
|
println!(" err: {}", err);
|
|
println!();
|
|
}
|
|
|
|
std::process::exit(1);
|
|
} else {
|
|
println!("all tests passed");
|
|
}
|
|
|
|
if !expected_failures.is_empty() && filter.is_none() {
|
|
println!();
|
|
println!("====================");
|
|
println!();
|
|
println!("tests were expected to fail but did not run:");
|
|
println!();
|
|
|
|
for name in expected_failures {
|
|
println!(" {}", name);
|
|
}
|
|
|
|
println!();
|
|
println!("if these tests were removed, update expected_failures.txt");
|
|
println!();
|
|
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
|
|
fn should_skip(name: &str, filter: Option<&str>) -> bool {
|
|
match filter {
|
|
Some(filter) => !name.contains(filter),
|
|
None => false,
|
|
}
|
|
}
|
|
|
|
fn report(
|
|
name: String,
|
|
res: Result<(), String>,
|
|
errors: &mut Vec<(String, String)>,
|
|
expected_failures: &mut Vec<&str>,
|
|
) {
|
|
let expected_failure = expected_failures.contains(&&*name);
|
|
expected_failures.retain(|&s| s != &*name);
|
|
match res {
|
|
Ok(()) => {
|
|
if expected_failure {
|
|
println!("🟠 (unexpected success)");
|
|
errors.push((name, "unexpected success".to_string()));
|
|
} else {
|
|
println!("✅");
|
|
}
|
|
}
|
|
Err(err) => {
|
|
if expected_failure {
|
|
println!("✅ (expected fail)");
|
|
} else {
|
|
println!("❌");
|
|
errors.push((name, err));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn run_url_test(
|
|
UrlTest {
|
|
base,
|
|
input,
|
|
result,
|
|
}: UrlTest,
|
|
) -> Result<(), String> {
|
|
let base = match base {
|
|
Some(base) => {
|
|
let base =
|
|
Url::parse(&base).map_err(|e| format!("errored while parsing base: {}", e))?;
|
|
Some(base)
|
|
}
|
|
None => None,
|
|
};
|
|
|
|
let res = Url::options()
|
|
.base_url(base.as_ref())
|
|
.parse(&input)
|
|
.map_err(|e| format!("errored while parsing input: {}", e));
|
|
|
|
match result {
|
|
UrlTestResult::Ok(ok) => check_url_ok(res, ok),
|
|
UrlTestResult::Fail(fail) => {
|
|
assert!(fail.failure);
|
|
if res.is_ok() {
|
|
return Err("expected failure, but parsed successfully".to_string());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn check_url_ok(res: Result<Url, String>, ok: UrlTestOk) -> Result<(), String> {
|
|
let url = match res {
|
|
Ok(url) => url,
|
|
Err(err) => {
|
|
return Err(format!("expected success, but errored: {:?}", err));
|
|
}
|
|
};
|
|
|
|
let href = url::quirks::href(&url);
|
|
if href != ok.href {
|
|
return Err(format!("expected href {:?}, but got {:?}", ok.href, href));
|
|
}
|
|
|
|
let protocol = url::quirks::protocol(&url);
|
|
if protocol != ok.protocol {
|
|
return Err(format!(
|
|
"expected protocol {:?}, but got {:?}",
|
|
ok.protocol, protocol
|
|
));
|
|
}
|
|
|
|
let username = url::quirks::username(&url);
|
|
if username != ok.username {
|
|
return Err(format!(
|
|
"expected username {:?}, but got {:?}",
|
|
ok.username, username
|
|
));
|
|
}
|
|
|
|
let password = url::quirks::password(&url);
|
|
if password != ok.password {
|
|
return Err(format!(
|
|
"expected password {:?}, but got {:?}",
|
|
ok.password, password
|
|
));
|
|
}
|
|
|
|
let host = url::quirks::host(&url);
|
|
if host != ok.host {
|
|
return Err(format!("expected host {:?}, but got {:?}", ok.host, host));
|
|
}
|
|
|
|
let hostname = url::quirks::hostname(&url);
|
|
if hostname != ok.hostname {
|
|
return Err(format!(
|
|
"expected hostname {:?}, but got {:?}",
|
|
ok.hostname, hostname
|
|
));
|
|
}
|
|
|
|
let port = url::quirks::port(&url);
|
|
if port != ok.port {
|
|
return Err(format!("expected port {:?}, but got {:?}", ok.port, port));
|
|
}
|
|
|
|
let pathname = url::quirks::pathname(&url);
|
|
if pathname != ok.pathname {
|
|
return Err(format!(
|
|
"expected pathname {:?}, but got {:?}",
|
|
ok.pathname, pathname
|
|
));
|
|
}
|
|
|
|
let search = url::quirks::search(&url);
|
|
if search != ok.search {
|
|
return Err(format!(
|
|
"expected search {:?}, but got {:?}",
|
|
ok.search, search
|
|
));
|
|
}
|
|
|
|
let hash = url::quirks::hash(&url);
|
|
if hash != ok.hash {
|
|
return Err(format!("expected hash {:?}, but got {:?}", ok.hash, hash));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_setter_test(
|
|
kind: &str,
|
|
SetterTest {
|
|
href,
|
|
new_value,
|
|
expected,
|
|
}: SetterTest,
|
|
) -> Result<(), String> {
|
|
let mut url = Url::parse(&href).map_err(|e| format!("errored while parsing href: {}", e))?;
|
|
|
|
match kind {
|
|
"protocol" => {
|
|
url::quirks::set_protocol(&mut url, &new_value).ok();
|
|
}
|
|
"username" => {
|
|
url::quirks::set_username(&mut url, &new_value).ok();
|
|
}
|
|
"password" => {
|
|
url::quirks::set_password(&mut url, &new_value).ok();
|
|
}
|
|
"host" => {
|
|
url::quirks::set_host(&mut url, &new_value).ok();
|
|
}
|
|
"hostname" => {
|
|
url::quirks::set_hostname(&mut url, &new_value).ok();
|
|
}
|
|
"port" => {
|
|
url::quirks::set_port(&mut url, &new_value).ok();
|
|
}
|
|
"pathname" => url::quirks::set_pathname(&mut url, &new_value),
|
|
"search" => url::quirks::set_search(&mut url, &new_value),
|
|
"hash" => url::quirks::set_hash(&mut url, &new_value),
|
|
_ => {
|
|
return Err(format!("unknown setter kind: {:?}", kind));
|
|
}
|
|
}
|
|
|
|
if let Some(expected_href) = expected.href {
|
|
let href = url::quirks::href(&url);
|
|
if href != expected_href {
|
|
return Err(format!(
|
|
"expected href {:?}, but got {:?}",
|
|
expected_href, href
|
|
));
|
|
}
|
|
}
|
|
|
|
if let Some(expected_protocol) = expected.protocol {
|
|
let protocol = url::quirks::protocol(&url);
|
|
if protocol != expected_protocol {
|
|
return Err(format!(
|
|
"expected protocol {:?}, but got {:?}",
|
|
expected_protocol, protocol
|
|
));
|
|
}
|
|
}
|
|
|
|
if let Some(expected_username) = expected.username {
|
|
let username = url::quirks::username(&url);
|
|
if username != expected_username {
|
|
return Err(format!(
|
|
"expected username {:?}, but got {:?}",
|
|
expected_username, username
|
|
));
|
|
}
|
|
}
|
|
|
|
if let Some(expected_password) = expected.password {
|
|
let password = url::quirks::password(&url);
|
|
if password != expected_password {
|
|
return Err(format!(
|
|
"expected password {:?}, but got {:?}",
|
|
expected_password, password
|
|
));
|
|
}
|
|
}
|
|
|
|
if let Some(expected_host) = expected.host {
|
|
let host = url::quirks::host(&url);
|
|
if host != expected_host {
|
|
return Err(format!(
|
|
"expected host {:?}, but got {:?}",
|
|
expected_host, host
|
|
));
|
|
}
|
|
}
|
|
|
|
if let Some(expected_hostname) = expected.hostname {
|
|
let hostname = url::quirks::hostname(&url);
|
|
if hostname != expected_hostname {
|
|
return Err(format!(
|
|
"expected hostname {:?}, but got {:?}",
|
|
expected_hostname, hostname
|
|
));
|
|
}
|
|
}
|
|
|
|
if let Some(expected_port) = expected.port {
|
|
let port = url::quirks::port(&url);
|
|
if port != expected_port {
|
|
return Err(format!(
|
|
"expected port {:?}, but got {:?}",
|
|
expected_port, port
|
|
));
|
|
}
|
|
}
|
|
|
|
if let Some(expected_pathname) = expected.pathname {
|
|
let pathname = url::quirks::pathname(&url);
|
|
if pathname != expected_pathname {
|
|
return Err(format!(
|
|
"expected pathname {:?}, but got {:?}",
|
|
expected_pathname, pathname
|
|
));
|
|
}
|
|
}
|
|
|
|
if let Some(expected_search) = expected.search {
|
|
let search = url::quirks::search(&url);
|
|
if search != expected_search {
|
|
return Err(format!(
|
|
"expected search {:?}, but got {:?}",
|
|
expected_search, search
|
|
));
|
|
}
|
|
}
|
|
|
|
if let Some(expected_hash) = expected.hash {
|
|
let hash = url::quirks::hash(&url);
|
|
if hash != expected_hash {
|
|
return Err(format!(
|
|
"expected hash {:?}, but got {:?}",
|
|
expected_hash, hash
|
|
));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|