use std::fs;
use std::io::Write;
use std::path::Path;
use std::process::{Command, Output, Stdio};
const RUST_SOURCE: &str = r#"//! Crate docs.
//!
//!#Overview
use std::fmt;
#[derive(Debug)]
pub struct Widget {
pub name: String,
}
impl fmt::Display for Widget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "* {} *", self.name)
}
}
"#;
const RUST_SOURCE_FIXED: &str = r#"//! Crate docs.
//!
//!# Overview
use std::fmt;
#[derive(Debug)]
pub struct Widget {
pub name: String,
}
impl fmt::Display for Widget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "* {} *", self.name)
}
}
"#;
fn rumdl(dir: &Path, args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_rumdl"))
.current_dir(dir)
.args(args)
.output()
.expect("failed to execute rumdl")
}
fn rumdl_with_stdin(dir: &Path, content: &str, args: &[&str]) -> (String, String) {
let mut child = Command::new(env!("CARGO_BIN_EXE_rumdl"))
.current_dir(dir)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("failed to execute rumdl");
child
.stdin
.as_mut()
.unwrap()
.write_all(content.as_bytes())
.expect("failed to write stdin");
let output = child.wait_with_output().expect("failed to collect rumdl output");
(
String::from_utf8_lossy(&output.stdout).replace("\r\n", "\n"),
String::from_utf8_lossy(&output.stderr).replace("\r\n", "\n"),
)
}
#[test]
fn fmt_rewrites_a_rust_files_doc_comment_and_leaves_the_rust_alone() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("widget.rs"), RUST_SOURCE).unwrap();
let output = rumdl(
dir.path(),
&["fmt", "--color", "never", "--no-cache", "--no-config", "widget.rs"],
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(
fs::read_to_string(dir.path().join("widget.rs")).unwrap(),
RUST_SOURCE_FIXED,
"only the doc comment is markdown.\nstdout:\n{stdout}"
);
let diagnostics: Vec<&str> = stdout.lines().filter(|line| line.contains("widget.rs:")).collect();
assert_eq!(
diagnostics,
["widget.rs:3:5: [MD018] No space after # in heading [fixed]"],
"the doc-comment heading is the only finding, and the fix pass resolved it.\nstdout:\n{stdout}"
);
assert!(stdout.contains("Fixed 1/1 issues in 1 file"), "stdout:\n{stdout}");
}
#[test]
fn the_same_bytes_as_markdown_are_rewritten_throughout() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("widget.md"), RUST_SOURCE).unwrap();
let output = rumdl(
dir.path(),
&["fmt", "--color", "never", "--no-cache", "--no-config", "widget.md"],
);
let fixed = fs::read_to_string(dir.path().join("widget.md")).unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
fixed.contains("# [derive(Debug)]"),
"the attribute reads as a heading in markdown.\nfixed:\n{fixed}\nstdout:\n{stdout}"
);
assert!(
fixed.contains(r#"write!(f, "*{}*", self.name)"#),
"the string literal reads as emphasis in markdown.\nfixed:\n{fixed}\nstdout:\n{stdout}"
);
}
#[test]
fn diff_mode_offers_no_change_to_a_rust_files_source() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("widget.rs"), RUST_SOURCE).unwrap();
let output = rumdl(
dir.path(),
&[
"check",
"--diff",
"--color",
"never",
"--no-cache",
"--no-config",
"widget.rs",
],
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("-//!#Overview") && stdout.contains("+//!# Overview"),
"the doc-comment fix is still offered.\nstdout:\n{stdout}"
);
assert!(
!stdout.contains("derive") && !stdout.contains("write!"),
"no line of Rust is offered for rewriting.\nstdout:\n{stdout}"
);
assert_eq!(
fs::read_to_string(dir.path().join("widget.rs")).unwrap(),
RUST_SOURCE,
"a diff writes nothing"
);
}
#[test]
fn stdin_named_as_a_rust_file_fixes_only_its_doc_comments() {
let dir = tempfile::tempdir().unwrap();
let (stdout, stderr) = rumdl_with_stdin(
dir.path(),
RUST_SOURCE,
&[
"fmt",
"--stdin",
"--stdin-filename",
"widget.rs",
"--color",
"never",
"--no-config",
],
);
assert_eq!(stdout, RUST_SOURCE_FIXED, "stderr:\n{stderr}");
assert!(
stderr.contains("widget.rs:3:5: [MD018] No space after # in heading [fixed]"),
"the doc-comment fix is reported.\nstderr:\n{stderr}"
);
assert!(
stderr.contains("1 issue(s) fixed, 0 issue(s) remaining"),
"stderr:\n{stderr}"
);
}
#[test]
fn stdin_named_as_a_rust_file_reports_only_its_doc_comments() {
let dir = tempfile::tempdir().unwrap();
let (stdout, stderr) = rumdl_with_stdin(
dir.path(),
RUST_SOURCE,
&[
"check",
"--stdin",
"--stdin-filename",
"widget.rs",
"--color",
"never",
"--no-config",
],
);
let diagnostics: Vec<&str> = stdout.lines().filter(|line| line.contains("widget.rs:")).collect();
assert_eq!(
diagnostics,
["widget.rs:3:5: [MD018] No space after # in heading"],
"the doc-comment heading is the only finding.\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
}