| // Copyright 2014-2017 The html5ever Project Developers. See the |
| // COPYRIGHT file at the top-level directory of this distribution. |
| // |
| // 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. |
| |
| extern crate rustc_test as test; |
| #[macro_use] |
| extern crate html5ever; |
| |
| mod foreach_html5lib_test; |
| use foreach_html5lib_test::foreach_html5lib_test; |
| |
| use std::collections::{HashMap, HashSet}; |
| use std::default::Default; |
| use std::ffi::OsStr; |
| use std::io::BufRead; |
| use std::iter::repeat; |
| use std::mem::replace; |
| use std::path::Path; |
| use std::{env, fs, io}; |
| use test::{DynTestName, TestDesc, TestDescAndFn, TestFn}; |
| |
| use html5ever::rcdom::{Handle, NodeData, RcDom}; |
| use html5ever::tendril::{StrTendril, TendrilSink}; |
| use html5ever::{parse_document, parse_fragment, ParseOpts}; |
| use html5ever::{LocalName, QualName}; |
| |
| fn parse_tests<It: Iterator<Item = String>>(mut lines: It) -> Vec<HashMap<String, String>> { |
| let mut tests = vec![]; |
| let mut test = HashMap::new(); |
| let mut key: Option<String> = None; |
| let mut val = String::new(); |
| |
| macro_rules! finish_val ( () => ( |
| match key.take() { |
| None => (), |
| Some(key) => { |
| assert!(test.insert(key, replace(&mut val, String::new())).is_none()); |
| } |
| } |
| )); |
| |
| macro_rules! finish_test ( () => ( |
| if !test.is_empty() { |
| tests.push(replace(&mut test, HashMap::new())); |
| } |
| )); |
| |
| loop { |
| match lines.next() { |
| None => break, |
| Some(line) => { |
| if line.starts_with("#") { |
| finish_val!(); |
| if line == "#data" { |
| finish_test!(); |
| } |
| key = Some(line[1..].to_string()); |
| } else { |
| val.push_str(&line); |
| val.push('\n'); |
| } |
| }, |
| } |
| } |
| |
| finish_val!(); |
| finish_test!(); |
| tests |
| } |
| |
| fn serialize(buf: &mut String, indent: usize, handle: Handle) { |
| buf.push_str("|"); |
| buf.push_str(&repeat(" ").take(indent).collect::<String>()); |
| |
| let node = handle; |
| match node.data { |
| NodeData::Document => panic!("should not reach Document"), |
| |
| NodeData::Doctype { |
| ref name, |
| ref public_id, |
| ref system_id, |
| } => { |
| buf.push_str("<!DOCTYPE "); |
| buf.push_str(&name); |
| if !public_id.is_empty() || !system_id.is_empty() { |
| buf.push_str(&format!(" \"{}\" \"{}\"", public_id, system_id)); |
| } |
| buf.push_str(">\n"); |
| }, |
| |
| NodeData::Text { ref contents } => { |
| buf.push_str("\""); |
| buf.push_str(&contents.borrow()); |
| buf.push_str("\"\n"); |
| }, |
| |
| NodeData::Comment { ref contents } => { |
| buf.push_str("<!-- "); |
| buf.push_str(&contents); |
| buf.push_str(" -->\n"); |
| }, |
| |
| NodeData::Element { |
| ref name, |
| ref attrs, |
| .. |
| } => { |
| buf.push_str("<"); |
| match name.ns { |
| ns!(svg) => buf.push_str("svg "), |
| ns!(mathml) => buf.push_str("math "), |
| _ => (), |
| } |
| buf.push_str(&*name.local); |
| buf.push_str(">\n"); |
| |
| let mut attrs = attrs.borrow().clone(); |
| attrs.sort_by(|x, y| x.name.local.cmp(&y.name.local)); |
| // FIXME: sort by UTF-16 code unit |
| |
| for attr in attrs.into_iter() { |
| buf.push_str("|"); |
| buf.push_str(&repeat(" ").take(indent + 2).collect::<String>()); |
| match attr.name.ns { |
| ns!(xlink) => buf.push_str("xlink "), |
| ns!(xml) => buf.push_str("xml "), |
| ns!(xmlns) => buf.push_str("xmlns "), |
| _ => (), |
| } |
| buf.push_str(&format!("{}=\"{}\"\n", attr.name.local, attr.value)); |
| } |
| }, |
| |
| NodeData::ProcessingInstruction { .. } => unreachable!(), |
| } |
| |
| for child in node.children.borrow().iter() { |
| serialize(buf, indent + 2, child.clone()); |
| } |
| |
| if let NodeData::Element { |
| template_contents: Some(ref content), |
| .. |
| } = node.data |
| { |
| buf.push_str("|"); |
| buf.push_str(&repeat(" ").take(indent + 2).collect::<String>()); |
| buf.push_str("content\n"); |
| for child in content.children.borrow().iter() { |
| serialize(buf, indent + 4, child.clone()); |
| } |
| } |
| } |
| |
| fn make_test( |
| tests: &mut Vec<TestDescAndFn>, |
| ignores: &HashSet<String>, |
| filename: &str, |
| idx: usize, |
| fields: HashMap<String, String>, |
| ) { |
| let scripting_flags = &[false, true]; |
| let scripting_flags = if fields.contains_key("script-off") { |
| &scripting_flags[0..1] |
| } else if fields.contains_key("script-on") { |
| &scripting_flags[1..2] |
| } else { |
| &scripting_flags[0..2] |
| }; |
| let name = format!("tb: {}-{}", filename, idx); |
| for scripting_enabled in scripting_flags { |
| let test = make_test_desc_with_scripting_flag(ignores, &name, &fields, *scripting_enabled); |
| tests.push(test); |
| } |
| } |
| |
| fn make_test_desc_with_scripting_flag( |
| ignores: &HashSet<String>, |
| name: &str, |
| fields: &HashMap<String, String>, |
| scripting_enabled: bool, |
| ) -> TestDescAndFn { |
| let get_field = |key| { |
| let field = fields.get(key).expect("missing field"); |
| field.trim_end_matches('\n').to_string() |
| }; |
| |
| let mut data = fields.get("data").expect("missing data").to_string(); |
| data.pop(); |
| let expected = get_field("document"); |
| let context = fields |
| .get("document-fragment") |
| .map(|field| context_name(field.trim_end_matches('\n'))); |
| let ignore = ignores.contains(name); |
| let mut name = name.to_owned(); |
| if scripting_enabled { |
| name.push_str(" (scripting enabled)"); |
| } else { |
| name.push_str(" (scripting disabled)"); |
| }; |
| let mut opts: ParseOpts = Default::default(); |
| opts.tree_builder.scripting_enabled = scripting_enabled; |
| |
| TestDescAndFn { |
| desc: TestDesc { |
| ignore: ignore, |
| ..TestDesc::new(DynTestName(name)) |
| }, |
| testfn: TestFn::dyn_test_fn(move || { |
| // Do this here because Tendril isn't Send. |
| let data = StrTendril::from_slice(&data); |
| let mut result = String::new(); |
| match context { |
| None => { |
| let dom = parse_document(RcDom::default(), opts).one(data.clone()); |
| for child in dom.document.children.borrow().iter() { |
| serialize(&mut result, 1, child.clone()); |
| } |
| }, |
| Some(ref context) => { |
| let dom = parse_fragment(RcDom::default(), opts, context.clone(), vec![]) |
| .one(data.clone()); |
| // fragment case: serialize children of the html element |
| // rather than children of the document |
| let doc = &dom.document; |
| let root = &doc.children.borrow()[0]; |
| for child in root.children.borrow().iter() { |
| serialize(&mut result, 1, child.clone()); |
| } |
| }, |
| }; |
| let len = result.len(); |
| result.truncate(len - 1); // drop the trailing newline |
| |
| if result != expected { |
| panic!( |
| "\ninput: {}\ngot:\n{}\nexpected:\n{}\n", |
| data, result, expected |
| ); |
| } |
| }), |
| } |
| } |
| |
| fn context_name(context: &str) -> QualName { |
| if context.starts_with("svg ") { |
| QualName::new(None, ns!(svg), LocalName::from(&context[4..])) |
| } else if context.starts_with("math ") { |
| QualName::new(None, ns!(mathml), LocalName::from(&context[5..])) |
| } else { |
| QualName::new(None, ns!(html), LocalName::from(context)) |
| } |
| } |
| |
| fn tests(src_dir: &Path, ignores: &HashSet<String>) -> Vec<TestDescAndFn> { |
| let mut tests = vec![]; |
| |
| foreach_html5lib_test( |
| src_dir, |
| "tree-construction", |
| OsStr::new("dat"), |
| |path, file| { |
| let buf = io::BufReader::new(file); |
| let lines = buf.lines().map(|res| res.ok().expect("couldn't read")); |
| let data = parse_tests(lines); |
| |
| for (i, test) in data.into_iter().enumerate() { |
| make_test( |
| &mut tests, |
| ignores, |
| path.file_name().unwrap().to_str().unwrap(), |
| i, |
| test, |
| ); |
| } |
| }, |
| ); |
| |
| tests |
| } |
| |
| fn main() { |
| let args: Vec<_> = env::args().collect(); |
| let src_dir = Path::new(env!("CARGO_MANIFEST_DIR")); |
| let mut ignores = HashSet::new(); |
| { |
| let f = fs::File::open(&src_dir.join("data/test/ignore")).unwrap(); |
| let r = io::BufReader::new(f); |
| for ln in r.lines() { |
| ignores.insert(ln.unwrap().trim_end().to_string()); |
| } |
| } |
| |
| test::test_main(&args, tests(src_dir, &ignores)); |
| } |