Source code
Revision control
Copy as Markdown
Other Tools
// Copyright 2013-2014 The rust-url developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// 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;
#[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![];
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(())
}