use super::{
ABANDONED_REASON, EmbeddedLinter, LINT_QUIET_WINDOW, embedded_linter, embedded_linter_with, guard_failure_reason,
};
use crate::config_schema::{MarkdownAutofixOptions, MarkdownRule, PolicyConfig};
use crate::markdown_linter_guard::GuardFailure;
use crate::markdown_linter_output::{EXPLANATION_LIMIT, LintRun, RemainingFinding};
use crate::policy_markdown::{LintRequest, MarkdownLinter};
use crate::test_support::{fixture, remove};
use monochromatic_lint::run_embedded::{EmbeddedFailure, EmbeddedRequest, EmbeddedRun};
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
const SHORT_WINDOW: Duration = Duration::from_millis(100);
fn default_options() -> MarkdownAutofixOptions {
return PolicyConfig::defaults().markdown_autofix;
}
fn lint(
linter: &mut EmbeddedLinter,
top_level: &Path,
path: &[u8],
source: &[u8],
) -> Result<LintRun, String> {
let options: MarkdownAutofixOptions = default_options();
return linter.lint(&LintRequest {
top_level,
options: &options,
path,
source,
});
}
#[test]
fn the_executable_linter_uses_its_labelled_unmeasured_window() {
assert_eq!(embedded_linter().window, LINT_QUIET_WINDOW);
}
static STALL_STARTS: AtomicUsize = AtomicUsize::new(0);
fn stalls(_request: &EmbeddedRequest<'_>) -> Result<EmbeddedRun, EmbeddedFailure> {
STALL_STARTS.fetch_add(1, Ordering::SeqCst);
loop {
std::thread::park();
}
}
fn panics(_request: &EmbeddedRequest<'_>) -> Result<EmbeddedRun, EmbeddedFailure> {
panic!("planted panic in the linter");
}
const NEVER_PRODUCED_STATUS: u8 = 1;
fn ends_with_error_status(_request: &EmbeddedRequest<'_>) -> Result<EmbeddedRun, EmbeddedFailure> {
return Err(EmbeddedFailure {
exit_status: NEVER_PRODUCED_STATUS,
explanation: String::from("planted"),
});
}
fn echoes(request: &EmbeddedRequest<'_>) -> Result<EmbeddedRun, EmbeddedFailure> {
return Ok(EmbeddedRun {
fixed: String::from_utf8_lossy(request.source).into_owned(),
findings: Vec::new(),
});
}
#[test]
fn a_stalled_linter_is_abandoned_when_it_shows_no_sign_of_work() {
let root: PathBuf = fixture("linter-stall");
let mut linter: EmbeddedLinter = embedded_linter_with(stalls, SHORT_WINDOW);
let reason: String = lint(&mut linter, root.as_path(), b"a.md", b"a\n").expect_err("abandoned");
assert_eq!(reason, guard_failure_reason(&GuardFailure::Quiet(SHORT_WINDOW)));
assert!(reason.contains(SHORT_WINDOW.as_millis().to_string().as_str()), "{reason}");
assert!(reason.contains("markdown/autofix"), "{reason}");
let starts: usize = STALL_STARTS.load(Ordering::SeqCst);
let later: String = lint(&mut linter, root.as_path(), b"b.md", b"b\n").expect_err("refused");
assert_eq!(later, ABANDONED_REASON);
assert_eq!(STALL_STARTS.load(Ordering::SeqCst), starts, "no second thread started");
remove(root.as_path());
}
fn panics_with_a_long_message(_request: &EmbeddedRequest<'_>) -> Result<EmbeddedRun, EmbeddedFailure> {
panic!("{}", "p".repeat(EXPLANATION_LIMIT * 3));
}
#[test]
fn a_long_panic_message_is_bounded() {
let root: PathBuf = fixture("linter-long-panic");
let mut linter: EmbeddedLinter = embedded_linter_with(panics_with_a_long_message, LINT_QUIET_WINDOW);
let reason: String = lint(&mut linter, root.as_path(), b"a.md", b"a\n").expect_err("reported");
assert!(reason.contains(format!("{}...", "p".repeat(EXPLANATION_LIMIT)).as_str()), "{reason}");
assert!(!reason.contains("p".repeat(EXPLANATION_LIMIT + 1).as_str()), "the payload is cut");
remove(root.as_path());
}
#[test]
fn a_panicking_linter_is_reported_and_the_wrapper_goes_on() {
let root: PathBuf = fixture("linter-panic");
let mut linter: EmbeddedLinter = embedded_linter_with(panics, LINT_QUIET_WINDOW);
let reason: String = lint(&mut linter, root.as_path(), b"a.md", b"a\n").expect_err("reported");
assert!(reason.contains("planted panic in the linter"), "{reason}");
assert!(reason.contains("internal error"), "{reason}");
let mut healthy: EmbeddedLinter = embedded_linter_with(echoes, LINT_QUIET_WINDOW);
assert_eq!(
lint(&mut healthy, root.as_path(), b"a.md", b"a\n"),
Ok(LintRun {
fixed: b"a\n".to_vec(),
remaining: Vec::new(),
})
);
remove(root.as_path());
}
#[test]
fn an_unexpected_status_is_refused() {
let root: PathBuf = fixture("linter-status");
let mut linter: EmbeddedLinter = embedded_linter_with(ends_with_error_status, LINT_QUIET_WINDOW);
let reason: String = lint(&mut linter, root.as_path(), b"a.md", b"a\n").expect_err("refused");
assert!(reason.contains(format!("status {NEVER_PRODUCED_STATUS}").as_str()), "{reason}");
remove(root.as_path());
}
#[test]
fn guard_failures_read_as_sentences() {
let thread: String = guard_failure_reason(&GuardFailure::Start(String::from("no threads")));
assert!(thread.contains("no threads"), "{thread}");
let vanished: String = guard_failure_reason(&GuardFailure::Vanished);
assert!(vanished.contains("without a result"), "{vanished}");
}
#[test]
fn a_candidate_name_is_the_exact_bytes_the_linter_is_given() {
let root: PathBuf = fixture("linter-name");
fn checks_the_name(request: &EmbeddedRequest<'_>) -> Result<EmbeddedRun, EmbeddedFailure> {
if request.name.as_os_str().as_bytes() != EXACT_NAME {
return Err(EmbeddedFailure {
exit_status: NEVER_PRODUCED_STATUS + 1,
explanation: String::from("the name was converted"),
});
}
return Ok(EmbeddedRun {
fixed: String::from("ok"),
findings: Vec::new(),
});
}
let mut linter: EmbeddedLinter = embedded_linter_with(checks_the_name, LINT_QUIET_WINDOW);
let run: LintRun = lint(&mut linter, root.as_path(), EXACT_NAME, b"a\n").expect("the name arrived unchanged");
assert_eq!(run.fixed, b"ok".to_vec());
remove(root.as_path());
}
const EXACT_NAME: &[u8] = b"-x dir/caf\xe9 =.md";
fn lfs_tree(root: &Path) -> PathBuf {
let top: PathBuf = root.join("top");
std::fs::create_dir_all(top.join("pkg/asset")).expect("tree");
std::fs::write(
top.join(".lfsconfig"),
b"[lfs]\n\turl = https://lfs.example.com/org/repo\n",
)
.expect("lfsconfig");
std::fs::write(
top.join(".gitattributes"),
b"*.png filter=lfs diff=lfs merge=lfs -text\n",
)
.expect("attributes");
std::fs::write(top.join("pkg/asset/shot.png"), b"image bytes").expect("image");
return top;
}
const SHOT_URL: &str = "https://lfs.example.com/org/repo/de7030234493a8bea844dbe1d8676e68a2c1a4b014c721f0425a22b6df66faec/pkg/asset/shot.png";
#[test]
fn the_linked_linter_meets_the_contract() {
let root: PathBuf = fixture("linter-real");
let top: PathBuf = lfs_tree(root.as_path());
let mut linter: EmbeddedLinter = embedded_linter();
let rewritten: String = format!("# T\n\n\n");
for name in [&b"pkg/README.md"[..], b"pkg/caf\xe9.md", b"pkg/-x.mdx"] {
assert_eq!(
lint(&mut linter, top.as_path(), name, b"# T\n\n\n"),
Ok(LintRun {
fixed: rewritten.clone().into_bytes(),
remaining: Vec::new(),
}),
"{:?}",
std::ffi::OsStr::from_bytes(name)
);
}
assert_eq!(
lint(&mut linter, top.as_path(), b"pkg/README.md", rewritten.as_bytes()),
Ok(LintRun {
fixed: rewritten.clone().into_bytes(),
remaining: Vec::new(),
})
);
let stale: String = format!(
"\n",
"b".repeat(64)
);
assert_eq!(
lint(&mut linter, top.as_path(), b"pkg/README.md", stale.as_bytes()),
Ok(LintRun {
fixed: stale.clone().into_bytes(),
remaining: vec![RemainingFinding {
rule: MarkdownRule::LfsImageUrl,
line: 1,
column: 1,
message: String::from(
"Object URL names pkg/asset/gone.png, which no longer exists in the repository."
),
}],
})
);
let mut options: MarkdownAutofixOptions = default_options();
options.exclude = vec![String::from("pkg/")];
let unchanged: &[u8] = b"\n";
assert_eq!(
linter.lint(&LintRequest {
top_level: top.as_path(),
options: &options,
path: b"pkg/README.md",
source: unchanged,
}),
Ok(LintRun {
fixed: unchanged.to_vec(),
remaining: Vec::new(),
})
);
remove(root.as_path());
}
const WORKING_WINDOW: Duration = Duration::from_millis(1200);
const WORKING_ROUNDS: u32 = 15;
fn works_then_answers(request: &EmbeddedRequest<'_>) -> Result<EmbeddedRun, EmbeddedFailure> {
let scratch: PathBuf = request.top_level.join("linter-progress");
for round in 0..WORKING_ROUNDS {
std::fs::write(&scratch, round.to_string()).expect("the fixture writes its progress");
std::thread::sleep(Duration::from_millis(200));
}
return echoes(request);
}
#[test]
fn a_linter_that_keeps_working_is_never_abandoned_however_long_it_runs() {
let root: PathBuf = fixture("linter-working");
let mut linter: EmbeddedLinter = embedded_linter_with(works_then_answers, WORKING_WINDOW);
let started: std::time::Instant = std::time::Instant::now();
let run: Result<LintRun, String> = lint(&mut linter, root.as_path(), b"a.md", b"a\n");
assert!(run.is_ok(), "{run:?}");
assert!(started.elapsed() > WORKING_WINDOW * 2, "the fixture ran only {:?}", started.elapsed());
remove(root.as_path());
}
#[test]
fn a_linter_that_keeps_working_is_abandoned_at_its_ceiling_naming_the_file() {
let root: PathBuf = fixture("linter-ceiling");
let ceiling: Duration = Duration::from_secs(2);
let mut linter: EmbeddedLinter = embedded_linter_with(works_then_answers, WORKING_WINDOW).with_ceiling(ceiling);
let reason: String = lint(&mut linter, root.as_path(), b"big.md", b"a\n").expect_err("abandoned at the ceiling");
assert!(reason.contains("monochromatic-lint") && reason.contains("big.md") && reason.contains(format!("{} seconds", ceiling.as_secs()).as_str()), "{reason}");
assert!(reason.contains("markdown/autofix") && reason.contains("by hand"), "{reason}");
let later: String = lint(&mut linter, root.as_path(), b"b.md", b"b\n").expect_err("refused");
assert_eq!(later, ABANDONED_REASON);
remove(root.as_path());
}
#[test]
fn the_shipped_ceiling_is_past_the_shipped_quiet_window() {
assert!(crate::wait_ceilings::LINT_CEILING > LINT_QUIET_WINDOW);
}