#![cfg(feature = "parallel")]
use super::*;
use crate::reference::Bibliography;
use crate::render::plain::PlainText;
use citum_schema::options::{
AndOptions, BibliographyOptions, Config, ContributorConfig, Processing,
};
use citum_schema::reference::{
Contributor, DateValue, Monograph, MonographType, MultilingualString, StructuredName, Title,
};
use citum_schema::template::{
ContributorForm, ContributorRole, NumberVariable, Rendering, TemplateComponent,
TemplateContributor, TemplateNumber, TemplateTitle, TitleType, WrapPunctuation,
};
use citum_schema::{BibliographySpec, Style, StyleInfo};
fn make_ref(id: &str, family: &str, given: &str, year: i32) -> Reference {
Reference::Monograph(Box::new(Monograph {
id: Some(id.into()),
r#type: MonographType::Book,
title: Some(Title::Single(format!("Title {id}"))),
author: Some(Contributor::StructuredName(StructuredName {
family: MultilingualString::Simple(family.to_string()),
given: MultilingualString::Simple(given.to_string()),
suffix: None,
dropping_particle: None,
non_dropping_particle: None,
})),
issued: DateValue::new(year.to_string()),
..Default::default()
}))
}
fn make_bibliography(n: usize) -> Bibliography {
let mut bib = Bibliography::new();
for i in 0..n {
let run = i / 3;
let family = format!("Author{run:03}");
let id = format!("ref{i:03}");
bib.insert(
id.clone(),
make_ref(
&id,
&family,
"Test",
2000 + i32::try_from(i % 20).unwrap_or(0),
),
);
}
bib
}
fn author_date_style() -> Style {
Style {
info: StyleInfo {
title: Some("Parallel Bibliography Equality Test".to_string()),
id: Some("parallel-bibliography-equality-test".into()),
..Default::default()
},
options: Some(Config {
processing: Some(Processing::AuthorDate),
contributors: Some(ContributorConfig {
and: Some(AndOptions::Text),
..Default::default()
}),
..Default::default()
}),
bibliography: Some(BibliographySpec {
options: Some(BibliographyOptions {
subsequent_author_substitute: Some("———".to_string()),
..Default::default()
}),
template: Some(vec![
TemplateComponent::Contributor(TemplateContributor {
contributor: ContributorRole::Author.into(),
form: ContributorForm::Long,
..Default::default()
}),
TemplateComponent::Title(TemplateTitle {
title: TitleType::Primary,
rendering: Rendering {
prefix: Some(", ".into()),
..Default::default()
},
..Default::default()
}),
]),
..Default::default()
}),
..Default::default()
}
}
fn numeric_style() -> Style {
let mut style = author_date_style();
style.options.as_mut().unwrap().processing = Some(Processing::Numeric);
let Some(bibliography) = style.bibliography.as_mut() else {
unreachable!("author_date_style always sets bibliography");
};
bibliography.template = Some(vec![
TemplateComponent::Number(TemplateNumber {
number: NumberVariable::CitationNumber,
rendering: Rendering {
wrap: Some(WrapPunctuation::Brackets.into()),
suffix: Some(" ".into()),
..Default::default()
},
..Default::default()
}),
TemplateComponent::Contributor(TemplateContributor {
contributor: ContributorRole::Author.into(),
form: ContributorForm::Long,
..Default::default()
}),
TemplateComponent::Title(TemplateTitle {
title: TitleType::Primary,
rendering: Rendering {
prefix: Some(", ".into()),
..Default::default()
},
..Default::default()
}),
]);
style
}
#[test]
fn given_large_author_date_bibliography_when_rendered_sequential_or_parallel_then_entries_match() {
let processor = Processor::new(author_date_style(), make_bibliography(48));
let run = processor.begin_run().finalize();
let sorted_refs = processor.sort_references(processor.bibliography.values().collect());
let numbered_refs = number_sorted_refs(sorted_refs.into_iter(), &run);
let ctx = processor.flat_render_context(&run);
let sequential_rendered =
processor.render_numbered_refs_sequential::<PlainText>(&numbered_refs, &ctx);
let parallel_rendered =
processor.render_numbered_refs_parallel::<PlainText>(&numbered_refs, &ctx);
let substitute = ctx
.bibliography_config
.subsequent_author_substitute
.as_ref();
let sequential_entries =
processor.apply_substitution_post_pass::<PlainText>(sequential_rendered, substitute, &ctx);
let parallel_entries =
processor.apply_substitution_post_pass::<PlainText>(parallel_rendered, substitute, &ctx);
assert_eq!(sequential_entries.len(), 48);
assert_eq!(sequential_entries, parallel_entries);
let substituted_count = sequential_entries
.iter()
.filter(|entry| {
entry
.template
.iter()
.any(|component| component.value == "———")
})
.count();
assert!(
substituted_count > 0,
"expected subsequent-author substitution to trigger at least once \
across a 48-entry bibliography with three-entry author runs"
);
}
#[test]
fn given_numeric_bibliography_when_rendered_sequential_or_parallel_then_entries_match() {
let processor = Processor::new(numeric_style(), make_bibliography(40));
let run = processor.begin_run().finalize();
let sorted_refs = processor.sort_references(processor.bibliography.values().collect());
let numbered_refs = number_sorted_refs(sorted_refs.into_iter(), &run);
let ctx = processor.flat_render_context(&run);
let sequential_rendered =
processor.render_numbered_refs_sequential::<PlainText>(&numbered_refs, &ctx);
let parallel_rendered =
processor.render_numbered_refs_parallel::<PlainText>(&numbered_refs, &ctx);
let substitute = ctx
.bibliography_config
.subsequent_author_substitute
.as_ref();
let sequential_entries =
processor.apply_substitution_post_pass::<PlainText>(sequential_rendered, substitute, &ctx);
let parallel_entries =
processor.apply_substitution_post_pass::<PlainText>(parallel_rendered, substitute, &ctx);
assert_eq!(sequential_entries.len(), 40);
assert_eq!(sequential_entries, parallel_entries);
}
#[test]
fn given_large_bibliography_group_when_rendered_sequential_or_parallel_then_entries_match() {
let processor = Processor::new(author_date_style(), make_bibliography(48));
let run = processor.begin_run().finalize();
let sorted_refs = processor.sort_references(processor.bibliography.values().collect());
let numbered_refs = number_sorted_refs(sorted_refs.into_iter(), &run);
let ctx = EntryRenderContext {
style: &processor.style,
hints: &processor.hints,
config: Arc::new(processor.get_bibliography_config().into_owned()),
bibliography_config: Arc::new(processor.get_bibliography_options().into_owned()),
run: &run,
};
let sequential_rendered =
processor.render_numbered_refs_sequential::<PlainText>(&numbered_refs, &ctx);
let parallel_rendered =
processor.render_numbered_refs_parallel::<PlainText>(&numbered_refs, &ctx);
let substitute = ctx
.bibliography_config
.subsequent_author_substitute
.as_ref();
let sequential_entries =
processor.apply_substitution_post_pass::<PlainText>(sequential_rendered, substitute, &ctx);
let parallel_entries =
processor.apply_substitution_post_pass::<PlainText>(parallel_rendered, substitute, &ctx);
assert_eq!(sequential_entries.len(), 48);
assert_eq!(sequential_entries, parallel_entries);
}
#[test]
fn given_bibliography_size_when_dispatching_then_selected_path_matches_sequential_output() {
for size in [8, 48] {
let processor = Processor::new(author_date_style(), make_bibliography(size));
let run = processor.begin_run().finalize();
let sorted_refs = processor.sort_references(processor.bibliography.values().collect());
let numbered_refs = number_sorted_refs(sorted_refs.into_iter(), &run);
let ctx = processor.flat_render_context(&run);
let dispatched = processor.render_numbered_refs::<PlainText>(&numbered_refs, &ctx);
let sequential =
processor.render_numbered_refs_sequential::<PlainText>(&numbered_refs, &ctx);
assert_eq!(
dispatched, sequential,
"dispatch changed output for {size} entries"
);
let expected = processor.apply_substitution_post_pass::<PlainText>(
sequential,
ctx.bibliography_config
.subsequent_author_substitute
.as_ref(),
&ctx,
);
let actual = processor
.process_references_with_format::<PlainText>(&run)
.bibliography;
assert_eq!(
actual, expected,
"public rendering changed output for {size} entries"
);
}
}