added unit tests for write_shared

This commit is contained in:
EtomicBomb 2024-07-25 15:27:29 +00:00
parent 17c89239d9
commit 67663fc680
3 changed files with 256 additions and 50 deletions

View file

@ -80,3 +80,6 @@ impl fmt::Display for EscapedJson {
write!(f, "{}", json)
}
}
#[cfg(test)]
mod tests;

View file

@ -15,7 +15,6 @@
use std::any::Any;
use std::cell::RefCell;
use std::collections::hash_map::Entry;
use std::ffi::OsString;
use std::fs::File;
use std::io::BufWriter;
@ -52,7 +51,7 @@ use crate::html::layout;
use crate::html::render::search_index::build_index;
use crate::html::render::search_index::SerializedSearchIndex;
use crate::html::render::sorted_json::{EscapedJson, SortedJson};
use crate::html::render::sorted_template::{self, SortedTemplate};
use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate};
use crate::html::render::{AssocItemLink, ImplRenderingParameters};
use crate::html::static_files::{self, suffix_path};
use crate::visit::DocVisitor;
@ -78,33 +77,29 @@ pub(crate) fn write_shared(
let crate_name = krate.name(cx.tcx());
let crate_name = crate_name.as_str(); // rand
let crate_name_json = SortedJson::serialize(crate_name); // "rand"
let external_crates = hack_get_external_crate_names(cx)?;
let external_crates = hack_get_external_crate_names(&cx.dst)?;
let info = CrateInfo {
src_files_js: SourcesPart::get(cx, &crate_name_json)?,
search_index_js: SearchIndexPart::get(cx, index)?,
search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?,
all_crates: AllCratesPart::get(crate_name_json.clone())?,
crates_index: CratesIndexPart::get(&crate_name, &external_crates)?,
trait_impl: TraitAliasPart::get(cx, &crate_name_json)?,
type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?,
};
let crates_info = vec![info]; // we have info from just one crate
let crates = vec![info]; // we have info from just one crate. rest will found in out dir
write_static_files(cx, &opt)?;
let dst = &cx.dst;
if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) {
if cx.include_sources {
write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, &crates_info)?;
write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, &crates)?;
}
write_rendered_cci::<SearchIndexPart, _>(
SearchIndexPart::blank,
dst,
&crates_info,
)?;
write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, &crates_info)?;
write_rendered_cci::<SearchIndexPart, _>(SearchIndexPart::blank, dst, &crates)?;
write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, &crates)?;
}
write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, &crates_info)?;
write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, &crates_info)?;
write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, &crates)?;
write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, &crates)?;
match &opt.index_page {
Some(index_page) if opt.enable_index_page => {
let mut md_opts = opt.clone();
@ -119,7 +114,7 @@ pub(crate) fn write_shared(
write_rendered_cci::<CratesIndexPart, _>(
|| CratesIndexPart::blank(cx),
dst,
&crates_info,
&crates,
)?;
}
_ => {} // they don't want an index page
@ -189,7 +184,8 @@ fn write_search_desc(
let path = path.join(filename);
let part = SortedJson::serialize(&part);
let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})");
write_create_parents(&path, part)?;
create_parents(&path)?;
try_err!(fs::write(&path, part), &path);
}
Ok(())
}
@ -286,8 +282,11 @@ else if (window.initSearch) window.initSearch(searchIndex);",
)
}
fn get(cx: &Context<'_>, search_index: SortedJson) -> Result<PartsAndLocations<Self>, Error> {
let path = suffix_path("search-index.js", &cx.shared.resource_suffix);
fn get(
search_index: SortedJson,
resource_suffix: &str,
) -> Result<PartsAndLocations<Self>, Error> {
let path = suffix_path("search-index.js", resource_suffix);
let search_index = EscapedJson::from(search_index);
Ok(PartsAndLocations::with(path, search_index))
}
@ -319,8 +318,8 @@ impl AllCratesPart {
///
/// This is to match the current behavior of rustdoc, which allows you to get all crates
/// on the index page, even if --enable-index-page is only passed to the last crate.
fn hack_get_external_crate_names(cx: &Context<'_>) -> Result<Vec<String>, Error> {
let path = cx.dst.join("crates.js");
fn hack_get_external_crate_names(doc_root: &Path) -> Result<Vec<String>, Error> {
let path = doc_root.join("crates.js");
let Ok(content) = fs::read_to_string(&path) else {
// they didn't emit invocation specific, so we just say there were no crates
return Ok(Vec::default());
@ -361,7 +360,7 @@ impl CratesIndexPart {
match SortedTemplate::magic(&template, MAGIC) {
Ok(template) => template,
Err(e) => panic!(
"{e}: Object Replacement Character (U+FFFC) should not appear in the --index-page"
"Object Replacement Character (U+FFFC) should not appear in the --index-page: {e}"
),
}
}
@ -860,6 +859,21 @@ impl Serialize for AliasSerializableImpl {
}
}
fn get_path_parts<T: CciPart>(
dst: &Path,
crates_info: &[CrateInfo],
) -> FxHashMap<PathBuf, Vec<String>> {
let mut templates: FxHashMap<PathBuf, Vec<String>> = FxHashMap::default();
crates_info.iter().map(|crate_info| crate_info.get::<T>().parts.iter()).flatten().for_each(
|(path, part)| {
let path = dst.join(&path);
let part = part.to_string();
templates.entry(path).or_default().push(part);
},
);
templates
}
/// Create all parents
fn create_parents(path: &Path) -> Result<(), Error> {
let parent = path.parent().expect("should not have an empty path here");
@ -867,20 +881,13 @@ fn create_parents(path: &Path) -> Result<(), Error> {
Ok(())
}
/// Create parents and then write
fn write_create_parents(path: &Path, content: String) -> Result<(), Error> {
create_parents(path)?;
try_err!(fs::write(path, content), path);
Ok(())
}
/// Returns a blank template unless we could find one to append to
fn read_template_or_blank<F, T: CciPart>(
fn read_template_or_blank<F, T: FileFormat>(
mut make_blank: F,
path: &Path,
) -> Result<SortedTemplate<T::FileFormat>, Error>
) -> Result<SortedTemplate<T>, Error>
where
F: FnMut() -> SortedTemplate<T::FileFormat>,
F: FnMut() -> SortedTemplate<T>,
{
match fs::read_to_string(&path) {
Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)),
@ -898,27 +905,14 @@ fn write_rendered_cci<T: CciPart, F>(
where
F: FnMut() -> SortedTemplate<T::FileFormat>,
{
// read parts from disk
let path_parts =
crates_info.iter().map(|crate_info| crate_info.get::<T>().parts.iter()).flatten();
// read previous rendered cci from storage, append to them
let mut templates: FxHashMap<PathBuf, SortedTemplate<T::FileFormat>> = Default::default();
for (path, part) in path_parts {
let part = format!("{part}");
let path = dst.join(&path);
match templates.entry(path.clone()) {
Entry::Vacant(entry) => {
let template = read_template_or_blank::<_, T>(&mut make_blank, &path)?;
let template = entry.insert(template);
template.append(part);
}
Entry::Occupied(mut t) => t.get_mut().append(part),
}
}
// write the merged cci to disk
for (path, template) in templates {
for (path, parts) in get_path_parts::<T>(dst, crates_info) {
create_parents(&path)?;
// read previous rendered cci from storage, append to them
let mut template = read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path)?;
for part in parts {
template.append(part);
}
let file = try_err!(File::create(&path), &path);
let mut file = BufWriter::new(file);
try_err!(write!(file, "{template}"), &path);
@ -926,3 +920,6 @@ where
}
Ok(())
}
#[cfg(test)]
mod tests;

View file

@ -0,0 +1,206 @@
use crate::html::render::sorted_json::{EscapedJson, SortedJson};
use crate::html::render::sorted_template::{Html, SortedTemplate};
use crate::html::render::write_shared::*;
#[test]
fn hack_external_crate_names() {
let path = tempfile::TempDir::new().unwrap();
let path = path.path();
let crates = hack_get_external_crate_names(&path).unwrap();
assert!(crates.is_empty());
fs::write(path.join("crates.js"), r#"window.ALL_CRATES = ["a","b","c"];"#).unwrap();
let crates = hack_get_external_crate_names(&path).unwrap();
assert_eq!(crates, ["a".to_string(), "b".to_string(), "c".to_string()]);
}
fn but_last_line(s: &str) -> &str {
let (before, _) = s.rsplit_once("\n").unwrap();
before
}
#[test]
fn sources_template() {
let mut template = SourcesPart::blank();
assert_eq!(
but_last_line(&template.to_string()),
r"var srcIndex = new Map(JSON.parse('[]'));
createSrcSidebar();"
);
template.append(EscapedJson::from(SortedJson::serialize("u")).to_string());
assert_eq!(
but_last_line(&template.to_string()),
r#"var srcIndex = new Map(JSON.parse('["u"]'));
createSrcSidebar();"#
);
template.append(EscapedJson::from(SortedJson::serialize("v")).to_string());
assert_eq!(
but_last_line(&template.to_string()),
r#"var srcIndex = new Map(JSON.parse('["u","v"]'));
createSrcSidebar();"#
);
}
#[test]
fn sources_parts() {
let parts = SearchIndexPart::get(SortedJson::serialize(["foo", "bar"]), "suffix").unwrap();
assert_eq!(&parts.parts[0].0, Path::new("search-indexsuffix.js"));
assert_eq!(&parts.parts[0].1.to_string(), r#"["foo","bar"]"#);
}
#[test]
fn all_crates_template() {
let mut template = AllCratesPart::blank();
assert_eq!(but_last_line(&template.to_string()), r"window.ALL_CRATES = [];");
template.append(EscapedJson::from(SortedJson::serialize("b")).to_string());
assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["b"];"#);
template.append(EscapedJson::from(SortedJson::serialize("a")).to_string());
assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["a","b"];"#);
}
#[test]
fn all_crates_parts() {
let parts = AllCratesPart::get(SortedJson::serialize("crate")).unwrap();
assert_eq!(&parts.parts[0].0, Path::new("crates.js"));
assert_eq!(&parts.parts[0].1.to_string(), r#""crate""#);
}
#[test]
fn search_index_template() {
let mut template = SearchIndexPart::blank();
assert_eq!(
but_last_line(&template.to_string()),
r"var searchIndex = new Map(JSON.parse('[]'));
if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
else if (window.initSearch) window.initSearch(searchIndex);"
);
template.append(EscapedJson::from(SortedJson::serialize([1, 2])).to_string());
assert_eq!(
but_last_line(&template.to_string()),
r"var searchIndex = new Map(JSON.parse('[[1,2]]'));
if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
else if (window.initSearch) window.initSearch(searchIndex);"
);
template.append(EscapedJson::from(SortedJson::serialize([4, 3])).to_string());
assert_eq!(
but_last_line(&template.to_string()),
r"var searchIndex = new Map(JSON.parse('[[1,2],[4,3]]'));
if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
else if (window.initSearch) window.initSearch(searchIndex);"
);
}
#[test]
fn crates_index_part() {
let external_crates = ["bar".to_string(), "baz".to_string()];
let mut parts = CratesIndexPart::get("foo", &external_crates).unwrap();
parts.parts.sort_by(|a, b| a.1.to_string().cmp(&b.1.to_string()));
assert_eq!(&parts.parts[0].0, Path::new("index.html"));
assert_eq!(&parts.parts[0].1.to_string(), r#"<li><a href="bar/index.html">bar</a></li>"#);
assert_eq!(&parts.parts[1].0, Path::new("index.html"));
assert_eq!(&parts.parts[1].1.to_string(), r#"<li><a href="baz/index.html">baz</a></li>"#);
assert_eq!(&parts.parts[2].0, Path::new("index.html"));
assert_eq!(&parts.parts[2].1.to_string(), r#"<li><a href="foo/index.html">foo</a></li>"#);
}
#[test]
fn trait_alias_template() {
let mut template = TraitAliasPart::blank();
assert_eq!(
but_last_line(&template.to_string()),
r#"(function() {
var implementors = Object.fromEntries([]);
if (window.register_implementors) {
window.register_implementors(implementors);
} else {
window.pending_implementors = implementors;
}
})()"#,
);
template.append(SortedJson::serialize(["a"]).to_string());
assert_eq!(
but_last_line(&template.to_string()),
r#"(function() {
var implementors = Object.fromEntries([["a"]]);
if (window.register_implementors) {
window.register_implementors(implementors);
} else {
window.pending_implementors = implementors;
}
})()"#,
);
template.append(SortedJson::serialize(["b"]).to_string());
assert_eq!(
but_last_line(&template.to_string()),
r#"(function() {
var implementors = Object.fromEntries([["a"],["b"]]);
if (window.register_implementors) {
window.register_implementors(implementors);
} else {
window.pending_implementors = implementors;
}
})()"#,
);
}
#[test]
fn type_alias_template() {
let mut template = TypeAliasPart::blank();
assert_eq!(
but_last_line(&template.to_string()),
r#"(function() {
var type_impls = Object.fromEntries([]);
if (window.register_type_impls) {
window.register_type_impls(type_impls);
} else {
window.pending_type_impls = type_impls;
}
})()"#,
);
template.append(SortedJson::serialize(["a"]).to_string());
assert_eq!(
but_last_line(&template.to_string()),
r#"(function() {
var type_impls = Object.fromEntries([["a"]]);
if (window.register_type_impls) {
window.register_type_impls(type_impls);
} else {
window.pending_type_impls = type_impls;
}
})()"#,
);
template.append(SortedJson::serialize(["b"]).to_string());
assert_eq!(
but_last_line(&template.to_string()),
r#"(function() {
var type_impls = Object.fromEntries([["a"],["b"]]);
if (window.register_type_impls) {
window.register_type_impls(type_impls);
} else {
window.pending_type_impls = type_impls;
}
})()"#,
);
}
#[test]
fn read_template_test() {
let path = tempfile::TempDir::new().unwrap();
let path = path.path().join("file.html");
let make_blank = || SortedTemplate::<Html>::before_after("<div>", "</div>");
let template = read_template_or_blank(make_blank, &path).unwrap();
assert_eq!(but_last_line(&template.to_string()), "<div></div>");
fs::write(&path, template.to_string()).unwrap();
let mut template = read_template_or_blank(make_blank, &path).unwrap();
template.append("<img/>".to_string());
fs::write(&path, template.to_string()).unwrap();
let mut template = read_template_or_blank(make_blank, &path).unwrap();
template.append("<br/>".to_string());
fs::write(&path, template.to_string()).unwrap();
let template = read_template_or_blank(make_blank, &path).unwrap();
assert_eq!(but_last_line(&template.to_string()), "<div><br/><img/></div>");
}