// Copyright 2013-2014 The rust-url developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , 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> = 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, #[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, protocol: Option, username: Option, password: Option, host: Option, hostname: Option, port: Option, pathname: Option, search: Option, hash: Option, } #[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::>(); let mut errors = vec![]; // Copied from https://github.com/web-platform-tests/wpt/blob/master/url/ let url_json: Vec = 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::(val).expect("parsing failed")) .collect::>(); let setter_json: HashMap = 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::>(v).expect("parsing failed"); (k, test) }) .collect::>(); 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, 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(()) }