use crate::core::backend::GeneratedFile;
use crate::core::config::ResolvedCrateConfig;
use crate::core::config::e2e::SnippetConfig;
use crate::e2e::config::E2eConfig;
use crate::e2e::diagnostic_log::DiagnosticLog;
use crate::e2e::{generate_e2e_with_extensions, snippets};
use std::path::{Path, PathBuf};
const FIRST_LANGUAGE: &str = "rust";
const SECOND_LANGUAGE: &str = "python";
const TOPIC: &str = "api";
const FIXTURE_ID: &str = "list_records";
fn write_documented_fixture(directory: &Path) {
std::fs::write(
directory.join("list_records.json"),
serde_json::json!({
"id": FIXTURE_ID,
"description": "lists records",
"docs": { "topic": TOPIC },
})
.to_string(),
)
.expect("write documented fixture");
}
fn e2e_config(fixtures: &Path, snippet_output: &str) -> E2eConfig {
let mut config = E2eConfig {
fixtures: fixtures.display().to_string(),
output: "e2e".to_string(),
languages: vec![FIRST_LANGUAGE.to_string(), SECOND_LANGUAGE.to_string()],
snippets: Some(SnippetConfig {
output: snippet_output.to_string(),
languages: vec![FIRST_LANGUAGE.to_string(), SECOND_LANGUAGE.to_string()],
..SnippetConfig::default()
}),
..E2eConfig::default()
};
config.call.function = "convert".to_string();
config
}
struct Rendered {
files: Vec<GeneratedFile>,
ledger: snippets::SnippetCoverageLedger,
deferred: Option<String>,
snippet_output: String,
}
fn render(snippet_output: &str, languages: Option<&[String]>) -> Rendered {
let directory = tempfile::tempdir().expect("temporary fixture directory");
write_documented_fixture(directory.path());
let (files, deferred_error) = generate_e2e_with_extensions(
&ResolvedCrateConfig::default(),
&e2e_config(directory.path(), snippet_output),
languages,
&[],
&[],
&[],
&[],
&[],
&DiagnosticLog::new(),
)
.expect("the e2e run must render, deferring any per-backend failure rather than aborting");
let deferred = deferred_error.map(|error| format!("{error:#}"));
let manifest_path = Path::new(snippet_output).join(snippets::COVERAGE_MANIFEST);
let manifest = files.iter().find(|file| file.path == manifest_path).unwrap_or_else(|| {
panic!(
"the snippet stage produced no coverage manifest at {}, so it did not run at all. \
Deferred failure: {deferred:?}",
manifest_path.display()
)
});
let ledger = serde_json::from_str(&manifest.content).expect("the coverage manifest must parse");
Rendered {
files,
ledger,
deferred,
snippet_output: snippet_output.to_string(),
}
}
impl Rendered {
fn snippet_path(&self, language: &str) -> PathBuf {
Path::new(&self.snippet_output)
.join(language)
.join(TOPIC)
.join(format!("{FIXTURE_ID}.md"))
}
fn wrote(&self, language: &str) -> bool {
let path = self.snippet_path(language);
self.files.iter().any(|file| file.path == path)
}
fn snippet_paths(&self) -> Vec<String> {
self.files
.iter()
.filter(|file| file.path.starts_with(&self.snippet_output))
.map(|file| file.path.display().to_string())
.collect()
}
fn assert_the_fixture_rendered_for_every_language(&self) {
assert!(
self.ledger.missing.is_empty(),
"the test fixture rendered no snippet for {} cell(s), so nothing below is measuring \
the language filter -- fix the fixture, not the assertions. Gaps: {}. Deferred \
failure: {:?}",
self.ledger.missing.len(),
self.ledger
.missing
.iter()
.map(|missing| format!(
"`{}`/`{}`: {}",
missing.key.fixture_id, missing.key.language, missing.reason
))
.collect::<Vec<_>>()
.join("; "),
self.deferred
);
for language in [FIRST_LANGUAGE, SECOND_LANGUAGE] {
assert!(
self.ledger.generated.iter().any(|key| key.language == language),
"the test fixture must render for `{language}` in every mode; the ledger recorded \
{:?}. Deferred failure: {:?}",
self.ledger.generated,
self.deferred
);
}
assert!(
!self.ledger.generated_metadata.is_empty(),
"a ledger with no generated-path entries is empty, not whole; nothing downstream can \
assert it survived anything. Deferred failure: {:?}",
self.deferred
);
}
}
#[test]
fn a_language_filtered_run_writes_no_other_languages_snippet_files() {
let rendered = render("docs/lang-scope-filtered", Some(&[FIRST_LANGUAGE.to_string()]));
rendered.assert_the_fixture_rendered_for_every_language();
assert!(
rendered.wrote(FIRST_LANGUAGE),
"the language that WAS named must still be regenerated: {:?}",
rendered.snippet_paths()
);
assert!(
!rendered.wrote(SECOND_LANGUAGE),
"a `--lang {FIRST_LANGUAGE}` run must not rewrite {SECOND_LANGUAGE}'s snippets: {:?}",
rendered.snippet_paths()
);
}
#[test]
fn an_unfiltered_run_still_writes_every_languages_snippet_files() {
let rendered = render("docs/lang-scope-unfiltered", None);
rendered.assert_the_fixture_rendered_for_every_language();
for language in [FIRST_LANGUAGE, SECOND_LANGUAGE] {
assert!(
rendered.wrote(language),
"an unfiltered run must still write `{language}`'s snippets: {:?}",
rendered.snippet_paths()
);
}
}
#[test]
fn a_language_filtered_run_leaves_the_shared_coverage_ledger_whole() {
let rendered = render("docs/lang-scope-ledger", Some(&[FIRST_LANGUAGE.to_string()]));
rendered.assert_the_fixture_rendered_for_every_language();
for language in [FIRST_LANGUAGE, SECOND_LANGUAGE] {
assert!(
rendered.ledger.expected.iter().any(|key| key.language == language),
"`{language}` must stay in the ledger's expected set: {:?}",
rendered.ledger.expected
);
assert!(
rendered
.ledger
.generated_metadata
.iter()
.any(|entry| entry.key.language == language),
"`{language}`'s generated-path ownership entry must survive a filtered run: {:?}",
rendered.ledger.generated_metadata
);
}
}