1use anyhow::{Context, Result};
15use std::path::{Path, PathBuf};
16use std::process::{Command, Output};
17use tempfile::TempDir;
18
19#[derive(Debug, Clone)]
21pub struct CheckResult {
22 pub success: bool,
24 pub errors: Vec<CompilerError>,
26 pub warnings: Vec<CompilerWarning>,
28 pub stderr: String,
30}
31
32#[derive(Debug, Clone)]
34pub struct CompilerError {
35 pub code: Option<String>,
37 pub message: String,
39 pub span: Option<ErrorSpan>,
41 pub is_semantic: bool,
43}
44
45#[derive(Debug, Clone)]
47pub struct CompilerWarning {
48 pub code: Option<String>,
49 pub message: String,
50}
51
52#[derive(Debug, Clone)]
54pub struct ErrorSpan {
55 pub file: String,
56 pub line_start: u32,
57 pub line_end: u32,
58 pub column_start: u32,
59 pub column_end: u32,
60}
61
62pub struct EphemeralWorkspace {
77 dir: TempDir,
79 #[allow(dead_code)]
81 name: String,
82 lib_path: PathBuf,
84}
85
86impl EphemeralWorkspace {
87 pub fn new(name: &str, rust_code: &str, cargo_toml: &str) -> Result<Self> {
98 let dir = TempDir::new().context("Failed to create temp directory")?;
99 let dir_path = dir.path();
100
101 let src_dir = dir_path.join("src");
103 std::fs::create_dir_all(&src_dir).context("Failed to create src directory")?;
104
105 let cargo_path = dir_path.join("Cargo.toml");
107 std::fs::write(&cargo_path, cargo_toml).context("Failed to write Cargo.toml")?;
108
109 let lib_path = src_dir.join("lib.rs");
111 std::fs::write(&lib_path, rust_code).context("Failed to write lib.rs")?;
112
113 Ok(Self {
114 dir,
115 name: name.to_string(),
116 lib_path,
117 })
118 }
119
120 pub fn path(&self) -> &Path {
122 self.dir.path()
123 }
124
125 pub fn lib_path(&self) -> &Path {
127 &self.lib_path
128 }
129
130 pub fn check(&self) -> Result<CheckResult> {
141 let output = Command::new("cargo")
142 .arg("check")
143 .arg("--message-format=json")
144 .current_dir(self.dir.path())
145 .env_remove("CARGO_LLVM_COV")
147 .env_remove("CARGO_LLVM_COV_SHOW_ENV")
148 .env_remove("CARGO_LLVM_COV_TARGET_DIR")
149 .env_remove("LLVM_PROFILE_FILE")
150 .env_remove("RUSTFLAGS")
151 .env_remove("CARGO_INCREMENTAL")
152 .env_remove("CARGO_BUILD_JOBS")
153 .env_remove("CARGO_TARGET_DIR")
154 .output()
155 .context("Failed to run cargo check")?;
156
157 self.parse_cargo_output(output)
158 }
159
160 fn parse_cargo_output(&self, output: Output) -> Result<CheckResult> {
162 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
163 let stdout = String::from_utf8_lossy(&output.stdout);
164
165 let mut errors = Vec::new();
166 let mut warnings = Vec::new();
167
168 for line in stdout.lines() {
170 if let Ok(msg) = serde_json::from_str::<serde_json::Value>(line) {
171 if let Some(reason) = msg.get("reason").and_then(|r| r.as_str()) {
172 if reason == "compiler-message" {
173 if let Some(message) = msg.get("message") {
174 self.parse_compiler_message(message, &mut errors, &mut warnings);
175 }
176 }
177 }
178 }
179 }
180
181 Ok(CheckResult {
182 success: output.status.success(),
183 errors,
184 warnings,
185 stderr,
186 })
187 }
188
189 fn parse_compiler_message(
191 &self,
192 message: &serde_json::Value,
193 errors: &mut Vec<CompilerError>,
194 warnings: &mut Vec<CompilerWarning>,
195 ) {
196 let level = message.get("level").and_then(|l| l.as_str()).unwrap_or("");
197 let msg_text = message
198 .get("message")
199 .and_then(|m| m.as_str())
200 .unwrap_or("")
201 .to_string();
202 let code = message
203 .get("code")
204 .and_then(|c| c.get("code"))
205 .and_then(|c| c.as_str())
206 .map(|s| s.to_string());
207
208 let span = message
210 .get("spans")
211 .and_then(|s| s.as_array())
212 .and_then(|arr| arr.first())
213 .map(|s| ErrorSpan {
214 file: s
215 .get("file_name")
216 .and_then(|f| f.as_str())
217 .unwrap_or("")
218 .to_string(),
219 line_start: s.get("line_start").and_then(|l| l.as_u64()).unwrap_or(0) as u32,
220 line_end: s.get("line_end").and_then(|l| l.as_u64()).unwrap_or(0) as u32,
221 column_start: s.get("column_start").and_then(|c| c.as_u64()).unwrap_or(0) as u32,
222 column_end: s.get("column_end").and_then(|c| c.as_u64()).unwrap_or(0) as u32,
223 });
224
225 match level {
226 "error" => {
227 let is_semantic = !Self::is_dependency_error(&code, &msg_text);
229 errors.push(CompilerError {
230 code,
231 message: msg_text,
232 span,
233 is_semantic,
234 });
235 }
236 "warning" => {
237 warnings.push(CompilerWarning {
238 code,
239 message: msg_text,
240 });
241 }
242 _ => {}
243 }
244 }
245
246 fn is_dependency_error(code: &Option<String>, message: &str) -> bool {
252 match code.as_deref() {
253 Some("E0432") => true, Some("E0433") => true, Some("E0463") => true, _ => {
257 message.contains("can't find crate")
259 || message.contains("unresolved import")
260 || message.contains("could not find")
261 }
262 }
263 }
264}
265
266pub fn compile_with_cargo(
283 name: &str,
284 rust_code: &str,
285 cargo_toml: Option<&str>,
286) -> Result<CheckResult> {
287 let default_toml = format!(
290 r#"[package]
291name = "{}"
292version = "0.1.0"
293edition = "2021"
294
295[dependencies]
296# Serialization (json, pickle modules)
297serde = {{ version = "1.0", features = ["derive"] }}
298serde_json = "1.0"
299
300# Regex (re module)
301regex = "1.10"
302
303# Random (random module)
304rand = "0.8"
305
306# Collections (itertools module)
307itertools = "0.12"
308
309# Date/time (datetime module)
310chrono = "0.4"
311
312# Async runtime (asyncio module)
313tokio = {{ version = "1.0", features = ["full"] }}
314
315# Lazy static (constants)
316once_cell = "1.19"
317
318# Testing
319quickcheck = "1.0"
320
321# Hashing (hashlib module)
322sha2 = "0.10"
323md-5 = "0.10"
324hex = "0.4"
325
326# Base64 encoding
327base64 = "0.22"
328
329# URL parsing (urllib module)
330url = "2.5"
331
332# Temporary file operations
333tempfile = "3.10"
334
335# Argument parsing (argparse module)
336clap = {{ version = "4.5", features = ["derive"] }}
337"#,
338 name.replace('-', "_")
339 );
340
341 let toml = cargo_toml.unwrap_or(&default_toml);
342 let workspace = EphemeralWorkspace::new(name, rust_code, toml)?;
343 workspace.check()
344}
345
346pub fn quick_check(name: &str, rust_code: &str, cargo_toml: Option<&str>) -> Result<(), String> {
351 match compile_with_cargo(name, rust_code, cargo_toml) {
352 Ok(result) if result.success => Ok(()),
353 Ok(result) => {
354 let error_msg = result
355 .errors
356 .iter()
357 .filter(|e| e.is_semantic) .map(|e| format!("{}: {}", e.code.as_deref().unwrap_or("E????"), e.message))
359 .collect::<Vec<_>>()
360 .join("\n");
361 if error_msg.is_empty() {
362 Err(result.stderr)
364 } else {
365 Err(error_msg)
366 }
367 }
368 Err(e) => Err(e.to_string()),
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
379 fn test_check_result_success_fields() {
380 let result = CheckResult {
381 success: true,
382 errors: vec![],
383 warnings: vec![],
384 stderr: String::new(),
385 };
386 assert!(result.success);
387 assert!(result.errors.is_empty());
388 assert!(result.warnings.is_empty());
389 assert!(result.stderr.is_empty());
390 }
391
392 #[test]
393 fn test_check_result_with_errors() {
394 let result = CheckResult {
395 success: false,
396 errors: vec![CompilerError {
397 code: Some("E0308".to_string()),
398 message: "mismatched types".to_string(),
399 span: None,
400 is_semantic: true,
401 }],
402 warnings: vec![],
403 stderr: "error: mismatched types".to_string(),
404 };
405 assert!(!result.success);
406 assert_eq!(result.errors.len(), 1);
407 assert_eq!(result.errors[0].code.as_deref(), Some("E0308"));
408 }
409
410 #[test]
411 fn test_check_result_with_warnings() {
412 let result = CheckResult {
413 success: true,
414 errors: vec![],
415 warnings: vec![CompilerWarning {
416 code: Some("unused_variables".to_string()),
417 message: "unused variable `x`".to_string(),
418 }],
419 stderr: String::new(),
420 };
421 assert!(result.success);
422 assert_eq!(result.warnings.len(), 1);
423 assert!(result.warnings[0].message.contains("unused"));
424 }
425
426 #[test]
427 fn test_check_result_clone() {
428 let result = CheckResult {
429 success: true,
430 errors: vec![],
431 warnings: vec![],
432 stderr: "test".to_string(),
433 };
434 let cloned = result.clone();
435 assert_eq!(cloned.success, result.success);
436 assert_eq!(cloned.stderr, result.stderr);
437 }
438
439 #[test]
440 fn test_check_result_debug() {
441 let result = CheckResult {
442 success: true,
443 errors: vec![],
444 warnings: vec![],
445 stderr: String::new(),
446 };
447 let debug = format!("{:?}", result);
448 assert!(debug.contains("CheckResult"));
449 assert!(debug.contains("success"));
450 }
451
452 #[test]
455 fn test_compiler_error_all_fields() {
456 let error = CompilerError {
457 code: Some("E0308".to_string()),
458 message: "mismatched types".to_string(),
459 span: Some(ErrorSpan {
460 file: "lib.rs".to_string(),
461 line_start: 10,
462 line_end: 10,
463 column_start: 5,
464 column_end: 15,
465 }),
466 is_semantic: true,
467 };
468 assert_eq!(error.code.as_deref(), Some("E0308"));
469 assert!(error.message.contains("mismatched"));
470 assert!(error.span.is_some());
471 assert!(error.is_semantic);
472 }
473
474 #[test]
475 fn test_compiler_error_no_code() {
476 let error = CompilerError {
477 code: None,
478 message: "some error".to_string(),
479 span: None,
480 is_semantic: false,
481 };
482 assert!(error.code.is_none());
483 assert!(!error.is_semantic);
484 }
485
486 #[test]
487 fn test_compiler_error_clone() {
488 let error = CompilerError {
489 code: Some("E0001".to_string()),
490 message: "test".to_string(),
491 span: None,
492 is_semantic: true,
493 };
494 let cloned = error.clone();
495 assert_eq!(cloned.code, error.code);
496 assert_eq!(cloned.message, error.message);
497 }
498
499 #[test]
500 fn test_compiler_error_debug() {
501 let error = CompilerError {
502 code: Some("E0001".to_string()),
503 message: "test".to_string(),
504 span: None,
505 is_semantic: true,
506 };
507 let debug = format!("{:?}", error);
508 assert!(debug.contains("CompilerError"));
509 assert!(debug.contains("E0001"));
510 }
511
512 #[test]
515 fn test_compiler_warning_fields() {
516 let warning = CompilerWarning {
517 code: Some("unused_variables".to_string()),
518 message: "unused variable: `x`".to_string(),
519 };
520 assert_eq!(warning.code.as_deref(), Some("unused_variables"));
521 assert!(warning.message.contains("unused"));
522 }
523
524 #[test]
525 fn test_compiler_warning_no_code() {
526 let warning = CompilerWarning {
527 code: None,
528 message: "warning without code".to_string(),
529 };
530 assert!(warning.code.is_none());
531 }
532
533 #[test]
534 fn test_compiler_warning_clone() {
535 let warning = CompilerWarning {
536 code: Some("dead_code".to_string()),
537 message: "unused".to_string(),
538 };
539 let cloned = warning.clone();
540 assert_eq!(cloned.code, warning.code);
541 assert_eq!(cloned.message, warning.message);
542 }
543
544 #[test]
545 fn test_compiler_warning_debug() {
546 let warning = CompilerWarning {
547 code: Some("test".to_string()),
548 message: "msg".to_string(),
549 };
550 let debug = format!("{:?}", warning);
551 assert!(debug.contains("CompilerWarning"));
552 }
553
554 #[test]
557 fn test_error_span_fields() {
558 let span = ErrorSpan {
559 file: "src/main.rs".to_string(),
560 line_start: 10,
561 line_end: 15,
562 column_start: 1,
563 column_end: 20,
564 };
565 assert_eq!(span.file, "src/main.rs");
566 assert_eq!(span.line_start, 10);
567 assert_eq!(span.line_end, 15);
568 assert_eq!(span.column_start, 1);
569 assert_eq!(span.column_end, 20);
570 }
571
572 #[test]
573 fn test_error_span_single_line() {
574 let span = ErrorSpan {
575 file: "lib.rs".to_string(),
576 line_start: 5,
577 line_end: 5,
578 column_start: 10,
579 column_end: 25,
580 };
581 assert_eq!(span.line_start, span.line_end);
582 }
583
584 #[test]
585 fn test_error_span_clone() {
586 let span = ErrorSpan {
587 file: "test.rs".to_string(),
588 line_start: 1,
589 line_end: 2,
590 column_start: 3,
591 column_end: 4,
592 };
593 let cloned = span.clone();
594 assert_eq!(cloned.file, span.file);
595 assert_eq!(cloned.line_start, span.line_start);
596 }
597
598 #[test]
599 fn test_error_span_debug() {
600 let span = ErrorSpan {
601 file: "test.rs".to_string(),
602 line_start: 1,
603 line_end: 1,
604 column_start: 1,
605 column_end: 10,
606 };
607 let debug = format!("{:?}", span);
608 assert!(debug.contains("ErrorSpan"));
609 assert!(debug.contains("test.rs"));
610 }
611
612 #[test]
615 fn test_ephemeral_workspace_creates_files() {
616 let rust_code = r#"
617 pub fn hello() -> &'static str {
618 "Hello, world!"
619 }
620 "#;
621 let cargo_toml = r#"
622 [package]
623 name = "test_module"
624 version = "0.1.0"
625 edition = "2021"
626 "#;
627
628 let workspace = EphemeralWorkspace::new("test_module", rust_code, cargo_toml).unwrap();
629
630 assert!(workspace.path().join("Cargo.toml").exists());
632 assert!(workspace.path().join("src/lib.rs").exists());
633
634 let lib_content = std::fs::read_to_string(workspace.lib_path()).unwrap();
636 assert!(lib_content.contains("hello"));
637 }
638
639 #[test]
640 fn test_ephemeral_workspace_path_accessors() {
641 let rust_code = "pub fn test() {}";
642 let cargo_toml = r#"
643[package]
644name = "test_paths"
645version = "0.1.0"
646edition = "2021"
647"#;
648 let workspace = EphemeralWorkspace::new("test_paths", rust_code, cargo_toml).unwrap();
649
650 assert!(workspace.path().exists());
652 assert!(workspace.path().is_dir());
653
654 assert!(workspace.lib_path().exists());
656 assert!(workspace.lib_path().ends_with("lib.rs"));
657 }
658
659 #[test]
660 fn test_ephemeral_workspace_with_special_name() {
661 let rust_code = "pub fn test() {}";
662 let cargo_toml = r#"
663[package]
664name = "test_special_name"
665version = "0.1.0"
666edition = "2021"
667"#;
668 let workspace =
669 EphemeralWorkspace::new("test-special-name", rust_code, cargo_toml).unwrap();
670 assert!(workspace.path().exists());
671 }
672
673 #[test]
674 fn test_valid_code_compiles() {
675 let rust_code = r#"
676 pub fn add(a: i32, b: i32) -> i32 {
677 a + b
678 }
679 "#;
680 let cargo_toml = r#"
681[package]
682name = "test_valid"
683version = "0.1.0"
684edition = "2021"
685"#;
686
687 let workspace = EphemeralWorkspace::new("test_valid", rust_code, cargo_toml).unwrap();
688 let result = workspace.check().unwrap();
689
690 assert!(
691 result.success,
692 "Valid code should compile: {:?}",
693 result.errors
694 );
695 assert!(result.errors.is_empty(), "Should have no errors");
696 }
697
698 #[test]
699 fn test_invalid_code_fails() {
700 let rust_code = r#"
701 pub fn broken() -> i32 {
702 "not an integer" // Type mismatch
703 }
704 "#;
705 let cargo_toml = r#"
706[package]
707name = "test_invalid"
708version = "0.1.0"
709edition = "2021"
710"#;
711
712 let workspace = EphemeralWorkspace::new("test_invalid", rust_code, cargo_toml).unwrap();
713 let result = workspace.check().unwrap();
714
715 assert!(!result.success, "Invalid code should fail");
716 assert!(!result.errors.is_empty(), "Should have errors");
717 assert!(
719 result.errors.iter().any(|e| e.is_semantic),
720 "Should be semantic error"
721 );
722 }
723
724 #[test]
725 fn test_with_serde_dependency() {
726 let rust_code = r#"
727 use serde::{Serialize, Deserialize};
728
729 #[derive(Serialize, Deserialize)]
730 pub struct Point {
731 pub x: f64,
732 pub y: f64,
733 }
734 "#;
735 let cargo_toml = r#"
736[package]
737name = "test_serde"
738version = "0.1.0"
739edition = "2021"
740
741[dependencies]
742serde = { version = "1.0", features = ["derive"] }
743"#;
744
745 let workspace = EphemeralWorkspace::new("test_serde", rust_code, cargo_toml).unwrap();
746 let result = workspace.check().unwrap();
747
748 assert!(
749 result.success,
750 "Code with serde should compile: {:?}",
751 result.errors
752 );
753 }
754
755 #[test]
758 fn test_is_dependency_error() {
759 assert!(EphemeralWorkspace::is_dependency_error(
761 &Some("E0432".to_string()),
762 "unresolved import"
763 ));
764
765 assert!(EphemeralWorkspace::is_dependency_error(
767 &Some("E0463".to_string()),
768 "can't find crate"
769 ));
770
771 assert!(!EphemeralWorkspace::is_dependency_error(
773 &Some("E0308".to_string()),
774 "mismatched types"
775 ));
776
777 assert!(!EphemeralWorkspace::is_dependency_error(
779 &Some("E0599".to_string()),
780 "method not found"
781 ));
782 }
783
784 #[test]
785 fn test_is_dependency_error_e0433() {
786 assert!(EphemeralWorkspace::is_dependency_error(
788 &Some("E0433".to_string()),
789 "failed to resolve"
790 ));
791 }
792
793 #[test]
794 fn test_is_dependency_error_message_based() {
795 assert!(EphemeralWorkspace::is_dependency_error(
797 &None,
798 "can't find crate `foo`"
799 ));
800
801 assert!(EphemeralWorkspace::is_dependency_error(
802 &None,
803 "unresolved import `bar::baz`"
804 ));
805
806 assert!(EphemeralWorkspace::is_dependency_error(
807 &None,
808 "could not find `qux` in `xyz`"
809 ));
810 }
811
812 #[test]
813 fn test_is_dependency_error_not_dependency() {
814 assert!(!EphemeralWorkspace::is_dependency_error(
816 &Some("E0277".to_string()),
817 "trait bound not satisfied"
818 ));
819
820 assert!(!EphemeralWorkspace::is_dependency_error(
821 &Some("E0382".to_string()),
822 "borrow of moved value"
823 ));
824
825 assert!(!EphemeralWorkspace::is_dependency_error(
826 &None,
827 "some other error message"
828 ));
829 }
830
831 #[test]
832 fn test_is_dependency_error_none_code() {
833 assert!(!EphemeralWorkspace::is_dependency_error(
835 &None,
836 "type mismatch"
837 ));
838 }
839
840 #[test]
846 #[ignore = "spawns cargo process - flaky under coverage"]
847 fn test_compile_with_cargo_default_toml() {
848 let rust_code = r#"
849 pub fn simple() -> i32 {
850 42
851 }
852 "#;
853 let result = compile_with_cargo("test_default", rust_code, None).unwrap();
854 assert!(
855 result.success,
856 "Simple code should compile with default toml"
857 );
858 }
859
860 #[test]
861 #[ignore = "spawns cargo process - flaky under coverage"]
862 fn test_compile_with_cargo_custom_toml() {
863 let rust_code = "pub fn test() {}";
864 let custom_toml = r#"
865[package]
866name = "custom_pkg"
867version = "0.2.0"
868edition = "2021"
869"#;
870 let result = compile_with_cargo("custom_pkg", rust_code, Some(custom_toml)).unwrap();
871 assert!(result.success);
872 }
873
874 #[test]
875 #[ignore = "spawns cargo process - flaky under coverage"]
876 fn test_compile_with_cargo_name_with_dash() {
877 let rust_code = "pub fn test() {}";
879 let result = compile_with_cargo("my-test-module", rust_code, None).unwrap();
880 assert!(result.success);
881 }
882
883 #[test]
886 #[ignore = "spawns cargo process - flaky under coverage"]
887 fn test_quick_check_success() {
888 let rust_code = "pub fn hello() {}";
889 let result = quick_check("test_quick_ok", rust_code, None);
890 assert!(result.is_ok());
891 }
892
893 #[test]
894 #[ignore = "spawns cargo process - flaky under coverage"]
895 fn test_quick_check_semantic_error() {
896 let rust_code = r#"
897 pub fn broken() -> i32 {
898 "string" // type error
899 }
900 "#;
901 let result = quick_check("test_quick_fail", rust_code, None);
902 assert!(result.is_err());
903 let err = result.unwrap_err();
904 assert!(err.contains("E0") || !err.is_empty());
906 }
907
908 #[test]
909 #[ignore = "spawns cargo process - flaky under coverage"]
910 fn test_quick_check_with_custom_toml() {
911 let rust_code = "pub fn test() {}";
912 let toml = r#"
913[package]
914name = "quick_custom"
915version = "0.1.0"
916edition = "2021"
917"#;
918 let result = quick_check("quick_custom", rust_code, Some(toml));
919 assert!(result.is_ok());
920 }
921
922 #[test]
925 #[ignore = "spawns cargo process - flaky under coverage"]
926 fn test_empty_rust_code() {
927 let rust_code = "";
928 let result = compile_with_cargo("empty_code", rust_code, None).unwrap();
929 assert!(result.success);
931 }
932
933 #[test]
934 #[ignore = "spawns cargo process - flaky under coverage"]
935 fn test_code_with_warnings() {
936 let rust_code = r#"
937 pub fn test() {
938 let unused_var = 42;
939 }
940 "#;
941 let result = compile_with_cargo("with_warnings", rust_code, None).unwrap();
942 assert!(result.success);
944 }
947
948 #[test]
949 #[ignore = "spawns cargo process - flaky under coverage"]
950 fn test_multiple_functions() {
951 let rust_code = r#"
952 pub fn add(a: i32, b: i32) -> i32 { a + b }
953 pub fn sub(a: i32, b: i32) -> i32 { a - b }
954 pub fn mul(a: i32, b: i32) -> i32 { a * b }
955 "#;
956 let result = compile_with_cargo("multi_fn", rust_code, None).unwrap();
957 assert!(result.success);
958 }
959
960 #[test]
961 #[ignore = "spawns cargo process - flaky under coverage"]
962 fn test_with_struct() {
963 let rust_code = r#"
964 pub struct Point {
965 pub x: f64,
966 pub y: f64,
967 }
968
969 impl Point {
970 pub fn new(x: f64, y: f64) -> Self {
971 Self { x, y }
972 }
973 }
974 "#;
975 let result = compile_with_cargo("with_struct", rust_code, None).unwrap();
976 assert!(result.success);
977 }
978
979 #[test]
980 #[ignore = "spawns cargo process - flaky under coverage"]
981 fn test_with_enum() {
982 let rust_code = r#"
983 pub enum Status {
984 Active,
985 Inactive,
986 Pending,
987 }
988 "#;
989 let result = compile_with_cargo("with_enum", rust_code, None).unwrap();
990 assert!(result.success);
991 }
992
993 #[test]
994 #[ignore = "spawns cargo process - flaky under coverage"]
995 fn test_with_generics() {
996 let rust_code = r#"
997 pub fn identity<T>(x: T) -> T {
998 x
999 }
1000
1001 pub struct Container<T> {
1002 pub value: T,
1003 }
1004 "#;
1005 let result = compile_with_cargo("with_generics", rust_code, None).unwrap();
1006 assert!(result.success);
1007 }
1008
1009 #[test]
1010 #[ignore = "spawns cargo process - flaky under coverage"]
1011 fn test_syntax_error() {
1012 let rust_code = "pub fn broken( { }"; let result = compile_with_cargo("syntax_err", rust_code, None).unwrap();
1014 assert!(!result.success);
1015 assert!(!result.errors.is_empty());
1016 }
1017}