#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
#![allow(dead_code)]
use predicates::prelude::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[derive(Debug)]
struct ErrorMessageQuality {
has_error_prefix: bool, has_source_location: bool, has_code_snippet: bool, has_caret_indicator: bool, has_explanation: bool, has_suggestion: bool, message_length: usize,
}
impl ErrorMessageQuality {
fn from_stderr(stderr: &str) -> Self {
Self {
has_error_prefix: stderr.contains("error:")
|| stderr.contains("Error:")
|| stderr.contains("error["),
has_source_location: stderr.contains(':')
&& stderr.chars().filter(|c| c.is_numeric()).count() > 0,
has_code_snippet: stderr.lines().any(|l| {
!l.starts_with("error:")
&& !l.starts_with("Error:")
&& !l.starts_with("note:")
&& !l.starts_with("help:")
}),
has_caret_indicator: stderr.contains('^'),
has_explanation: stderr.contains("note:") || stderr.contains("note"),
has_suggestion: stderr.contains("help:") || stderr.contains("consider"),
message_length: stderr.len(),
}
}
fn score(&self) -> f32 {
let mut score = 0.0;
if self.has_error_prefix {
score += 1.0;
}
if self.has_source_location {
score += 1.5;
}
if self.has_code_snippet {
score += 1.5;
}
if self.has_caret_indicator {
score += 1.0;
}
if self.has_explanation {
score += 2.0;
}
if self.has_suggestion {
score += 2.0;
}
if self.message_length > 500 {
score -= 1.0;
}
score / 9.0 }
}
#[test]
fn test_async_syntax_error_message() {
let rust_code = r#"
async fn fetch_data() -> Result<String, Error> {
Ok("data".to_string())
}
fn main() {
let data = fetch_data().await;
}
"#;
let mut file = NamedTempFile::new().unwrap();
file.write_all(rust_code.as_bytes()).unwrap();
let output = assert_cmd::cargo_bin_cmd!("bashrs")
.arg("build")
.arg(file.path())
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("async") || stderr.contains("Unsupported"),
"Error should mention async or unsupported. Got: {}",
stderr
);
assert_eq!(
output.status.code(),
Some(1),
"Should fail with exit code 1. Got: {:?}",
output.status.code()
);
}
#[test]
fn test_unsafe_block_error_message() {
let rust_code = r#"
fn main() {
unsafe {
let ptr = std::ptr::null::<i32>();
}
}
"#;
let mut file = NamedTempFile::new().unwrap();
file.write_all(rust_code.as_bytes()).unwrap();
let output = assert_cmd::cargo_bin_cmd!("bashrs")
.arg("build")
.arg(file.path())
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("unsafe") || stderr.contains("Unsupported") || stderr.contains("error"),
"Error should mention unsafe or unsupported. Got: {}",
stderr
);
assert_eq!(output.status.code(), Some(1));
}
#[test]
fn test_trait_definition_transpiles_successfully() {
let rust_code = r#"
trait Drawable {
fn draw(&self);
}
fn main() {}
"#;
let mut file = NamedTempFile::new().unwrap();
file.write_all(rust_code.as_bytes()).unwrap();
let output = assert_cmd::cargo_bin_cmd!("bashrs")
.arg("build")
.arg(file.path())
.output()
.unwrap();
assert!(
output.status.success(),
"Trait definitions should be silently ignored. Exit: {:?}, stderr: {}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_impl_block_transpiles_successfully() {
let rust_code = r#"
struct Foo;
impl Foo {
fn new() -> Self { Foo }
}
fn main() {}
"#;
let mut file = NamedTempFile::new().unwrap();
file.write_all(rust_code.as_bytes()).unwrap();
let output = assert_cmd::cargo_bin_cmd!("bashrs")
.arg("build")
.arg(file.path())
.output()
.unwrap();
assert!(
output.status.success(),
"Impl blocks should be silently ignored. Exit: {:?}, stderr: {}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_generic_type_transpiles_successfully() {
let rust_code = r#"
fn process<T>(item: T) -> T {
item
}
fn main() {}
"#;
let mut file = NamedTempFile::new().unwrap();
file.write_all(rust_code.as_bytes()).unwrap();
let output = assert_cmd::cargo_bin_cmd!("bashrs")
.arg("build")
.arg(file.path())
.output()
.unwrap();
assert!(
output.status.success(),
"Generic functions should transpile. Exit: {:?}, stderr: {}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_loop_statement_transpiles_successfully() {
let rust_code = r#"
fn main() {
loop {
break;
}
}
"#;
let mut file = NamedTempFile::new().unwrap();
file.write_all(rust_code.as_bytes()).unwrap();
let output = assert_cmd::cargo_bin_cmd!("bashrs")
.arg("build")
.arg(file.path())
.output()
.unwrap();
assert!(
output.status.success(),
"loop+break should transpile. Exit: {:?}, stderr: {}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_use_statement_transpiles_successfully() {
let rust_code = r#"
use std::collections::HashMap;
fn main() {}
"#;
let mut file = NamedTempFile::new().unwrap();
file.write_all(rust_code.as_bytes()).unwrap();
let output = assert_cmd::cargo_bin_cmd!("bashrs")
.arg("build")
.arg(file.path())
.output()
.unwrap();
assert!(
output.status.success(),
"Use statements should be silently ignored. Exit: {:?}, stderr: {}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_syntax_error_diagnostic() {
let rust_code = r#"
fn main() {
let x = 10 +;
}
"#;
let mut file = NamedTempFile::new().unwrap();
file.write_all(rust_code.as_bytes()).unwrap();
let output = assert_cmd::cargo_bin_cmd!("bashrs")
.arg("build")
.arg(file.path())
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("error") || stderr.contains("Error"),
"Should contain error message. Got: {}",
stderr
);
assert_eq!(output.status.code(), Some(2)); }
#[test]
fn test_help_flag() {
assert_cmd::cargo_bin_cmd!("bashrs")
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("bashrs"))
.stdout(predicate::str::contains("Rust-to-Shell transpiler"));
}
#[test]
fn test_version_flag() {
assert_cmd::cargo_bin_cmd!("bashrs")
.arg("--version")
.assert()
.success()
.stdout(predicate::str::contains("bashrs"));
}
#[test]
include!("cli_error_handling_tests_main.rs");