use {
crate::process,
ferrotype::Ferrotype,
proc_macro2::Span,
quote::quote,
};
#[test_log::test]
fn test_error_not_a_struct() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub enum NotAStruct {
Variant1,
Variant2,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected ParseItemNotStruct error");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
error_message.contains("struct"),
"Error message should mention 'struct', got: {error_message}",
);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_function_instead_of_struct() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub fn not_a_struct() -> i32 {
42
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected error for function instead of struct");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
error_message.contains("struct"),
"Error message should mention 'struct', got: {error_message}",
);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_unsupported_container_type() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub struct UnsupportedContainer {
map_field: HashMap<String, NodeId<crate::Type>>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected error for unsupported container type");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
error_message.contains("literal types do not take any arguments"),
"Error message should mention literal types, got: {error_message}",
);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_missing_node_id_generic() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub struct MissingGeneric {
bad_node: NodeId,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected error for NodeId without generic parameter");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
!error_message.is_empty(),
"Error message should not be empty"
);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_conflicting_flags() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST, CST)]
pub struct Conflicting {
field: NodeId<crate::Type>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST, CST), input) {
| Ok(_) => {
snapshot.add("Note", "Conflicting flags were accepted".to_string());
},
| Err(e) => {
snapshot.add_debug("Error", &e);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_no_syntax_type() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax()]
pub struct NoType {
field: NodeId<crate::Type>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(), input) {
| Ok(_) => {
panic!("Expected error for missing AST/CST flag");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_span_preservation() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub struct SpanTest {
good_field: NodeId<crate::Type>,
bad_field: HashMap<String, i32>,
another_good: Option<NodeId<crate::Type>>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected error for bad field");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
!error_message.is_empty(),
"Error message should not be empty"
);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_with_help_message() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub trait NotAStruct {
fn method();
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected error for trait instead of struct");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
!error_message.is_empty(),
"Error message should not be empty"
);
snapshot.add("Error Message", error_message);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_multiple_errors() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub struct MultipleErrors {
bad1: String,
bad2: i32,
bad3: HashMap<String, i32>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected errors for multiple bad fields");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
!error_message.is_empty(),
"Error message should not be empty"
);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_complex_invalid_type() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub struct ComplexInvalid {
good: NodeId<crate::Type>,
bad_result: Result<NodeId<crate::Type>, Error>,
bad_box: Box<NodeId<crate::Type>>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected error for unsupported wrapper types");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
error_message.contains("field") || error_message.contains("type"),
"Error message should mention 'field' or 'type', got: {error_message}",
);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_recovery_suggestions() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub struct NeedsSuggestion {
almost_right: HashMap<String, crate::Type>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected error with suggestion");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
!error_message.is_empty(),
"Error message should not be empty"
);
snapshot.add("Error Message", error_message);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_empty_struct() {
let mut snapshot = Ferrotype::new();
let input = quote! {
#[laburnum_syntax(AST)]
pub struct Empty {}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(output) => {
snapshot.add_token_stream("Output", &output);
},
| Err(e) => {
snapshot.add_debug("Error", &e);
snapshot.set_expect_errors(true);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_combined_errors() {
use syn::Error as SynError;
let span = Span::call_site();
let mut main_error = SynError::new(span, "main error");
let secondary_error = SynError::new(span, "secondary error");
main_error.combine(secondary_error);
let error_string = main_error.to_string();
assert!(error_string.contains("main error"));
}
#[test_log::test]
fn test_custom_error_creation() {
let token_stream = quote! { invalid_token };
let error = syn::Error::new_spanned(token_stream, "custom error message");
assert!(error.to_string().contains("custom error"));
}
#[test_log::test]
fn test_cst_missing_span_field() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(CST)]
pub struct MissingSpan {
other_field: NodeId<crate::Type>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(CST), input) {
| Ok(_) => {
panic!("Expected MissingRequiredSpanField error");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
error_message.contains("span") && error_message.contains("Span"),
"Error message should mention 'span' and 'Span', got: {error_message}",
);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_cst_invalid_field_naming_convention() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(CST)]
pub struct InvalidNaming {
span: Span,
bad_control: ControlSpan,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(CST), input) {
| Ok(_) => {
panic!("Expected InvalidFieldNamingConvention error");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
error_message.contains("bad_control")
&& error_message.contains("_token"),
"Error message should mention 'bad_control' and '_token', got: {error_message}",
);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_cst_keyword_span_naming() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(CST)]
pub struct KeywordNaming {
span: Span,
bad_name: KeywordSpan,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(CST), input) {
| Ok(_) => {
panic!("Expected InvalidFieldNamingConvention error for KeywordSpan");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
error_message.contains("bad_name")
&& error_message.contains("_keyword"),
"Error message should mention 'bad_name' and '_keyword', got: {error_message}",
);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_reserved_keyword_field_name_error_details() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub struct ReservedKeyword {
self_: NodeId<crate::Type>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected ReservedKeywordFieldName error");
},
| Err(e) => {
snapshot.add_debug("Error", &e);
let error_message = e.to_string();
assert!(
error_message.contains("self_")
&& error_message.contains("Self")
&& error_message.contains("reserved keyword"),
"Error message should mention 'self_', 'Self', and 'reserved keyword', got: {error_message}",
);
snapshot.add("Error Message", error_message);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_ast_error_accumulation_multiple_field_errors() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub struct MultipleErrors {
bad_field1: HashMap<String, i32>,
bad_field2: Result<NodeId<crate::Type>, Error>,
self_: NodeId<crate::Type>,
good_field: NodeId<crate::Type>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input.clone()) {
| Ok(_) => {
panic!("Expected multiple accumulated errors");
},
| Err(accumulated_error) => {
snapshot.add("Accumulated Errors", accumulated_error.to_string());
let error_str = accumulated_error.to_string();
assert!(
!error_str.is_empty(),
"Expected non-empty error message"
);
let compiler_output = accumulated_error.to_compile_error().to_string();
assert!(
compiler_output.len() > 50,
"Expected detailed compiler error output with multiple issues"
);
},
}
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected error from regular processing too");
},
| Err(single_error) => {
snapshot.add_debug("Single Error (Regular Processing)", &single_error);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_cst_error_accumulation_multiple_naming_errors() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(CST)]
pub struct MultipleNamingErrors {
span: Span,
bad_control: ControlSpan,
bad_keyword: KeywordSpan,
bad_scalar: ScalarSpan,
good_token: ControlSpan,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(CST), input.clone()) {
| Ok(_) => {
panic!("Expected multiple accumulated naming errors");
},
| Err(accumulated_error) => {
snapshot.add("Accumulated Naming Errors", accumulated_error.to_string());
let error_str = accumulated_error.to_string();
assert!(
error_str.contains("bad_control") || error_str.contains("naming"),
"Should mention field naming issues"
);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_accumulation_vs_single_error() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub struct CompareErrorModes {
bad1: String,
bad2: i32,
bad3: HashMap<String, Vec<i32>>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input.clone()) {
| Ok(_) => panic!("Expected error in single mode"),
| Err(single_error) => {
snapshot.add_debug("Single Error Mode", &single_error);
},
}
match process(quote!(AST), input) {
| Ok(_) => panic!("Expected errors in accumulation mode"),
| Err(accumulated_errors) => {
snapshot.add("Accumulated Error Mode", accumulated_errors.to_string());
let acc_str = accumulated_errors.to_string();
assert!(
!acc_str.is_empty(),
"Accumulated errors should not be empty"
);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_accumulation_with_valid_fields() {
let mut snapshot = Ferrotype::new();
let input = quote! {
#[laburnum_syntax(AST)]
pub struct MixedValidInvalid {
good1: NodeId<crate::Type>,
good2: Option<NodeId<crate::Expression>>,
good3: Vec<NodeId<crate::Statement>>,
bad1: HashMap<String, i32>,
good4: Field<bool>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(_) => {
snapshot.add("Result", "Unexpectedly succeeded".to_string());
},
| Err(error) => {
snapshot.add("Error with Mixed Fields", error.to_string());
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_accumulator_utility() {
use crate::error::{
Error,
ErrorAccumulator,
};
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let mut accumulator = ErrorAccumulator::new();
accumulator
.add_error(Error::MissingFieldIdentifier(proc_macro2::Span::call_site()));
accumulator.add_error(Error::EmptyTypePath(proc_macro2::Span::call_site()));
accumulator.add_error(Error::ReservedKeywordFieldName(
proc_macro2::Span::call_site(),
"self_".to_string(),
"Self".to_string(),
));
assert_eq!(accumulator.error_count(), 3);
assert!(accumulator.has_errors());
match accumulator.into_result(()) {
| Ok(_) => panic!("Should have failed with errors"),
| Err(combined_error) => {
snapshot.add("Combined Error", combined_error.to_string());
let error_str = combined_error.to_string();
assert!(!error_str.is_empty());
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_accumulation_guaranteed_multiple_failures() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub struct GuaranteedFailures {
field1: UnsupportedType1<i32>,
field2: UnsupportedType2<String>,
field3: UnsupportedType3<bool>,
field4: CompletelyInvalidType<u8>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input.clone()) {
| Ok(_) => {
panic!("Expected multiple parsing failures");
},
| Err(accumulated_error) => {
snapshot.add("Accumulated Parse Errors", accumulated_error.to_string());
let error_str = accumulated_error.to_string();
let error_count = error_str.matches("error").count()
+ error_str.matches("failed").count()
+ error_str.matches("invalid").count();
snapshot.add("Approximate Error Count", error_count.to_string());
},
}
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected error from regular parsing too");
},
| Err(single_error) => {
snapshot.add_debug("Single Parse Error", &single_error);
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_error_accumulator_detailed_behavior() {
use crate::error::{
Error,
ErrorAccumulator,
};
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let mut accumulator = ErrorAccumulator::new();
accumulator.add_error(Error::UnsupportedFieldType(
proc_macro2::Span::call_site(),
"HashMap<String, i32>".to_string(),
));
accumulator.add_error(Error::ReservedKeywordFieldName(
proc_macro2::Span::call_site(),
"self_".to_string(),
"Self".to_string(),
));
accumulator.add_error(Error::InvalidFieldNamingConvention(
proc_macro2::Span::call_site(),
"bad_field".to_string(),
"_token".to_string(),
));
snapshot.add("Error Count", accumulator.error_count().to_string());
snapshot.add("Has Errors", accumulator.has_errors().to_string());
match accumulator.into_result(()) {
| Ok(_) => panic!("Should have failed with multiple errors"),
| Err(combined) => {
snapshot.add("Combined Error Message", combined.to_string());
let msg = combined.to_string();
let contains_field_type = msg.contains("field") || msg.contains("type");
let contains_keyword = msg.contains("keyword") || msg.contains("Self");
let contains_naming =
msg.contains("naming") || msg.contains("convention");
snapshot
.add("Contains Field Type Error", contains_field_type.to_string());
snapshot.add("Contains Keyword Error", contains_keyword.to_string());
snapshot.add("Contains Naming Error", contains_naming.to_string());
},
}
ferrotype::assert!(snapshot);
}
#[test_log::test]
fn test_multiple_errors_in_compiler_output() {
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true);
let input = quote! {
#[laburnum_syntax(AST)]
pub struct MultipleCompilerErrors {
bad_field1: HashMap<String, i32>,
bad_field2: Result<NodeId<crate::Type>, Error>,
self_: NodeId<crate::Type>,
}
};
snapshot.add_token_stream("Input", &input);
match process(quote!(AST), input) {
| Ok(_) => {
panic!("Expected multiple errors in compiler output");
},
| Err(combined_error) => {
let error_output = combined_error.to_compile_error().to_string();
snapshot.add("Compiler Error Output", error_output.clone());
let error_str = combined_error.to_string();
snapshot.add("Error String", error_str.clone());
let has_field_errors =
error_str.contains("field") || error_str.contains("struct");
let has_type_errors =
error_str.contains("type") || error_str.contains("HashMap");
let has_keyword_errors =
error_str.contains("keyword") || error_str.contains("self_");
snapshot.add("Has Field Errors", has_field_errors.to_string());
snapshot.add("Has Type Errors", has_type_errors.to_string());
snapshot.add("Has Keyword Errors", has_keyword_errors.to_string());
assert!(
error_output.len() > 100,
"Compiler output should be substantial for multiple errors"
);
},
}
ferrotype::assert!(snapshot);
}