testing-conventions 0.0.106

Enforce testing conventions in libraries (Python, TypeScript, and Rust).
Documentation
use std::path::PathBuf;
use std::process::{Command, Output};

/// The rule id a violation carries. A clean run never names it — asserting on the
/// rule rather than on empty stderr keeps the check pointed at this rule, since the
/// binary also writes its version banner to stderr on every run.
const RULE: &str = "one-function-per-file";

fn fixture(name: &str) -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("tests/fixtures/one_function")
        .join(name)
}

/// Output of `unit one-function-per-file --language <language> <fixture>`.
fn run(language: &str, fixture_name: &str) -> Output {
    Command::new(env!("CARGO_BIN_EXE_testing-conventions"))
        .args(["unit", "one-function-per-file", "--language", language])
        .arg(fixture(fixture_name))
        .output()
        .expect("the built binary should run")
}

/// Output of the same run with `--config <config>`.
fn run_with_config(language: &str, fixture_name: &str, config_name: &str) -> Output {
    Command::new(env!("CARGO_BIN_EXE_testing-conventions"))
        .args(["unit", "one-function-per-file", "--language", language])
        .arg("--config")
        .arg(fixture(config_name))
        .arg(fixture(fixture_name))
        .output()
        .expect("the built binary should run")
}

fn code(output: &Output) -> i32 {
    output
        .status
        .code()
        .expect("the process should exit with a code")
}

fn stderr(output: &Output) -> String {
    String::from_utf8(output.stderr.clone()).expect("stderr should be UTF-8")
}

fn stdout(output: &Output) -> String {
    String::from_utf8(output.stdout.clone()).expect("stdout should be UTF-8")
}

#[test]
fn python_red_exits_nonzero() {
    assert_eq!(code(&run("python", "python/red")), 1);
}

#[test]
fn python_red_names_the_extra_function_and_the_one_holding_the_file() {
    let reported = stderr(&run("python", "python/red"));
    assert!(
        reported.contains("two_functions.py:6: one-function-per-file"),
        "the second function's line carries the rule id: {reported}"
    );
    assert!(
        reported.contains("`beta`") && reported.contains("`alpha`"),
        "the message names the extra function and the one holding the file: {reported}"
    );
}

#[test]
fn python_red_counts_every_function_past_the_first() {
    let reported = stderr(&run("python", "python/red"));
    assert!(
        reported.contains("error: 3 function(s)"),
        "two files contribute one and two violations: {reported}"
    );
}

#[test]
fn python_clean_exits_zero() {
    let output = run("python", "python/clean");
    assert_eq!(code(&output), 0);
    assert!(
        !stderr(&output).contains(RULE),
        "a clean tree reports no violation: {}",
        stderr(&output)
    );
}

#[test]
fn python_raised_fails_at_the_default_threshold() {
    let output = run("python", "python/raised");
    assert_eq!(code(&output), 1);
    assert!(
        stderr(&output).contains("1-line threshold"),
        "the summary states the threshold in force: {}",
        stderr(&output)
    );
}

#[test]
fn python_raised_passes_at_its_configured_threshold() {
    assert_eq!(
        code(&run_with_config(
            "python",
            "python/raised",
            "python/raised/testing-conventions.toml"
        )),
        0
    );
}

#[test]
fn python_raised_red_reports_the_configured_threshold() {
    let output = run_with_config(
        "python",
        "python/raised_red",
        "python/raised_red/testing-conventions.toml",
    );
    assert_eq!(code(&output), 1);
    assert!(
        stderr(&output).contains("5-line threshold"),
        "the configured threshold reaches the summary: {}",
        stderr(&output)
    );
}

#[test]
fn python_waived_exits_zero() {
    assert_eq!(
        code(&run_with_config(
            "python",
            "python/waived",
            "python/waived/testing-conventions.toml"
        )),
        0
    );
}

#[test]
fn typescript_red_exits_nonzero() {
    assert_eq!(code(&run("typescript", "typescript/red")), 1);
}

#[test]
fn typescript_red_flags_an_arrow_bound_to_a_module_scope_const() {
    let reported = stderr(&run("typescript", "typescript/red"));
    assert!(
        reported.contains("two-functions.ts:6: one-function-per-file"),
        "the arrow-bound `beta` is a module-scope function: {reported}"
    );
}

#[test]
fn typescript_clean_exits_zero() {
    let output = run("typescript", "typescript/clean");
    assert_eq!(code(&output), 0);
    assert!(
        !stderr(&output).contains(RULE),
        "a clean tree reports no violation: {}",
        stderr(&output)
    );
}

#[test]
fn typescript_raised_fails_at_the_default_threshold() {
    assert_eq!(code(&run("typescript", "typescript/raised")), 1);
}

#[test]
fn typescript_raised_passes_at_its_configured_threshold() {
    assert_eq!(
        code(&run_with_config(
            "typescript",
            "typescript/raised",
            "typescript/raised/testing-conventions.toml"
        )),
        0
    );
}

#[test]
fn typescript_raised_red_reports_the_configured_threshold() {
    let output = run_with_config(
        "typescript",
        "typescript/raised_red",
        "typescript/raised_red/testing-conventions.toml",
    );
    assert_eq!(code(&output), 1);
    assert!(
        stderr(&output).contains("5-line threshold"),
        "the configured threshold reaches the summary: {}",
        stderr(&output)
    );
}

#[test]
fn typescript_waived_exits_zero() {
    assert_eq!(
        code(&run_with_config(
            "typescript",
            "typescript/waived",
            "typescript/waived/testing-conventions.toml"
        )),
        0
    );
}

#[test]
fn rust_is_off_until_a_config_opts_in() {
    let output = run("rust", "rust/red");
    assert_eq!(code(&output), 0);
    assert!(
        stdout(&output).contains("one-function-per-file: not enabled for rust"),
        "an unconfigured Rust run must say so, got: {}",
        stdout(&output)
    );
}

#[test]
fn rust_red_exits_nonzero_once_a_config_opts_in() {
    assert_eq!(
        code(&run_with_config(
            "rust",
            "rust/red",
            "rust/red/testing-conventions.toml"
        )),
        1
    );
}

#[test]
fn rust_red_names_the_extra_function() {
    let reported = stderr(&run_with_config(
        "rust",
        "rust/red",
        "rust/red/testing-conventions.toml",
    ));
    assert!(
        reported.contains("two_functions.rs:6: one-function-per-file"),
        "the second `fn` item carries the rule id: {reported}"
    );
}

#[test]
fn rust_clean_exits_zero() {
    let output = run("rust", "rust/clean");
    assert_eq!(code(&output), 0);
    assert!(
        !stderr(&output).contains(RULE),
        "a clean tree reports no violation: {}",
        stderr(&output)
    );
}

#[test]
fn rust_raised_is_unjudged_without_a_config() {
    assert_eq!(code(&run("rust", "rust/raised")), 0);
}

#[test]
fn rust_raised_passes_at_its_configured_threshold() {
    assert_eq!(
        code(&run_with_config(
            "rust",
            "rust/raised",
            "rust/raised/testing-conventions.toml"
        )),
        0
    );
}

#[test]
fn rust_raised_red_reports_the_configured_threshold() {
    let output = run_with_config(
        "rust",
        "rust/raised_red",
        "rust/raised_red/testing-conventions.toml",
    );
    assert_eq!(code(&output), 1);
    assert!(
        stderr(&output).contains("5-line threshold"),
        "the configured threshold reaches the summary: {}",
        stderr(&output)
    );
}

#[test]
fn rust_waived_exits_zero() {
    assert_eq!(
        code(&run_with_config(
            "rust",
            "rust/waived",
            "rust/waived/testing-conventions.toml"
        )),
        0
    );
}