use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::OnceLock;
struct Output {
stdout: String,
stderr: String,
success: bool,
}
static EXAMPLE_BUILD: OnceLock<()> = OnceLock::new();
fn build_examples() {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let status = Command::new(cargo)
.args(["build", "--examples", "--quiet"])
.status()
.expect("failed to spawn cargo build");
assert!(
status.success(),
"cargo build --examples failed with {status}"
);
}
fn example_bin_path(name: &str) -> PathBuf {
let target_dir = std::env::var_os("CARGO_TARGET_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("target"));
let bin = target_dir.join("debug/examples").join(name);
#[cfg(windows)]
let bin = bin.with_extension("exe");
bin
}
fn run_example(name: &str, input: &str) -> Output {
EXAMPLE_BUILD.get_or_init(build_examples);
let mut child = Command::new(example_bin_path(name))
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("failed to spawn example binary");
child
.stdin
.as_mut()
.expect("stdin piped")
.write_all(input.as_bytes())
.expect("write stdin");
let out = child.wait_with_output().expect("wait example binary");
Output {
stdout: String::from_utf8(out.stdout).expect("utf8 stdout"),
stderr: String::from_utf8(out.stderr).expect("utf8 stderr"),
success: out.status.success(),
}
}
#[test]
fn parse_error_accepts_valid_json() {
let out = run_example("parse_error", r#"{"a": 1, "b": [true, null]}"#);
assert!(out.success, "stderr: {}", out.stderr);
assert!(out.stdout.contains("valid JSON"), "stdout: {}", out.stdout);
}
#[test]
fn parse_error_reports_line_column_and_caret() {
let out = run_example("parse_error", "{\n \"a\": 1,\n \"b\":\n}");
assert!(!out.success);
assert!(out.stderr.contains("error:"), "stderr: {}", out.stderr);
assert!(out.stderr.contains("4 |"), "stderr: {}", out.stderr);
assert!(out.stderr.contains("^ here"), "stderr: {}", out.stderr);
}
#[test]
fn jsonc_pretty_formats_object_multiline() {
let out = run_example("jsonc_pretty", r#"{"a":1,"b":2}"#);
assert!(out.success, "stderr: {}", out.stderr);
assert_eq!(out.stdout, "{\n \"a\": 1,\n \"b\": 2\n}\n");
}
#[test]
fn jsonc_pretty_preserves_comments_on_own_lines() {
let out = run_example("jsonc_pretty", r#"{"a":1,/*c*/"b":2}"#);
assert!(out.success, "stderr: {}", out.stderr);
assert_eq!(out.stdout, "{\n \"a\": 1,\n /*c*/\n \"b\": 2\n}\n");
}
#[test]
fn jsonc_pretty_keeps_empty_containers_compact() {
let out = run_example("jsonc_pretty", r#"{"a":[],"b":{}}"#);
assert!(out.success, "stderr: {}", out.stderr);
assert_eq!(out.stdout, "{\n \"a\": [],\n \"b\": {}\n}\n");
}
#[test]
fn jsonc_pretty_rejects_invalid_input() {
let out = run_example("jsonc_pretty", "{not valid}");
assert!(!out.success);
assert!(!out.stderr.is_empty());
}