Files
brave-core/components/speedreader/rust/lib/tests/lib.rs
T
Ralph Giles b7b3517594 speedreader: Add license headers
Document each file as MPL 2.0-licensed, per code policy.
Addresses an `npm run presubmit` warning.
2024-01-25 11:43:38 -08:00

341 lines
10 KiB
Rust

// Copyright (c) 2021 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
#![allow(dead_code)]
extern crate distance;
extern crate html5ever;
extern crate readability;
extern crate speedreader;
extern crate url;
use readability::extractor::extract;
use speedreader::classifier::feature_extractor::FeatureExtractorStreamer;
use std::fs::File;
use std::io::Read;
use url::Url;
use distance::damerau_levenshtein;
use html5ever::LocalName;
use kuchikiki::NodeData::{Element, Text};
use kuchikiki::NodeRef as Handle;
use std::vec::Vec;
static SAMPLES_PATH: &str = "data/tests-samples/";
pub fn extract_flattened_tree(
handle: Handle,
tags_attrs: Vec<(&str, &str)>,
flattened_nodes: &mut Vec<Handle>,
) -> Vec<Handle> {
for child in handle.children() {
let c = child.clone();
match c.data() {
Text(_) => {
flattened_nodes.push(c.clone());
}
Element(ref data) => {
let t = data.name.local.as_ref();
let attrs_borrow = data.attributes.borrow();
// check if current node name and attr match expected
for ta in tags_attrs.clone() {
let (tag_name, attr_name): (&str, &str) = ta;
if t == tag_name && attrs_borrow.get(LocalName::from(attr_name)).is_some() {
flattened_nodes.push(c.clone());
}
}
// if type Element, traverse to children in next iteration
extract_flattened_tree(child.clone(), tags_attrs.clone(), flattened_nodes);
}
_ => (),
}
}
flattened_nodes.to_vec()
}
// recursively extracts all text of leaf nodes into a string for comparison
pub fn extract_text(handle: Handle, text: &mut String) {
for child in handle.children() {
let c = child.clone();
match c.data() {
Text(ref contents) => {
text.push_str(contents.borrow().trim());
}
Element(_) => {
extract_text(child.clone(), text);
}
_ => (),
}
}
}
// recursively collects values of nodes with a certain tuple (tag_id,
// attribute_id) into a vector of strings for comparison
fn stripped_content(
handle: Handle,
tag_name: &str,
attr_name: &str,
nodes: &mut Vec<Handle>,
values: &mut Vec<String>,
) {
for child in handle.children() {
if let Element(ref data) = child.data() {
let t = data.name.local.as_ref();
if t.to_lowercase() == tag_name {
nodes.push(child.clone());
if let Some(value) = data.attributes.borrow().get(LocalName::from(attr_name)) {
values.push(value.to_string());
}
};
stripped_content(child.clone(), tag_name, attr_name, nodes, values);
}
}
}
// compares if DOMs keep an approximate (to a factor) number and value of the
// tuple (tag_name, attr_name)
fn tags_match_approx(
d1: Handle,
d2: Handle,
tag_name: &str,
attr_name: &str,
approx_factor: usize,
) -> bool {
let mut values_d1 = Vec::new();
let mut values_d2 = Vec::new();
stripped_content(d1, tag_name, attr_name, &mut Vec::new(), &mut values_d1);
stripped_content(d2, tag_name, attr_name, &mut Vec::new(), &mut values_d2);
if values_d2.len() > values_d1.len() + approx_factor {
println!("{:#?}\n != \n{:#?}", values_d1.len(), values_d2.len());
return false;
}
values_d1.sort();
values_d2.sort();
let mut approx_counter = approx_factor;
for (i, _) in values_d1.clone().iter().enumerate() {
if values_d2.len() > i && values_d1[i] != values_d2[i] {
approx_counter -= 1;
if approx_counter == 0 {
return false;
}
}
}
true
}
// stricly compares if flattened tree with subset of (tags, attrs) match
fn flattened_tree_match_strict(d1: Handle, d2: Handle, tags_attrs: Vec<(&str, &str)>) -> bool {
let _ftree1 = extract_flattened_tree(d1, tags_attrs.clone(), &mut Vec::new());
let _ftree2 = extract_flattened_tree(d2, tags_attrs, &mut Vec::new());
// #TODO: compare nodes' content
//for (i, _) in ftree1.clone().iter().enumerate() {
// if ftree1[i] != ftree2[i] {
// return false;
//}
true
}
fn strings_match_approx(s1: &str, s2: &str, f: usize) -> bool {
let diff = damerau_levenshtein(s1, s2);
if diff > f {
println!("damerau_levenshtein:: {}", diff);
return false;
}
true
}
fn load_test_files(test_name: &str) -> String {
let mut expected = "".to_owned();
let mut exp_f = File::open(format!("{}/{}/expected.html", SAMPLES_PATH, test_name)).unwrap();
exp_f.read_to_string(&mut expected).unwrap();
expected
}
#[macro_use]
#[cfg(test)]
mod test {
macro_rules! test {
($name:ident) => {
#[test]
fn $name() {
let url = Url::parse("http://url.com").unwrap();
let mut source_f =
File::open(format!("{}/{}/source.html", SAMPLES_PATH, stringify!($name)))
.unwrap();
// opens and parses the expected final result into a rcdom
// (for comparing with the result)
let expected_string = load_test_files(stringify!($name));
let mut feature_extractor = FeatureExtractorStreamer::try_new(&url).unwrap();
feature_extractor.write(&mut expected_string.as_bytes()).unwrap();
let expected = feature_extractor.end();
// uses the mapper build the mapper based on the source HTML
// document
let product = extract(&mut source_f, Some(url.as_str())).unwrap();
let mut feature_extractor = FeatureExtractorStreamer::try_new(&url).unwrap();
feature_extractor.write(&mut product.content.as_bytes()).unwrap();
let result = feature_extractor.end();
// checks full flattened tree for a subset of (tags, attrs)
//let mut tags_attrs: Vec<(&str, &str)> = Vec::new();
//tags_attrs.push(("a", "href"));
//tags_attrs.push(("img", "src"));
//let flattened_tree_match = flattened_tree_match_strict(
// expected.dom.document.clone(),
// result.dom.document.clone(),
// tags_attrs);
//assert!(flattened_tree_match, "Full flattened trees do not strictly match");
let atags_match = tags_match_approx(
expected.rcdom.document_node.clone(),
result.rcdom.document_node.clone(),
"a",
"href",
5,
);
assert!(atags_match, "Node values of <a href=''> do not approximately match");
let imgtags_match = tags_match_approx(
expected.rcdom.document_node.clone(),
result.rcdom.document_node.clone(),
"img",
"src",
5,
);
assert!(imgtags_match, "Node values of <img src=''> do not strictly match");
// note: now we can define tests similar to tags_match_strict
// but that are less strict. e.g. number of nodes in dom of a
// certain (tag, attr) may be differ by x)
// compares full flattened text nodes
let levenstein_threshold = 900;
let mut text_result = String::new();
extract_text(result.rcdom.document_node.clone(), &mut text_result);
let mut text_expected = String::new();
extract_text(expected.rcdom.document_node.clone(), &mut text_expected);
let strings_approx =
strings_match_approx(&text_result, &text_expected, levenstein_threshold);
assert!(strings_approx, "Flattened text is not similar enough");
//assert_eq!(text_result, text_expected, "Falttened texts in p tags do not
// match");
}
};
}
}
// passing
#[cfg(test)]
mod fulltest {
use super::*;
test!(ars_1);
test!(cnet);
test!(folha);
test!(liberation_1);
}
// test!(metadata_content_missing);
// test!(msn);
// test!(rtl_1);
// test!(rtl_2);
// test!(rtl_3);
// test!(rtl_4);
// test!(title_and_h1_discrepancy);
// test!(tumblr);
// test!(yahoo_4);
// test!(videos_2);
// test!(wordpress);
// test!(pixnet);
// // not passing in strict mode
// test!(aclu);
// test!(base_url);
// test!(base_url_base_element);
// test!(base_url_base_element_relative);
// test!(basic_tags_cleaning);
// test!(bbc_1);
// test!(blogger);
// test!(breitbart);
// test!(bug_1255978);
// test!(buzzfeed_1);
// test!(citylab_1);
// test!(clean_links);
// test!(cnet_svg_classes);
// test!(cnn);
// test!(comment_inside_script_parsing);
// test!(daringfireball_1);
// test!(ehow_1);
// test!(ehow_2);
// test!(embedded_videos);
// test!(engadget);
// test!(gmw);
// test!(guardian_1);
// test!(heise);
// test!(herald_sun_1);
// test!(hidden_nodes);
// test!(hukumusume);
// test!(iab_1);
// test!(ietf_1);
// test!(keep_images);
// test!(keep_tabular_data);
// test!(la_nacion);
// test!(lemonde_1);
// test!(lifehacker_post_comment_load);
// test!(lifehacker_working);
// test!(links_in_tables);
// test!(lwn_1);
// test!(medicalnewstoday);
// test!(medium_1);
// test!(medium_3);
// test!(mercurial);
// test!(missing_paragraphs);
// test!(mozilla_1);
// test!(mozilla_2);
// test!(normalize_spaces);
// test!(nytimes_1);
// test!(nytimes_2);
// test!(nytimes_3);
// test!(nytimes_4);
// test!(qq);
// test!(remove_extra_brs);
// test!(remove_extra_paragraphs);
// test!(remove_script_tags);
// test!(reordering_paragraphs);
// test!(replace_brs);
// test!(replace_font_tags);
// test!(salon_1);
// test!(seattletimes_1);
// test!(simplyfound_3);
// test!(social_buttons);
// test!(style_tags_removal);
// test!(svg_parsing);
// test!(table_style_attributes);
// test!(telegraph);
// test!(tmz_1);
// test!(videos_1);
// test!(wapo_1);
// test!(wapo_2);
// test!(webmd_1);
// test!(webmd_2);
// test!(wikipedia);
// test!(yahoo_1);
// test!(yahoo_2);
// test!(yahoo_3);
// test!(youth);