diff --git a/.github/workflows/rust-ci.yaml b/.github/workflows/rust-ci.yaml index 5cd9901..82ed46b 100644 --- a/.github/workflows/rust-ci.yaml +++ b/.github/workflows/rust-ci.yaml @@ -157,7 +157,7 @@ jobs: - name: Run cargo-tarpaulin uses: actions-rs/tarpaulin@v0.1 with: - version: '0.18.0' + version: '0.19.1' timeout: '240' args: '-- --test-threads 1' diff --git a/Dockerfile b/Dockerfile index 18bdd1d..8c4aac6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:latest -ENV VERSION=0.8.4 +ENV VERSION=0.8.5 ENV RELEASE=nanoq-${VERSION}-x86_64-unknown-linux-musl.tar.gz RUN apk update && apk add --no-cache wget diff --git a/src/cli.rs b/src/cli.rs index f61063b..ca13703 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -30,7 +30,7 @@ pub struct Cli { #[structopt(short = "w", long, value_name = "FLOAT", default_value = "0")] pub max_qual: f32, - /// Verbose output statistics [multiple up to -vvv] + /// Verbose output statistics [multiple, up to -vvv] #[structopt( short, long, @@ -38,11 +38,15 @@ pub struct Cli { )] pub verbose: u64, + /// Header for summary output + #[structopt(short = "H", long)] + pub header: bool, + /// Number of top reads in verbose summary. #[structopt(short, long, value_name = "INT", default_value = "5")] pub top: usize, - /// Summary statistics report + /// Summary statistics report. #[structopt(short, long)] pub stats: bool, diff --git a/src/main.rs b/src/main.rs index 4d06585..c000346 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,7 @@ fn main() -> Result<()> { let mut read_set = ReadSet::new(read_lengths, read_qualities); read_set - .summary(&cli.verbose, cli.top) + .summary(&cli.verbose, cli.top, cli.header) .context("unable to get summary")?; Ok(()) diff --git a/src/needlecast.rs b/src/needlecast.rs index 1bfbe81..4639939 100644 --- a/src/needlecast.rs +++ b/src/needlecast.rs @@ -256,17 +256,69 @@ mod tests { } #[test] - fn needlecast_filter_length_fq_ok() { + fn needlecast_filter_max_fq_ok() { use structopt::StructOpt; let cli = Cli::from_iter(&["nanoq", "-i", "tests/cases/test_ok.fq", "-o", "/dev/null"]); let mut caster = NeedleCast::new(&cli); + let (read_lengths, read_quals) = caster.filter(0, 3, 0.0, 0.0).unwrap(); + + assert_eq!(read_lengths, vec![]); + assert_eq!(read_quals, vec![]); + } + + #[test] + fn needlecast_filter_length_fq_ok() { + use structopt::StructOpt; + + let cli = Cli::from_iter(&["nanoq", "-i", "tests/cases/test_len.fq", "-o", "/dev/null"]); + let mut caster = NeedleCast::new(&cli); let (read_lengths, read_quals) = caster.filter_length(0, 0).unwrap(); + assert_eq!(read_lengths, vec![4, 8]); + assert_eq!(read_quals, vec![]); + } + + #[test] + fn needlecast_filter_length_max_fq_ok() { + use structopt::StructOpt; + + let cli = Cli::from_iter(&["nanoq", "-i", "tests/cases/test_len.fq", "-o", "/dev/null"]); + + let mut caster = NeedleCast::new(&cli); + let (read_lengths, read_quals) = caster.filter_length(0, 3).unwrap(); + + assert_eq!(read_lengths, vec![]); + assert_eq!(read_quals, vec![]); + + // NeedleCast struct has to be initiated again to reset filter length parameters + let mut caster = NeedleCast::new(&cli); + let (read_lengths, read_quals) = caster.filter_length(0, 5).unwrap(); + assert_eq!(read_lengths, vec![4]); assert_eq!(read_quals, vec![]); } + #[test] + fn needlecast_filter_length_min_fq_ok() { + use structopt::StructOpt; + + let cli = Cli::from_iter(&["nanoq", "-i", "tests/cases/test_len.fq", "-o", "/dev/null"]); + + let mut caster = NeedleCast::new(&cli); + let (read_lengths, read_quals) = caster.filter_length(5, 0).unwrap(); + + assert_eq!(read_lengths, vec![8]); + assert_eq!(read_quals, vec![]); + + // NeedleCast struct has to be initiated again to reset filter length parameters + let mut caster = NeedleCast::new(&cli); + let (read_lengths, read_quals) = caster.filter_length(4, 0).unwrap(); + + assert_eq!(read_lengths, vec![4, 8]); + assert_eq!(read_quals, vec![]); + } + #[test] fn needlecast_filter_fa_ok() { use structopt::StructOpt; diff --git a/src/utils.rs b/src/utils.rs index 497b242..e4c490c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -85,13 +85,24 @@ impl ReadSet { /// ) /// read_set.summary(0, 3); /// ``` - pub fn summary(&mut self, verbosity: &u64, top: usize) -> Result<(), UtilityError> { + pub fn summary( + &mut self, + verbosity: &u64, + top: usize, + header: bool, + ) -> Result<(), UtilityError> { let length_range = self.range_length(); match verbosity { &0 => { + let head = match header { + true => "reads bases n50 longest shortest mean_length median_length mean_quality median_quality\n", + false => "" + }; + eprintdoc! { - "{reads} {bases} {n50} {longest} {shortest} {mean} {median} {meanq:.1} {medianq:.1}\n", + "{head}{reads} {bases} {n50} {longest} {shortest} {mean} {median} {meanq:.1} {medianq:.1}\n", + head = head, reads = self.reads(), bases = self.bases(), n50 = self.n50(), @@ -661,12 +672,12 @@ mod tests { read_set_odd.print_ranking(3); read_set_odd.print_ranking(5); - read_set_odd.summary(&0, 5).unwrap(); - read_set_odd.summary(&1, 5).unwrap(); - read_set_odd.summary(&2, 5).unwrap(); - read_set_odd.summary(&3, 5).unwrap(); + read_set_odd.summary(&0, 5, false).unwrap(); + read_set_odd.summary(&1, 5, false).unwrap(); + read_set_odd.summary(&2, 5, false).unwrap(); + read_set_odd.summary(&3, 5, false).unwrap(); - let error = read_set_odd.summary(&4, 5).unwrap_err(); + let error = read_set_odd.summary(&4, 5, false).unwrap_err(); assert_eq!(error, UtilityError::InvalidVerbosity("4".to_string())); } @@ -679,7 +690,7 @@ mod tests { read_set_noqual.print_thresholds(); read_set_noqual.print_ranking(3); - read_set_noqual.summary(&3, 3).unwrap(); + read_set_noqual.summary(&3, 3, false).unwrap(); } #[test] @@ -693,7 +704,7 @@ mod tests { read_set_none.print_thresholds(); read_set_none.print_ranking(3); - read_set_none.summary(&3, 3).unwrap(); + read_set_none.summary(&3, 3, false).unwrap(); } #[test] @@ -709,6 +720,31 @@ mod tests { read_set_none.print_thresholds(); read_set_none.print_ranking(3); - read_set_none.summary(&3, 3).unwrap(); + read_set_none.summary(&3, 3, false).unwrap(); + } + // These tests are not testing for the correct stderr output, + // does not seem possible with libtest at the moment: + // * https://github.com/rust-lang/rust/issues/42474 + // * https://github.com/rust-lang/rust/issues/40298 + #[test] + fn summary_output_ok() { + use float_eq::float_eq; + + let mut read_set_none = ReadSet::new(vec![10], vec![8.0]); + assert_eq!(read_set_none.mean_length(), 10); + assert_eq!(read_set_none.median_length(), 10); + float_eq!(read_set_none.mean_quality(), 8.0, abs <= f32::EPSILON); + float_eq!(read_set_none.median_quality(), 8.0, abs <= f32::EPSILON); + assert_eq!(read_set_none.range_length(), [10, 10]); + + read_set_none.print_thresholds(); + read_set_none.print_ranking(3); + read_set_none.summary(&3, 3, false).unwrap(); + } + + #[test] + fn summary_header_stderr_ok() { + let mut read_set_none = ReadSet::new(vec![10], vec![8.0]); + read_set_none.summary(&0, 3, true).unwrap(); } } diff --git a/tests/cases/test_len.fq b/tests/cases/test_len.fq new file mode 100644 index 0000000..06f235a --- /dev/null +++ b/tests/cases/test_len.fq @@ -0,0 +1,8 @@ +@id1 +ACTG ++ +IIII +@id2 +ACTGACTG ++ +IIIIIIII