use std::process::{Command, Output};
fn binary() -> &'static str {
env!("CARGO_BIN_EXE_surreal-lsp")
}
fn run(args: &[&str]) -> Output {
Command::new(binary())
.args(args)
.env_remove("SURREAL_INVOKED_AS")
.env_remove("SURREAL_CLI_LAUNCHER")
.output()
.expect("spawn")
}
const LAUNCHER_ENV: [(&str, &str); 2] = [
("SURREAL_INVOKED_AS", "surreal lsp"),
("SURREAL_CLI_LAUNCHER", "/usr/local/bin/surreal"),
];
fn run_launched(args: &[&str]) -> Output {
Command::new(binary())
.args(args)
.envs(LAUNCHER_ENV)
.output()
.expect("spawn")
}
fn envelope(output: &Output) -> serde_json::Value {
serde_json::from_slice(&output.stderr).unwrap_or_else(|err| {
panic!(
"stderr is not one JSON object ({err}): {}",
String::from_utf8_lossy(&output.stderr)
)
})
}
fn stdout(output: &Output) -> String {
String::from_utf8(output.stdout.clone()).expect("utf-8 stdout")
}
fn expected_version_line() -> String {
format!(
"{} for {} on {}\n",
env!("CARGO_PKG_VERSION"),
std::env::consts::OS,
std::env::consts::ARCH
)
}
#[test]
fn the_version_flag_prints_the_release_and_platform() {
for args in [&["--version"][..], &["-v"], &["-V"]] {
for output in [run(args), run_launched(args)] {
assert_eq!(output.status.code(), Some(0), "{args:?}");
assert!(output.stderr.is_empty(), "stderr: {:?}", output.stderr);
assert_eq!(stdout(&output), expected_version_line(), "{args:?}");
}
}
}
#[test]
fn the_version_flag_with_json_prints_one_object() {
let expected = format!(
"{{\n \"arch\": \"{}\",\n \"os\": \"{}\",\n \"version\": \"{}\"\n}}\n",
std::env::consts::ARCH,
std::env::consts::OS,
env!("CARGO_PKG_VERSION"),
);
for args in [
&["--version", "--json"][..],
&["--json", "--version"],
&["-v", "-j"],
&["-j", "-v"],
&["-vj"],
&["-V", "-j"],
] {
for output in [run(args), run_launched(args)] {
assert_eq!(output.status.code(), Some(0), "{args:?}");
assert!(output.stderr.is_empty(), "stderr: {:?}", output.stderr);
assert_eq!(stdout(&output), expected, "{args:?}");
}
}
}
#[test]
fn version_is_not_a_command() {
for args in [&["version"][..], &["version", "--json"]] {
let output = run(args);
assert_eq!(output.status.code(), Some(2), "{args:?}");
assert!(output.stdout.is_empty(), "stdout: {:?}", output.stdout);
}
}
#[test]
fn a_top_level_json_without_the_version_flag_is_a_usage_error() {
for flag in ["--json", "-j"] {
let output = run(&[flag]);
assert_eq!(output.status.code(), Some(2), "{flag}");
assert!(output.stdout.is_empty(), "stdout: {:?}", output.stdout);
let value = envelope(&output);
assert_eq!(value["kind"], "usage");
assert_eq!(value["command"], "surreal-lsp --help");
}
}
#[test]
fn a_usage_error_under_json_is_the_error_envelope() {
for flag in ["--json", "-j"] {
let output = run_launched(&["check", flag, "--bogus", "a.surql"]);
assert_eq!(output.status.code(), Some(2), "{flag}");
assert!(output.stdout.is_empty(), "stdout: {:?}", output.stdout);
let value = envelope(&output);
let expected = serde_json::json!({
"kind": "usage",
"message": "unexpected argument '--bogus' found",
"status": null,
"code": null,
"request_id": null,
"hint": "See the usage with:",
"command": "surreal lsp check --help",
"docs": null,
"retry_after_secs": null,
"exit_code": 2,
});
assert_eq!(value, expected, "{flag}");
}
}
#[test]
fn a_usage_error_under_format_json_is_the_error_envelope() {
let json = run_launched(&["check", "--json", "--bogus", "a.surql"]);
for args in [
&["check", "--format", "json", "--bogus", "a.surql"][..],
&["check", "--format=json", "--bogus", "a.surql"],
] {
let output = run_launched(args);
assert_eq!(output.status.code(), Some(2), "{args:?}");
assert!(output.stdout.is_empty(), "stdout: {:?}", output.stdout);
assert_eq!(output.stderr, json.stderr, "{args:?}");
}
}
#[test]
fn a_usage_error_under_a_cluster_with_j_is_the_error_envelope() {
let output = run(&["check", "-jj", "a.surql"]);
assert_eq!(output.status.code(), Some(2));
assert!(output.stdout.is_empty(), "stdout: {:?}", output.stdout);
let value = envelope(&output);
assert_eq!(value["kind"], "usage");
assert_eq!(
value["message"],
"the argument '--json' cannot be used multiple times"
);
}
#[test]
fn a_missing_argument_under_json_names_the_argument() {
let output = run(&["check", "--json"]);
assert_eq!(output.status.code(), Some(2));
assert!(output.stdout.is_empty(), "stdout: {:?}", output.stdout);
let value = envelope(&output);
assert_eq!(value["kind"], "usage");
assert_eq!(
value["message"],
"the following required arguments were not provided: <PATHS>..."
);
assert_eq!(value["command"], "surreal-lsp check --help");
}
#[test]
fn json_with_format_conflicts_only_with_format_text() {
let output = run(&["check", "--json", "--format", "json", "--bogus"]);
assert_eq!(
envelope(&output)["message"],
"unexpected argument '--bogus' found"
);
let output = run(&["check", "--json", "--format", "text", "a.surql"]);
assert_eq!(output.status.code(), Some(2));
assert!(output.stdout.is_empty(), "stdout: {:?}", output.stdout);
let value = envelope(&output);
assert_eq!(value["kind"], "usage");
assert_eq!(
value["message"],
"the argument '--json' cannot be used with '--format text'"
);
}
#[test]
fn a_usage_error_clap_cannot_state_is_the_error_envelope_too() {
let output = run(&["check", "--json", "--stdin-filename", "a.surql", "a.surql"]);
assert_eq!(output.status.code(), Some(2));
assert!(output.stdout.is_empty(), "stdout: {:?}", output.stdout);
let value = envelope(&output);
assert_eq!(value["kind"], "usage");
assert_eq!(value["message"], "`--stdin-filename` needs `--stdin`");
assert_eq!(value["command"], "surreal-lsp check --help");
assert_eq!(value["exit_code"], 2);
}
#[test]
fn a_usage_error_without_json_is_clap_text() {
for args in [
&["check", "--bogus", "a.surql"][..],
&["check", "--bogus", "--", "--json"],
] {
let output = run_launched(args);
assert_eq!(output.status.code(), Some(2), "{args:?}");
let text = String::from_utf8(output.stderr).expect("utf-8 stderr");
assert!(
text.starts_with("error: unexpected argument '--bogus' found"),
"{text}"
);
assert!(text.contains("Usage: surreal lsp check"), "{text}");
}
}
#[test]
fn help_ignores_json() {
for args in [
&["check", "--json", "--help"][..],
&["--json", "--help"],
&["--version", "-j", "--help"],
] {
let output = run_launched(args);
assert_eq!(output.status.code(), Some(0), "{args:?}");
assert!(output.stderr.is_empty(), "stderr: {:?}", output.stderr);
let text = stdout(&output);
assert!(!text.trim_start().starts_with('{'), "{text}");
}
}