-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #1095 currently validates against std extensions can read from stdin with `echo "<json>" | hugr -` binary depends on optional feature to limit dependencies, but not put in sub crate to allow `cargo install hugr` add integration testing of binary behaviour based on https://docs.rs/assert_cmd/2.0.14/assert_cmd/cmd/struct.Command.html#method.write_stdin
- Loading branch information
Showing
5 changed files
with
198 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
//! Standard command line tools, used by the hugr binary. | ||
use crate::{extension::ExtensionRegistry, Hugr, HugrView}; | ||
use clap::Parser; | ||
use clap_stdin::FileOrStdin; | ||
use thiserror::Error; | ||
/// Validate and visualise a HUGR file. | ||
#[derive(Parser, Debug)] | ||
#[clap(version = "1.0", long_about = None)] | ||
#[clap(about = "Validate a HUGR.")] | ||
pub struct CmdLineArgs { | ||
input: FileOrStdin, | ||
/// Visualise with mermaid. | ||
#[arg(short, long, value_name = "MERMAID", help = "Visualise with mermaid.")] | ||
mermaid: bool, | ||
/// Skip validation. | ||
#[arg(short, long, help = "Skip validation.")] | ||
no_validate: bool, | ||
// TODO YAML extensions | ||
} | ||
|
||
/// Error type for the CLI. | ||
#[derive(Error, Debug)] | ||
pub enum CliError { | ||
/// Error reading input. | ||
#[error("Error reading input: {0}")] | ||
Input(#[from] clap_stdin::StdinError), | ||
/// Error parsing input. | ||
#[error("Error parsing input: {0}")] | ||
Parse(#[from] serde_json::Error), | ||
/// Error validating HUGR. | ||
#[error("Error validating HUGR: {0}")] | ||
Validate(#[from] crate::hugr::ValidationError), | ||
} | ||
|
||
/// String to print when validation is successful. | ||
pub const VALID_PRINT: &str = "HUGR valid!"; | ||
|
||
impl CmdLineArgs { | ||
/// Run the HUGR cli and validate against an extension registry. | ||
pub fn run(&self, registry: &ExtensionRegistry) -> Result<(), CliError> { | ||
let mut hugr: Hugr = serde_json::from_reader(self.input.into_reader()?)?; | ||
if self.mermaid { | ||
println!("{}", hugr.mermaid_string()); | ||
} | ||
|
||
if !self.no_validate { | ||
hugr.update_validate(registry)?; | ||
|
||
println!("{}", VALID_PRINT); | ||
} | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
//! Validate serialized HUGR on the command line | ||
use hugr::std_extensions::arithmetic::{ | ||
conversions::EXTENSION as CONVERSIONS_EXTENSION, float_ops::EXTENSION as FLOAT_OPS_EXTENSION, | ||
float_types::EXTENSION as FLOAT_TYPES_EXTENSION, int_ops::EXTENSION as INT_OPS_EXTENSION, | ||
int_types::EXTENSION as INT_TYPES_EXTENSION, | ||
}; | ||
use hugr::std_extensions::logic::EXTENSION as LOGICS_EXTENSION; | ||
|
||
use hugr::extension::{ExtensionRegistry, PRELUDE}; | ||
|
||
use clap::Parser; | ||
use hugr::cli::CmdLineArgs; | ||
|
||
fn main() { | ||
let opts = CmdLineArgs::parse(); | ||
|
||
// validate with all std extensions | ||
let reg = ExtensionRegistry::try_new([ | ||
PRELUDE.to_owned(), | ||
INT_OPS_EXTENSION.to_owned(), | ||
INT_TYPES_EXTENSION.to_owned(), | ||
CONVERSIONS_EXTENSION.to_owned(), | ||
FLOAT_OPS_EXTENSION.to_owned(), | ||
FLOAT_TYPES_EXTENSION.to_owned(), | ||
LOGICS_EXTENSION.to_owned(), | ||
]) | ||
.unwrap(); | ||
|
||
if let Err(e) = opts.run(®) { | ||
eprintln!("{}", e); | ||
std::process::exit(1); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
#![cfg(feature = "cli")] | ||
use assert_cmd::Command; | ||
use assert_fs::{fixture::FileWriteStr, NamedTempFile}; | ||
use hugr::{ | ||
builder::{Container, Dataflow, DataflowHugr}, | ||
extension::prelude::{BOOL_T, QB_T}, | ||
type_row, | ||
types::FunctionType, | ||
Hugr, | ||
}; | ||
use predicates::{prelude::*, str::contains}; | ||
use rstest::{fixture, rstest}; | ||
|
||
use hugr::builder::DFGBuilder; | ||
use hugr::cli::VALID_PRINT; | ||
#[fixture] | ||
fn cmd() -> Command { | ||
Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap() | ||
} | ||
|
||
#[fixture] | ||
fn test_hugr() -> Hugr { | ||
let df = DFGBuilder::new(FunctionType::new_endo(type_row![BOOL_T])).unwrap(); | ||
let [i] = df.input_wires_arr(); | ||
df.finish_prelude_hugr_with_outputs([i]).unwrap() | ||
} | ||
|
||
#[fixture] | ||
fn test_hugr_string(test_hugr: Hugr) -> String { | ||
serde_json::to_string(&test_hugr).unwrap() | ||
} | ||
|
||
#[fixture] | ||
fn test_hugr_file(test_hugr_string: String) -> NamedTempFile { | ||
let file = assert_fs::NamedTempFile::new("sample.hugr").unwrap(); | ||
file.write_str(&test_hugr_string).unwrap(); | ||
file | ||
} | ||
|
||
#[rstest] | ||
fn test_doesnt_exist(mut cmd: Command) { | ||
cmd.arg("foobar"); | ||
cmd.assert() | ||
.failure() | ||
.stderr(contains("No such file or directory").and(contains("Error reading input"))); | ||
} | ||
|
||
#[rstest] | ||
fn test_validate(test_hugr_file: NamedTempFile, mut cmd: Command) { | ||
cmd.arg(test_hugr_file.path()); | ||
cmd.assert().success().stdout(contains(VALID_PRINT)); | ||
} | ||
|
||
#[rstest] | ||
fn test_stdin(test_hugr_string: String, mut cmd: Command) { | ||
cmd.write_stdin(test_hugr_string); | ||
cmd.arg("-"); | ||
|
||
cmd.assert().success().stdout(contains(VALID_PRINT)); | ||
} | ||
|
||
#[rstest] | ||
fn test_mermaid(test_hugr_file: NamedTempFile, mut cmd: Command) { | ||
const MERMAID: &str = "graph LR\n subgraph 0 [\"(0) DFG\"]"; | ||
cmd.arg(test_hugr_file.path()); | ||
cmd.arg("--mermaid"); | ||
cmd.arg("--no-validate"); | ||
cmd.assert().success().stdout(contains(MERMAID)); | ||
} | ||
|
||
#[rstest] | ||
fn test_bad_hugr(mut cmd: Command) { | ||
let df = DFGBuilder::new(FunctionType::new_endo(type_row![QB_T])).unwrap(); | ||
let bad_hugr = df.hugr().clone(); | ||
|
||
let bad_hugr_string = serde_json::to_string(&bad_hugr).unwrap(); | ||
cmd.write_stdin(bad_hugr_string); | ||
cmd.arg("-"); | ||
|
||
cmd.assert() | ||
.failure() | ||
.stderr(contains("Error validating HUGR").and(contains("unconnected port"))); | ||
} | ||
|
||
#[rstest] | ||
fn test_bad_json(mut cmd: Command) { | ||
cmd.write_stdin(r#"{"foo": "bar"}"#); | ||
cmd.arg("-"); | ||
|
||
cmd.assert() | ||
.failure() | ||
.stderr(contains("Error parsing input")); | ||
} |