use std::io::Write as _;
use std::process::{Command, Output, Stdio};
const CBOR: &str = env!("CARGO_BIN_EXE_cbor");
fn run(args: &[&str], input: &[u8]) -> Output {
let mut child = Command::new(CBOR)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("binary spawns");
child
.stdin
.take()
.expect("stdin is piped")
.write_all(input)
.expect("input is consumed");
child.wait_with_output().expect("binary exits")
}
fn ok(args: &[&str], input: &[u8]) -> Vec<u8> {
let out = run(args, input);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(out.stderr.is_empty());
out.stdout
}
#[test]
fn show_preserves_wire_details() {
let out = ok(&[], &hex("bf61610161629f0203ffff"));
assert_eq!(
out,
b"{_\n \"a\": 1,\n \"b\": [_\n 2,\n 3\n ]\n}\n"
);
let out = ok(&[], &hex("f7c074323031332d30332d32315432303a30343a30305a"));
assert_eq!(out, b"undefined\n0(\"2013-03-21T20:04:00Z\")\n");
let out = ok(&[], &hex("c249010000000000000000"));
assert_eq!(out, b"18446744073709551616\n");
}
#[test]
fn show_accepts_hex_and_base64_arguments() {
assert_eq!(ok(&["a201020326"], b""), b"{\n 1: 2,\n 3: -7\n}\n");
assert_eq!(ok(&["0xA201020326"], b""), b"{\n 1: 2,\n 3: -7\n}\n");
assert_eq!(ok(&["a2 0102 0326"], b""), b"{\n 1: 2,\n 3: -7\n}\n");
assert_eq!(ok(&["oWFhAQ=="], b""), b"{\n \"a\": 1\n}\n");
assert_eq!(ok(&["oWFhAQ"], b""), b"{\n \"a\": 1\n}\n");
assert_eq!(ok(&["Q_vvvg"], b""), b"h'fbefbe'\n");
assert_eq!(ok(&["0102"], b""), b"1\n2\n");
}
#[test]
fn decode_pretty_prints_json() {
let out = ok(&["decode"], &hex("a1616101"));
assert_eq!(out, b"{\n \"a\": 1\n}\n");
let out = ok(&["decode"], &hex("01a16374776f02"));
assert_eq!(out, b"1\n{\n \"two\": 2\n}\n");
let out = ok(&["decode"], &hex("a10742dead"));
assert_eq!(out, b"{\n \"7\": \"dead\"\n}\n");
let out = ok(&["decode"], &hex("c11a514b67b0"));
assert_eq!(out, b"1363896240\n");
let out = ok(&["decode", "a1616101"], b"");
assert_eq!(out, b"{\n \"a\": 1\n}\n");
}
#[test]
fn decode_diag_pretty_prints_diagnostic_notation() {
let out = ok(&["decode", "--diag", "a101820203"], b"");
assert_eq!(out, b"{\n 1: [\n 2,\n 3\n ]\n}\n");
let out = ok(&["decode", "-d", "01"], b"");
assert_eq!(out, b"1\n");
let out = ok(&["decode", "-d", "bf0101ff"], b"");
assert_eq!(out, b"{_\n 1: 1\n}\n");
}
#[test]
fn encode_streams_json_values() {
assert_eq!(ok(&["encode"], br#"{"a": 1}"#), hex("a1616101"));
assert_eq!(
ok(&["encode"], b"1 {\"two\":2}\n[3]"),
hex("01a16374776f028103")
);
}
#[test]
fn commands_chain_into_a_round_trip() {
let json = br#"{"name": "example", "ok": true, "tags": [1, 2.5]}"#;
let cbor = ok(&["encode"], json);
let shown = ok(&[], &cbor);
assert_eq!(
shown,
b"{\n \"name\": \"example\",\n \"ok\": true,\n \"tags\": [\n 1,\n 2.5\n ]\n}\n"
);
let back = ok(&["decode"], &cbor);
let reparsed: serde_json::Value = serde_json::from_slice(&back).unwrap();
assert_eq!(
reparsed,
serde_json::from_slice::<serde_json::Value>(json).unwrap()
);
}
#[test]
fn reads_from_a_file_argument() {
let path = std::env::temp_dir().join(format!("cbor_cli_test_{}.cbor", std::process::id()));
std::fs::write(&path, hex("8401020304")).unwrap();
let out = ok(&[path.to_str().unwrap()], b"");
assert_eq!(out, b"[\n 1,\n 2,\n 3,\n 4\n]\n");
let out = ok(&["decode", path.to_str().unwrap()], b"");
assert_eq!(out, b"[\n 1,\n 2,\n 3,\n 4\n]\n");
let out = ok(&["-"], &hex("00"));
assert_eq!(out, b"0\n");
std::fs::remove_file(&path).unwrap();
}
#[test]
fn help_and_version_print_and_exit_cleanly() {
for args in [&["--help"][..], &["-h"][..], &["decode", "--help"][..]] {
let out = run(args, b"");
assert!(out.status.success());
let text = String::from_utf8(out.stdout).unwrap();
assert!(text.contains("Usage: cbor [COMMAND] [INPUT]"), "{text}");
}
let out = run(&["--version"], b"");
assert!(out.status.success());
let text = String::from_utf8(out.stdout).unwrap();
assert!(text.contains(env!("CARGO_PKG_VERSION")), "{text}");
}
#[test]
fn usage_errors_exit_with_status_2() {
for args in [
&["--bogus"][..],
&["decode", "a1616101", "01"][..],
&["--diag", "01"][..],
&["encode", "--diag"][..],
&["/nonexistent/cbor_cli_test"][..],
&["Q/vvvg=="][..],
&["not hex, not base64!"][..],
] {
let out = run(args, b"");
assert_eq!(out.status.code(), Some(2), "args: {args:?}");
assert!(
String::from_utf8_lossy(&out.stderr).contains("Try `cbor --help`"),
"args: {args:?}"
);
}
}
#[test]
fn data_errors_exit_with_status_1() {
for (args, input) in [
(&["1a0000"][..], &b""[..]),
(&["decode"][..], &hex("ff")[..]),
(&["encode"][..], &b"{broken"[..]),
] {
let out = run(args, input);
assert_eq!(out.status.code(), Some(1), "args: {args:?}");
assert!(
String::from_utf8_lossy(&out.stderr).starts_with("cbor:"),
"args: {args:?}"
);
}
}
fn hex(s: &str) -> Vec<u8> {
assert!(s.len() % 2 == 0);
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}