use std::error::Error; use std::process::Command; // Run programs use assert_cmd::prelude::*; // Add methods on commands use indoc::indoc; use predicates::prelude::*; // Used for writing assertions use rstest::rstest; #[test] fn test_no_args() -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.assert() .failure() .stderr(predicate::str::contains("Usage:")); Ok(()) } #[test] fn test_unknown_chord() -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("chart"); cmd.arg("blafoo"); cmd.assert().failure().stderr(predicate::str::contains( "error: invalid value 'blafoo' for '': could not parse chord name 'blafoo'", )); Ok(()) } #[test] fn test_no_voicing_found() -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("chart"); cmd.arg("--max-span").arg("0"); cmd.arg("C"); cmd.assert() .success() .stdout("No matching chord voicing was found\n"); Ok(()) } #[test] fn test_invalid_pattern() -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("name"); cmd.arg("blafoo"); cmd.assert().failure().stderr(predicate::str::contains( "error: invalid value 'blafoo' for '': fret pattern has wrong format (should be something like 1234 or '7 8 9 10')", )); Ok(()) } #[test] fn test_unknown_pattern() -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("name"); cmd.arg("1234"); cmd.assert() .success() .stdout("No matching chord was found\n"); Ok(()) } #[test] fn test_no_voicing_seq_found() -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("voice-lead"); cmd.arg("--max-fret").arg("2"); cmd.arg("C F G"); cmd.assert() .success() .stdout("No matching chord voicing sequence was found\n"); Ok(()) } #[rstest( chord, chart, case( "C", indoc!(" [C - C major] A ||---|---|-3-|---|- C E o||---|---|---|---|- E C o||---|---|---|---|- C G o||---|---|---|---|- G ") ), case( "C#", indoc!(" [C# - C# major] A ||---|---|---|-4-|- C# E ||-1-|---|---|---|- F C ||-1-|---|---|---|- C# G ||-1-|---|---|---|- G# ") ), case( "Db", indoc!(" [Db - Db major] A ||---|---|---|-4-|- Db E ||-1-|---|---|---|- F C ||-1-|---|---|---|- Db G ||-1-|---|---|---|- Ab ") ), )] fn test_chart(chord: &str, chart: &'static str) -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("chart").arg(chord); cmd.assert().success().stdout(format!("{chart}\n")); Ok(()) } #[rstest( chord, tuning, chart, case( "C", "C", indoc!(" [C - C major] A ||---|---|-3-|---|- C E o||---|---|---|---|- E C o||---|---|---|---|- C G o||---|---|---|---|- G ") ), case( "D", "D", indoc!(" [D - D major] B ||---|---|-3-|---|- D F# o||---|---|---|---|- F# D o||---|---|---|---|- D A o||---|---|---|---|- A ") ), case( "G", "G", indoc!(" [G - G major] E ||---|---|-3-|---|- G B o||---|---|---|---|- B G o||---|---|---|---|- G D o||---|---|---|---|- D ") ), )] fn test_tuning( chord: &str, tuning: &str, chart: &'static str, ) -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("chart"); cmd.arg("--tuning").arg(tuning); cmd.arg(chord); cmd.assert().success().stdout(format!("{chart}\n")); Ok(()) } #[rstest( chord, min_fret, chart, case( "C", "1", indoc!(" [C - C major] A -|-1-|---|---|---|- C E -|-1-|---|---|---|- G C -|---|-2-|---|---|- E G -|---|---|-3-|---|- C 3 ") ), case( "C", "3", indoc!(" [C - C major] A -|-1-|---|---|---|- C E -|-1-|---|---|---|- G C -|---|-2-|---|---|- E G -|---|---|-3-|---|- C 3 ") ), case( "C", "10", indoc!(" [C - C major] A -|-1-|---|---|---|- G E -|---|---|-4-|---|- E C -|---|---|-3-|---|- C G -|---|---|-2-|---|- G 10 ") ), )] fn test_min_fret( chord: &str, min_fret: &str, chart: &'static str, ) -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("chart"); cmd.arg("--min-fret").arg(min_fret); cmd.arg(chord); cmd.assert().success().stdout(format!("{chart}\n")); Ok(()) } #[rstest( chord, max_span, chart, case( "C#", "3", indoc!(" [C# - C# major] A -|-1-|---|---|---|- C# E -|-1-|---|---|---|- G# C -|---|-2-|---|---|- F G -|---|---|-3-|---|- C# 4 ") ), case( "C#", "5", indoc!(" [C# - C# major] A ||---|---|---|-4-|---|- C# E ||-1-|---|---|---|---|- F C ||-1-|---|---|---|---|- C# G ||-1-|---|---|---|---|- G# ") ), )] fn test_max_span( chord: &str, max_span: &str, chart: &'static str, ) -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("chart"); cmd.arg("--max-span").arg(max_span); cmd.arg(chord); cmd.assert().success().stdout(format!("{chart}\n")); Ok(()) } #[rstest( chord, semitones, chart, case( "C", "0", indoc!(" [C - C major] A ||---|---|-3-|---|- C E o||---|---|---|---|- E C o||---|---|---|---|- C G o||---|---|---|---|- G ") ), case( "C", "+1", indoc!(" [C# - C# major] A ||---|---|---|-4-|- C# E ||-1-|---|---|---|- F C ||-1-|---|---|---|- C# G ||-1-|---|---|---|- G# ") ), case( "C", "1", indoc!(" [C# - C# major] A ||---|---|---|-4-|- C# E ||-1-|---|---|---|- F C ||-1-|---|---|---|- C# G ||-1-|---|---|---|- G# ") ), case( "D", "-1", indoc!(" [Db - Db major] A ||---|---|---|-4-|- Db E ||-1-|---|---|---|- F C ||-1-|---|---|---|- Db G ||-1-|---|---|---|- Ab ") ), )] fn test_transpose( chord: &str, semitones: &str, chart: &'static str, ) -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("chart"); cmd.arg("--transpose").arg(semitones); cmd.arg(chord); cmd.assert().success().stdout(format!("{chart}\n")); Ok(()) } #[rstest( chord, min_fret, max_fret, chart, case( "C#", None, None, indoc!(" [C# - C# major] A ||---|---|---|-4-|- C# E ||-1-|---|---|---|- F C ||-1-|---|---|---|- C# G ||-1-|---|---|---|- G# A -|-1-|---|---|---|- C# E -|-1-|---|---|---|- G# C -|---|-2-|---|---|- F G -|---|---|-3-|---|- C# 4 A -|---|---|-3-|---|- F E -|---|---|---|-4-|- C# C -|---|---|-2-|---|- G# G -|-1-|---|---|---|- C# 6 A -|-1-|---|---|---|- F E -|---|-2-|---|---|- C# C -|-1-|---|---|---|- G# G -|---|---|-3-|---|- F 8 A -|---|---|---|-4-|- G# E -|---|-2-|---|---|- C# C -|-1-|---|---|---|- G# G -|---|---|-3-|---|- F 8 ") ), case( "C#", Some("5"), None, indoc!(" [C# - C# major] A -|---|---|-3-|---|- F E -|---|---|---|-4-|- C# C -|---|---|-2-|---|- G# G -|-1-|---|---|---|- C# 6 A -|-1-|---|---|---|- F E -|---|-2-|---|---|- C# C -|-1-|---|---|---|- G# G -|---|---|-3-|---|- F 8 A -|---|---|---|-4-|- G# E -|---|-2-|---|---|- C# C -|-1-|---|---|---|- G# G -|---|---|-3-|---|- F 8 ") ), case( "C#", None, Some("10"), indoc!(" [C# - C# major] A ||---|---|---|-4-|- C# E ||-1-|---|---|---|- F C ||-1-|---|---|---|- C# G ||-1-|---|---|---|- G# A -|-1-|---|---|---|- C# E -|-1-|---|---|---|- G# C -|---|-2-|---|---|- F G -|---|---|-3-|---|- C# 4 A -|---|---|-3-|---|- F E -|---|---|---|-4-|- C# C -|---|---|-2-|---|- G# G -|-1-|---|---|---|- C# 6 A -|-1-|---|---|---|- F E -|---|-2-|---|---|- C# C -|-1-|---|---|---|- G# G -|---|---|-3-|---|- F 8 ") ), case( "C#", Some("5"), Some("10"), indoc!(" [C# - C# major] A -|---|---|-3-|---|- F E -|---|---|---|-4-|- C# C -|---|---|-2-|---|- G# G -|-1-|---|---|---|- C# 6 A -|-1-|---|---|---|- F E -|---|-2-|---|---|- C# C -|-1-|---|---|---|- G# G -|---|---|-3-|---|- F 8 ") ), )] fn test_all( chord: &str, min_fret: Option<&str>, max_fret: Option<&str>, chart: &'static str, ) -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("chart").arg("--all"); if let Some(fret) = min_fret { cmd.arg("--min-fret").arg(fret); } if let Some(fret) = max_fret { cmd.arg("--max-fret").arg(fret); } cmd.arg(chord); cmd.assert().success().stdout(format!("{chart}\n")); Ok(()) } #[rstest( chart, names, case("0000", "C6 - C major 6th\nAm7 - A minor 7th"), case("0003", "C - C major"), case("0013", "Csus4 - C suspended 4th\nFsus2 - F suspended 2nd"), case("10 10 10 10", "Gm7 - G minor 7th\nA#6 - A# major 6th"), case("7604", "Dmaj9 - D major 9th"), case("1304", "Emaj13 - E major 13th") )] fn test_name(chart: &str, names: &'static str) -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("name").arg(chart); cmd.assert().success().stdout(format!("{names}\n")); Ok(()) } #[rstest( chart, tuning, names, case("0003", "C", "C - C major"), case("0003", "D", "D - D major"), case("0003", "G", "G - G major"), case("10 10 10 10", "C", "Gm7 - G minor 7th\nA#6 - A# major 6th"), case("10 10 10 10", "D", "C6 - C major 6th\nAm7 - A minor 7th"), case("10 10 10 10", "G", "Dm7 - D minor 7th\nF6 - F major 6th") )] fn test_name_with_tuning( chart: &str, tuning: &str, names: &'static str, ) -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("name"); cmd.arg("--tuning").arg(tuning); cmd.arg(chart); cmd.assert().success().stdout(format!("{names}\n")); Ok(()) } #[rstest( chord_seq, option, chart, case( "C", None, indoc!(" [C - C major] A ||---|---|-3-|---|- C E o||---|---|---|---|- E C o||---|---|---|---|- C G o||---|---|---|---|- G ") ), case( "C Am", None, indoc!(" [C - C major] A ||---|---|-3-|---|- C E o||---|---|---|---|- E C o||---|---|---|---|- C G o||---|---|---|---|- G [Am - A minor] A ||---|---|-3-|---|- C E o||---|---|---|---|- E C o||---|---|---|---|- C G ||---|-2-|---|---|- A ") ), case( "C F G", None, indoc!(" [C - C major] A ||---|---|-3-|---|- C E o||---|---|---|---|- E C o||---|---|---|---|- C G o||---|---|---|---|- G [F - F major] A ||---|---|-3-|---|- C E ||-1-|---|---|---|- F C o||---|---|---|---|- C G ||---|-2-|---|---|- A [G - G major] A ||---|-2-|---|---|- B E ||---|---|-3-|---|- G C ||---|-1-|---|---|- D G o||---|---|---|---|- G ") ), case( "C F G", Some(("--tuning", "D")), indoc!(" [C - C major] B ||-1-|---|---|---|- C F# ||-1-|---|---|---|- G D ||---|-2-|---|---|- E A ||---|---|-3-|---|- C [F - F major] B ||-1-|---|---|---|- C F# ||---|---|-4-|---|- A D ||---|---|-3-|---|- F A ||---|---|-2-|---|- C [G - G major] B o||---|---|---|---|- B F# ||-1-|---|---|---|- G D o||---|---|---|---|- D A ||---|-2-|---|---|- B ") ), case( "C F G", Some(("--transpose", "1")), indoc!(" [C# - C# major] A ||---|---|---|-4-|- C# E ||-1-|---|---|---|- F C ||-1-|---|---|---|- C# G ||-1-|---|---|---|- G# [F# - F# major] A ||---|---|---|-4-|- C# E ||---|-2-|---|---|- F# C ||-1-|---|---|---|- C# G ||---|---|-3-|---|- A# [G# - G# major] A -|-1-|---|---|---|- C E -|---|-2-|---|---|- G# C -|-1-|---|---|---|- D# G -|---|---|-3-|---|- C 3 ") ), )] fn test_voice_lead( chord_seq: &str, option: Option<(&str, &str)>, chart: &'static str, ) -> Result<(), Box> { let mut cmd = Command::cargo_bin("ukebox")?; cmd.arg("voice-lead"); if let Some((arg, value)) = option { cmd.arg(arg).arg(value); } cmd.arg(chord_seq); cmd.assert().success().stdout(format!("{chart}\n")); Ok(()) }