Skip to content

Commit

Permalink
test(readme): FIX_README - automatic fixing functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
alexpovel committed Nov 9, 2024
1 parent 2c29cfd commit 635839b
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 112 deletions.
6 changes: 0 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,11 @@ tested](./tests/readme.rs). The testing of the `bash` snippets is pure Rust (no
binary needed), making it platform-independent. The downside is that custom parsing is
used. This has known warts. Those warts have workarounds:

- the README contains the full output of `srgn --help`, which is also tested against.
Run [`./scripts/update-readme.py`](./scripts/update-readme.py) to update this README
section automatically if you updated it.
- the tests contain [**hard-coded names of CLI
options**](https://github.com/alexpovel/srgn/blob/8ff54ee53ac0a53cdc4791b069648ee4511c7b94/tests/readme.rs#L494-L521).
This is necessary as otherwise it'd be unclear if a string `--foo` should be a flag
(no argument) or an option (an argument follows, `--foo bar`).

All these could be considered plain bugs in the custom parser. They would certainly
be fixable, given the time.

### Custom macros to work around `clap`

The needs of `srgn` go slightly beyond what `clap` offers out of the box. For this,
Expand Down
67 changes: 0 additions & 67 deletions scripts/update-readme.py

This file was deleted.

93 changes: 54 additions & 39 deletions tests/readme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod tests {
use core::{fmt, panic};
use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
use std::error::Error;
use std::io::Write;
use std::mem::ManuallyDrop;
use std::rc::Rc;
Expand All @@ -40,7 +41,8 @@ mod tests {
use unescape::unescape;

const PROGRAM_NAME: &str = env!("CARGO_PKG_NAME");
const DOCUMENT: &str = include_str!("../README.md");
const PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/", "README.md");
const FIX_ENV_VAR: &str = "FIX_README";

/// A flag, either short or long.
///
Expand Down Expand Up @@ -738,10 +740,10 @@ mod tests {

type Snippets = HashMap<String, Snippet>;

fn get_readme_snippets() -> Snippets {
fn get_readme_snippets(doc: &str) -> Snippets {
let mut snippets = HashMap::new();

map_on_markdown_codeblocks(DOCUMENT, |ncb| {
map_on_markdown_codeblocks(doc, |ncb| {
if let Some((_language, options)) = ncb.info.split_once(' ') {
let mut snippet = Snippet::default();
let options: BlockOptions = options.into();
Expand Down Expand Up @@ -774,10 +776,10 @@ mod tests {
snippets
}

fn get_readme_program_pipes(snippets: &Snippets) -> Vec<PipedPrograms> {
fn get_readme_program_pipes(doc: &str, snippets: &Snippets) -> Vec<PipedPrograms> {
let mut pipes = Vec::new();

map_on_markdown_codeblocks(DOCUMENT, |ncb| {
map_on_markdown_codeblocks(doc, |ncb| {
let (language, options): (&str, Option<BlockOptions>) = ncb
.info
.split_once(' ')
Expand Down Expand Up @@ -826,11 +828,12 @@ mod tests {
}

#[test]
fn test_readme_code_blocks() {
let _helper = TestHinter;
fn test_readme_code_blocks() -> Result<(), Box<dyn Error>> {
let _hinter = TestHinter;

let snippets = get_readme_snippets();
let pipes = get_readme_program_pipes(&snippets);
let contents = std::fs::read_to_string(PATH)?;
let snippets = get_readme_snippets(&contents);
let pipes = get_readme_program_pipes(&contents, &snippets);

for pipe in pipes {
let mut previous_stdin = None;
Expand Down Expand Up @@ -875,37 +878,48 @@ mod tests {
}

if observed_stdout != expected_stdout {
// Write to files for easier inspection
let (mut obs_f, mut exp_f) = (
ManuallyDrop::new(NamedTempFile::new().unwrap()),
ManuallyDrop::new(NamedTempFile::new().unwrap()),
);

obs_f.write_all(observed_stdout.as_bytes()).unwrap();
exp_f.write_all(expected_stdout.as_bytes()).unwrap();

// Now panic as usual, for the usual output
assert_eq!(
// Get some more readable output diff compared to
// `assert::Command`'s `stdout()` function, for which diffing
// whitespace is very hard.
expected_stdout,
observed_stdout,
"Output differs; for inspection see observed stdout at '{}', expected stdout at '{}'",
obs_f.path().display(),
exp_f.path().display()
);

// Temporary files remain, they're not dropped.

unreachable!();
if std::env::var(FIX_ENV_VAR).as_deref() == Ok("1") {
let old_readme = std::fs::read_to_string(PATH)?;
// We'd REALLY want the exact byte ranges here for exact
// precision, but those aren't easily available. This
// naive replacement will fail in various scenarios.
let new_readme = old_readme.replace(&expected_stdout, &observed_stdout);

assert!(old_readme != new_readme, "No changes made to {PATH}, bug.");

std::fs::write(PATH, new_readme)?;
} else {
// Write to files for easier inspection
let (mut obs_f, mut exp_f) = (
ManuallyDrop::new(NamedTempFile::new().unwrap()),
ManuallyDrop::new(NamedTempFile::new().unwrap()),
);

obs_f.write_all(observed_stdout.as_bytes()).unwrap();
exp_f.write_all(expected_stdout.as_bytes()).unwrap();

// Now panic as usual, for the usual output
assert_eq!(
// Get some more readable output diff compared to
// `assert::Command`'s `stdout()` function, for which diffing
// whitespace is very hard.
expected_stdout,
observed_stdout,
"Output differs; for inspection see observed stdout at '{}', expected stdout at '{}'",
obs_f.path().display(),
exp_f.path().display()
);

// Temporary files remain, they're not dropped.
}
}
}

// Pipe stdout to stdin of next run...
previous_stdin = Some(observed_stdout);
}
}

Ok(())
}

fn fix_windows_output(mut input: String) -> String {
Expand Down Expand Up @@ -966,11 +980,12 @@ mod tests {
impl Drop for TestHinter {
fn drop(&mut self) {
if std::thread::panicking() {
println!("\n==============================================");
println!("💡 README test failed!");
println!("Did you update the `srgn --help` output?");
println!("If no, run `./scripts/update-readme.py README.md` and try again.");
println!("==============================================\n");
println!(
r#"
💡 README test failed!
Run with `{FIX_ENV_VAR}=1` env var to fix the README automatically (BEST EFFORT!).
"#
);
}
}
}
Expand Down

0 comments on commit 635839b

Please sign in to comment.