rsfgsea 0.3.4

High-performance fgsea-compatible preranked Gene Set Enrichment Analysis in Rust
Documentation
use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::tempdir;

fn write_test_inputs() -> (
    tempfile::TempDir,
    std::path::PathBuf,
    std::path::PathBuf,
    std::path::PathBuf,
) {
    let dir = tempdir().unwrap();
    let ranks = dir.path().join("test.rnk");
    let gmt = dir.path().join("test.gmt");
    let output = dir.path().join("out.tsv");

    fs::write(&ranks, "g1\t2.0\ng2\t1.0\ng3\t-1.0\ng4\t-2.0\n").unwrap();
    fs::write(&gmt, "PW_A\tdesc\tg1\tg2\nPW_B\tdesc\tg3\tg4\n").unwrap();

    (dir, ranks, gmt, output)
}

fn cli_bin() -> Command {
    Command::new(env!("CARGO_BIN_EXE_rsfgsea"))
}

fn plot_cli_bin() -> Command {
    Command::new(env!("CARGO_BIN_EXE_rsfgsea-plot-enrichment"))
}

fn plot_table_cli_bin() -> Command {
    Command::new(env!("CARGO_BIN_EXE_rsfgsea-plot-gsea-table"))
}

#[test]
fn cli_simple_mode_writes_results() {
    let (_dir, ranks, gmt, output) = write_test_inputs();

    cli_bin()
        .args([
            "--mode",
            "simple",
            "--nPermSimple",
            "100",
            "--ranks",
            ranks.to_str().unwrap(),
            "--gmt",
            gmt.to_str().unwrap(),
            "--output",
            output.to_str().unwrap(),
        ])
        .assert()
        .success();

    let content = fs::read_to_string(output).unwrap();
    assert!(content.contains("pathway\tsize\tes\tnes\tpval\tpadj\tlog2err\tleading_edge"));
    assert!(content.contains("PW_A"));
}

#[test]
fn cli_gpu_rejects_non_fgsea_mode_before_adapter_init() {
    let (_dir, ranks, gmt, output) = write_test_inputs();
    let expected_stderr = if cfg!(feature = "gpu") {
        "--gpu currently supports only --mode fgsea."
    } else {
        "--gpu requires building the CLI with --features gpu."
    };

    cli_bin()
        .args([
            "--gpu",
            "--mode",
            "simple",
            "--ranks",
            ranks.to_str().unwrap(),
            "--gmt",
            gmt.to_str().unwrap(),
            "--output",
            output.to_str().unwrap(),
        ])
        .assert()
        .failure()
        .stderr(predicate::str::contains(expected_stderr));
}

#[test]
fn plot_cli_writes_png() {
    let (_dir, ranks, gmt, output) = write_test_inputs();
    let png = output.with_extension("png");

    plot_cli_bin()
        .args([
            "--ranks",
            ranks.to_str().unwrap(),
            "--gmt",
            gmt.to_str().unwrap(),
            "--pathway",
            "PW_A",
            "--output",
            png.to_str().unwrap(),
            "--dpi",
            "300",
        ])
        .assert()
        .success();

    let bytes = fs::read(&png).unwrap();
    assert!(bytes.starts_with(&[0x89, b'P', b'N', b'G']));
}

#[test]
fn plot_cli_transparent_background_writes_rgba_png() {
    let (_dir, ranks, gmt, output) = write_test_inputs();
    let png = output.with_extension("transparent.png");

    plot_cli_bin()
        .args([
            "--ranks",
            ranks.to_str().unwrap(),
            "--gmt",
            gmt.to_str().unwrap(),
            "--pathway",
            "PW_A",
            "--output",
            png.to_str().unwrap(),
            "--transparent-background",
        ])
        .assert()
        .success();

    let data = fs::read(&png).unwrap();
    assert!(data.starts_with(&[0x89, b'P', b'N', b'G']));
    assert_eq!(data[25], 6);
}

#[test]
fn plot_cli_dpi_controls_pixel_dimensions() {
    let (_dir, ranks, gmt, output) = write_test_inputs();
    let png = output.with_extension("png");

    plot_cli_bin()
        .args([
            "--ranks",
            ranks.to_str().unwrap(),
            "--gmt",
            gmt.to_str().unwrap(),
            "--pathway",
            "PW_A",
            "--output",
            png.to_str().unwrap(),
            "--width-in",
            "1.2",
            "--height-in",
            "1.0",
            "--dpi",
            "300",
        ])
        .assert()
        .success();

    let bytes = fs::read(&png).unwrap();
    let width = u32::from_be_bytes(bytes[16..20].try_into().unwrap());
    let height = u32::from_be_bytes(bytes[20..24].try_into().unwrap());
    assert_eq!(width, 360);
    assert_eq!(height, 300);
}

#[test]
fn plot_table_cli_writes_png() {
    let (_dir, ranks, gmt, output) = write_test_inputs();
    let png = output.with_extension("table.png");

    plot_table_cli_bin()
        .args([
            "--ranks",
            ranks.to_str().unwrap(),
            "--gmt",
            gmt.to_str().unwrap(),
            "--pathway",
            "PW_A",
            "PW_B",
            "--output",
            png.to_str().unwrap(),
            "--dpi",
            "300",
            "--nPermSimple",
            "100",
        ])
        .assert()
        .success();

    let bytes = fs::read(&png).unwrap();
    assert!(bytes.starts_with(&[0x89, b'P', b'N', b'G']));
    let width = u32::from_be_bytes(bytes[16..20].try_into().unwrap());
    let height = u32::from_be_bytes(bytes[20..24].try_into().unwrap());
    assert!(
        width >= 1680,
        "expected table plot width >= 1680 px, got {width}"
    );
    assert!(
        height >= 200,
        "expected table plot height >= 200 px, got {height}"
    );
    assert!(width > height, "expected table plot to be wider than tall");
}