|
| 1 | +use serde_json::{Value, json}; |
| 2 | +use std::{fs, process::Command, path::Path}; |
| 3 | + |
| 4 | +#[test] |
| 5 | +#[ignore = "requires explicit existing Clang via CODEGUARD_CLANG_BIN"] |
| 6 | +fn placeholder_import_reuses_file_identity_and_never_closes_on_clean_observation() { |
| 7 | + let tool = std::env::var("CODEGUARD_CLANG_BIN").unwrap(); |
| 8 | + let root = std::env::temp_dir().join(format!( |
| 9 | + "cg-placeholder-import-{}-{}", |
| 10 | + std::process::id(), |
| 11 | + std::time::SystemTime::now() |
| 12 | + .duration_since(std::time::UNIX_EPOCH) |
| 13 | + .unwrap() |
| 14 | + .as_nanos() |
| 15 | + )); |
| 16 | + fs::create_dir(&root).unwrap(); |
| 17 | + let root = root.canonicalize().unwrap(); |
| 18 | + struct Cleanup(std::path::PathBuf); |
| 19 | + impl Drop for Cleanup { |
| 20 | + fn drop(&mut self) { |
| 21 | + let _ = fs::remove_dir_all(&self.0); |
| 22 | + } |
| 23 | + } |
| 24 | + let _cleanup = Cleanup(root.clone()); |
| 25 | + let source = root.join("api.c"); |
| 26 | + let cli = |args: Vec<String>| { |
| 27 | + let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) |
| 28 | + .args(args) |
| 29 | + .arg("--format=json") |
| 30 | + .output() |
| 31 | + .unwrap(); |
| 32 | + assert!( |
| 33 | + matches!(output.status.code(), Some(0 | 3)), |
| 34 | + "{}", |
| 35 | + String::from_utf8_lossy(&output.stderr) |
| 36 | + ); |
| 37 | + serde_json::from_slice::<Value>(&output.stdout).unwrap() |
| 38 | + }; |
| 39 | + cli(vec![ |
| 40 | + "init".into(), |
| 41 | + root.display().to_string(), |
| 42 | + "--apply".into(), |
| 43 | + ]); |
| 44 | + let scan = || { |
| 45 | + cli(vec![ |
| 46 | + "comments".into(), |
| 47 | + "c".into(), |
| 48 | + source.display().to_string(), |
| 49 | + "--clang-tool".into(), |
| 50 | + tool.clone(), |
| 51 | + "--standard".into(), |
| 52 | + "c11".into(), |
| 53 | + ]) |
| 54 | + }; |
| 55 | + let import = |feedback: &Value, index: usize| { |
| 56 | + let run = format!("clangdocplaceholder-1-1-{index}"); |
| 57 | + let packet = json!({"schema_version":"0.1.0","report_type":"clang_documentation_placeholder_workbench_observation","workspace_binding":"bound","workspace_id":feedback["workbench"]["workspace_id"],"run_id":run,"path":"api.c","language":"c","standard":"c11","profile":"clang-documentation-placeholder-v1","selected_tool":tool,"source_sha256":feedback["source_sha256"],"local_scan_complete":feedback["local_scan_complete"],"native":feedback["native"],"structure":feedback["documentation_structure"],"placeholders":feedback["documentation_placeholders"]["observation"],"authority":"local_unverified","coverage_proven":false,"delivery_decision":"not_evaluated"}); |
| 58 | + fs::write( |
| 59 | + root.join(format!(".codeguard/reports/{run}.json")), |
| 60 | + serde_json::to_vec_pretty(&packet).unwrap(), |
| 61 | + ) |
| 62 | + .unwrap(); |
| 63 | + cli(vec![ |
| 64 | + "work".into(), |
| 65 | + "sync".into(), |
| 66 | + root.display().to_string(), |
| 67 | + ]); |
| 68 | + assert!( |
| 69 | + root.join(format!(".codeguard/state/consumed/{run}.json")) |
| 70 | + .is_file() |
| 71 | + ); |
| 72 | + }; |
| 73 | + let facts = || { |
| 74 | + fs::read_dir(root.join(".codeguard/findings")) |
| 75 | + .unwrap() |
| 76 | + .map(|entry| entry.unwrap().path()) |
| 77 | + .filter(|path| path.is_dir()) |
| 78 | + .collect::<Vec<_>>() |
| 79 | + }; |
| 80 | + let initial = "/// TODO.\n/// @param x TBD\n/// @return FIXME!\nint f(int x);\n"; |
| 81 | + fs::write(&source, initial).unwrap(); |
| 82 | + import(&scan(), 1); |
| 83 | + let first = facts(); |
| 84 | + assert_eq!(first.len(), 1); |
| 85 | + let fact_path = first[0].join("finding.json"); |
| 86 | + let fact: Value = serde_json::from_slice(&fs::read(&fact_path).unwrap()).unwrap(); |
| 87 | + assert_eq!(fact["checker_id"], "c.clang.documentation_placeholder"); |
| 88 | + assert_eq!( |
| 89 | + fact["native_rule_id"], |
| 90 | + "codeguard.documentation.placeholder_description" |
| 91 | + ); |
| 92 | + fs::write(&source, format!("\n{initial}")).unwrap(); |
| 93 | + import(&scan(), 2); |
| 94 | + assert_eq!(facts(), first); |
| 95 | + fs::write(&source,"/// Return the input unchanged.\n/// @param x Input value.\n/// @return The same value.\nint f(int x);\n").unwrap(); |
| 96 | + import(&scan(), 3); |
| 97 | + assert_eq!(facts(), first); |
| 98 | + let preserved: Value = serde_json::from_slice(&fs::read(&fact_path).unwrap()).unwrap(); |
| 99 | + assert_eq!(preserved["state"], "open"); |
| 100 | + assert!(Path::new(&fact_path).is_file()); |
| 101 | + let mut forged: Value = serde_json::from_slice( |
| 102 | + &fs::read(root.join(".codeguard/reports/clangdocplaceholder-1-1-3.json")).unwrap(), |
| 103 | + ) |
| 104 | + .unwrap(); |
| 105 | + forged["run_id"] = json!("clangdocplaceholder-1-1-4"); |
| 106 | + let row = &forged["structure"]["observation"]["functions"][0]; |
| 107 | + let bad_position = json!({"offset_byte":row["offset_byte"],"line":row["line"],"column_byte":row["column_byte"],"component":"parameter:foreign"}); |
| 108 | + forged["placeholders"]["positions"] = json!([bad_position]); |
| 109 | + fs::write( |
| 110 | + root.join(".codeguard/reports/clangdocplaceholder-1-1-4.json"), |
| 111 | + serde_json::to_vec_pretty(&forged).unwrap(), |
| 112 | + ) |
| 113 | + .unwrap(); |
| 114 | + cli(vec![ |
| 115 | + "work".into(), |
| 116 | + "sync".into(), |
| 117 | + root.display().to_string(), |
| 118 | + ]); |
| 119 | + assert!( |
| 120 | + !root |
| 121 | + .join(".codeguard/state/consumed/clangdocplaceholder-1-1-4.json") |
| 122 | + .exists() |
| 123 | + ); |
| 124 | + assert_eq!(facts(), first); |
| 125 | + let task_id = first[0].file_name().unwrap().to_str().unwrap(); |
| 126 | + let verification = cli(vec![ |
| 127 | + "task".into(), |
| 128 | + "verify".into(), |
| 129 | + task_id.into(), |
| 130 | + root.display().to_string(), |
| 131 | + ]); |
| 132 | + assert_eq!( |
| 133 | + verification["reason"], |
| 134 | + "clang_placeholder_task_workflow_not_integrated" |
| 135 | + ); |
| 136 | + assert!(verification["native_scan"].is_null()); |
| 137 | + assert_eq!(verification["event_persisted"], false); |
| 138 | +} |
0 commit comments