Skip to content

Commit bb058ce

Browse files
committed
test: 补齐 Java21 原生差分与隐藏恢复判定
1 parent 6c3663c commit bb058ce

7 files changed

Lines changed: 244 additions & 6 deletions

File tree

‎crates/codeguard-cli/tests/java_native_differential.rs‎

Lines changed: 125 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![cfg(all(feature = "wasm-precheck", unix))]
22

33
use std::fs;
4+
use sha2::Digest;
45
use std::path::Path;
56
use std::process::Command;
67

@@ -42,7 +43,7 @@ fn corpus() -> [(&'static str, &'static str, bool); 13] {
4243
]
4344
}
4445

45-
fn candidate_is_valid(root: &Path, name: &str, source: &str) -> bool {
46+
fn candidate_report(root: &Path, name: &str, source: &str) -> serde_json::Value {
4647
let file = root.join(format!("{name}.java"));
4748
fs::write(&file, source).unwrap();
4849
let candidate = Command::new(env!("CARGO_BIN_EXE_codeguard"))
@@ -60,7 +61,21 @@ fn candidate_is_valid(root: &Path, name: &str, source: &str) -> bool {
6061
serde_json::Value::Null,
6162
"{name}: {report}"
6263
);
63-
report["recoveries"].as_array().unwrap().is_empty()
64+
report
65+
}
66+
67+
fn candidate_classification(report: &serde_json::Value) -> Option<bool> {
68+
if report["precheck"]["truncated_files"].as_u64().unwrap() != 0 {
69+
None
70+
} else {
71+
Some(report["recoveries"].as_array().unwrap().is_empty())
72+
}
73+
}
74+
75+
#[test]
76+
fn hidden_java_recovery_is_unknown_instead_of_clean() {
77+
let report = serde_json::json!({"precheck":{"truncated_files":1},"recoveries":[]});
78+
assert_eq!(candidate_classification(&report), None);
6479
}
6580

6681
#[test]
@@ -72,8 +87,8 @@ fn java_worker_preserves_native_labeled_syntax_corpus() {
7287
fs::create_dir_all(&root).unwrap();
7388
for (name, source, expected_valid) in corpus() {
7489
assert_eq!(
75-
candidate_is_valid(&root, name, source),
76-
expected_valid,
90+
candidate_classification(&candidate_report(&root, name, source)),
91+
Some(expected_valid),
7792
"{name}"
7893
);
7994
}
@@ -119,11 +134,115 @@ fn pinned_java_worker_matches_native_javac_on_syntax_corpus() {
119134
);
120135
assert_eq!(fs::read_to_string(&file).unwrap(), source);
121136
assert_eq!(
122-
candidate_is_valid(&root, name, source),
123-
native.status.success(),
137+
candidate_classification(&candidate_report(&root, name, source)),
138+
Some(native.status.success()),
124139
"{name}: javac={} worker classification differs",
125140
String::from_utf8_lossy(&native.stderr)
126141
);
127142
}
128143
fs::remove_dir_all(root).unwrap();
129144
}
145+
146+
/// 本轮新增的Java21样本与既有固定回归语料分开统计,不授予grammar资格。
147+
#[test]
148+
#[ignore = "requires explicit existing javac 21 via CODEGUARD_JAVAC_BIN"]
149+
fn java21_language_forms_match_existing_native_compiler() {
150+
let javac = std::env::var("CODEGUARD_JAVAC_BIN").unwrap();
151+
let tool_bytes = fs::read(&javac).unwrap();
152+
let tool_sha256 = format!("{:x}", sha2::Sha256::digest(&tool_bytes));
153+
let version = Command::new(&javac).arg("-version").output().unwrap();
154+
assert!(version.status.success());
155+
assert!(String::from_utf8_lossy(&version.stdout).starts_with("javac 21."));
156+
let root = std::env::temp_dir().canonicalize().unwrap().join(format!(
157+
"codeguard-java21-differential-{}",
158+
std::process::id()
159+
));
160+
fs::create_dir_all(&root).unwrap();
161+
let cases = [
162+
(
163+
"pattern_switch",
164+
"class C { int f(Object o) { return switch(o) { case String s -> s.length(); case Integer i -> i; default -> 0; }; } }",
165+
true,
166+
),
167+
(
168+
"guarded_switch",
169+
"class C { int f(Object o) { return switch(o) { case String s when s.isEmpty() -> 0; case String s -> 1; default -> 2; }; } }",
170+
true,
171+
),
172+
(
173+
"record_pattern",
174+
"record P(int x, int y) {} class C { int f(Object o) { return o instanceof P(int x, int y) ? x + y : 0; } }",
175+
true,
176+
),
177+
(
178+
"sealed_record",
179+
"sealed interface S permits P {} record P(int x) implements S {}",
180+
true,
181+
),
182+
(
183+
"text_block",
184+
"class C { String s = \"\"\"\nhello\n\"\"\"; }",
185+
true,
186+
),
187+
(
188+
"missing_guard",
189+
"class C { int f(Object o) { return switch(o) { case String s when -> 0; default -> 2; }; } }",
190+
false,
191+
),
192+
(
193+
"missing_record_parameter_type",
194+
"record P(int x) {} class C { boolean f(Object o) { return o instanceof P(x); } }",
195+
false,
196+
),
197+
(
198+
"missing_switch_arrow",
199+
"class C { int f(Object o) { return switch(o) { case String s s.length(); default -> 0; }; } }",
200+
false,
201+
),
202+
];
203+
let mut observations = Vec::new();
204+
for (name, source, expected_valid) in cases {
205+
let file = root.join(format!("{name}.java"));
206+
fs::write(&file, source).unwrap();
207+
let out = root.join(format!("out-{name}"));
208+
fs::create_dir_all(&out).unwrap();
209+
let native = Command::new(&javac)
210+
.args(["--release", "21", "-proc:none", "-Xlint:none", "-d"])
211+
.arg(&out)
212+
.arg(&file)
213+
.output()
214+
.unwrap();
215+
assert!(
216+
matches!(native.status.code(), Some(0 | 1)),
217+
"{name}: {native:?}"
218+
);
219+
assert_eq!(
220+
native.status.success(),
221+
expected_valid,
222+
"{name}: {}",
223+
String::from_utf8_lossy(&native.stderr)
224+
);
225+
let candidate = candidate_report(&root, name, source);
226+
let classification = candidate_classification(&candidate);
227+
observations.push(serde_json::json!({"case":name,"source_sha256":format!("{:x}",sha2::Sha256::digest(source.as_bytes())),"native_valid":native.status.success(),"candidate_valid":classification,"candidate":candidate}));
228+
assert_eq!(fs::read_to_string(&file).unwrap(), source);
229+
}
230+
assert_eq!(
231+
fs::read(&javac).unwrap(),
232+
tool_bytes,
233+
"native compiler changed during oracle execution"
234+
);
235+
if let Ok(path) = std::env::var("CODEGUARD_JAVA21_REPORT") {
236+
fs::write(path, serde_json::to_vec_pretty(&serde_json::json!({"schema_version":"0.1.0","scope":"java21_new_local_cases","native_tool_sha256":tool_sha256,"native_version":String::from_utf8_lossy(&version.stdout),"independent_holdout":false,"grammar_qualified":false,"delivery_decision":"not_evaluated","observations":observations})).unwrap()).unwrap();
237+
}
238+
fs::remove_dir_all(root).unwrap();
239+
let disagreements: Vec<_> = observations
240+
.iter()
241+
.filter(|r| r["candidate_valid"].is_boolean() && r["candidate_valid"] != r["native_valid"])
242+
.map(|r| r["case"].clone())
243+
.collect();
244+
assert!(
245+
disagreements.is_empty(),
246+
"Java21 decidable differences: {disagreements:?}"
247+
);
248+
}

‎docs/Codeguard-Grammar-Evaluation.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,7 @@ Go candidate-task rechecks now accept `--go-tool /absolute/sdk/bin/go` and invok
157157
### Native-first Go lint with missing-tool candidates
158158

159159
Source-built `codeguard lint go . --format json` prefers explicit `--go-tool`, then executable Go from absolute caller PATH entries. Selected tool/version/execution failures remain native failures. When no native tool exists, the built-in WASM produces bounded whole-file recovery and structure candidates. Candidates or incomplete prechecks require a project-appropriate native tool; zero candidates in the completely observed bounded scope only recommend preparation. Native obligations remain incomplete and exit3 is retained. Builds without WASM report that capability gap. Repeated lint/check reuse the confirmation task; adding a package declaration does not close it. Public npm0.1.4 is unchanged. See [limited acceptance](../tests/acceptance/go-lint-fallback.md).
160+
161+
## 2026-10-06 Java21 native comparison
162+
163+
Java differential acceptance now checks truncated_files before treating empty recoveries as clean; hidden recovery remains unknown. The original13 cases retain their javac21/release17 comparison. A separate release21 group covers pattern switch, guards, record patterns, sealed records, text blocks and three syntax errors: actual3TP/5TN/0FP/0FN/0unknown. These are author-created local cases (independent_holdout=false), not independent qualification or additions to the fixed358-case metrics. Native-tool and source digests bind the evidence. See [acceptance](../tests/acceptance/java21-native-differential.md).

‎docs/Codeguard-Grammar-Evaluation.zh_CN.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,7 @@ Go候选任务复检现接受 `--go-tool /absolute/sdk/bin/go`,对冻结整文
169169
### Go统一lint的原生优先与缺工具初检
170170

171171
源码版 `codeguard lint go . --format json` 优先显式 `--go-tool`,否则查找调用方绝对PATH中的Go。工具已选择但版本/执行失败时保留原生故障;真正缺工具时,内置WASM做有界整文件初检,保留恢复和独立结构候选。候选或初检未完成要求准备项目适用原生工具;完整有界范围的零候选只推荐准备,原生义务仍未完成,退出码继续3。默认不含WASM的构建明确报告能力缺失。重复lint与check复用确认任务,补声明不自动关闭。公开npm0.1.4未更新。见[局部验收](../tests/acceptance/go-lint-fallback.md)。
172+
173+
## 2026-10-06 Java21原生增量对照
174+
175+
Java差分验收先核对truncated_files,隐藏恢复保留unknown,避免空recoveries误计为干净。原13例仍用javac21按release17对照;新增8例单独按release21执行,含模式switch/guard/record pattern/sealed/text block及三个语法错误,实际3TP/5TN/0FP/0FN/0unknown。新增组是本轮作者样本,independent_holdout=false,不提升grammar资格或混入固定358例。原生工具和源码摘要随证据绑定。见 [验收](../tests/acceptance/java21-native-differential.md)。

‎openspec/changes/introduce-rust-codeguard-cli/tasks.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,3 +1887,10 @@ next/task show在已核验当前原生零诊断、原grammar引用及同一源
18871887
- [x] 修复check java的P3C准备简报与聚合0.38 schema不兼容,并验证完整实际输出及拒绝反例(新增0.58,历史协议保留;见p3c-preparation-aggregate-schema验收)。
18881888

18891889
见 [验收](../../../tests/acceptance/maven-javadoc-task-recheck.md)。110通过/0失败/22条件忽略;真实Maven新工作台、复杂项目、可信关闭/复发与实际宿主未完成,6.x/9.x父任务仍开放。
1890+
1891+
## 2026-10-06 Java差分验收分类与Java21版本组
1892+
1893+
- [x] Java原生差分将截断/隐藏恢复保留unknown,空recoveries不独立证明干净;原13例原生对照继续通过。
1894+
- [x] 用既有javac21新增Java21语法8例原生对照,绑定工具和源码摘要,单独统计3TP/5TN;作者语料不当作独立盲测,不提升32grammar资格。
1895+
1896+
见 tests/acceptance/java21-native-differential.md。Java目标4通过/0失败/0忽略;独立holdout、既有六项争议、全语言及真实宿主父任务继续开放。

‎openspec/changes/introduce-rust-codeguard-cli/verification.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,3 +2512,7 @@ WASM CLI全目标Clippy -D warnings、定向rustfmt、分层、OpenSpec strict
25122512
## 2026-10-06 P3C配置准备简报聚合协议修复
25132513

25142514
完整实际0.38聚合schema拒绝P3C配置准备简报,目标0.58版本测试先失败。新增0.58及限定blocker/检查器/原因/review-project-policy/固定重扫参数的封闭schema,修复Java选择原因码遗漏,保留历史协议。四目标57通过/0失败/10条件忽略;完整实际聚合与两处简报通过,六篡改与旧消费者拒绝符合预期。CLI严格Clippy、分层/OpenSpec strict通过。局部验收 tests/acceptance/p3c-preparation-aggregate-schema.md;不代替全部P3C分支、原生执行、宿主或完整目标。
2515+
2516+
## 2026-10-06 Java差分隐藏恢复与Java21增量对照
2517+
2518+
Java验收分类器先检查truncated_files,隐藏恢复不计作干净。现有JDK21.0.12.1对原13例release17和新增8例release21实际差分均一致;新增组3TP/5TN/0FP/0FN/0unknown,作者样本independent_holdout=false,与358固定回归不混计。整个Java目标4通过/0失败/0忽略;目标特性Clippy、diff/OpenSpec验证见对应验收与证据。未运行Erlang草稿或完整WASM/358回放,未安装/下载工具,资格和整体目标仍未完成。
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
{
2+
"delivery_decision": "not_evaluated",
3+
"grammar_qualified": false,
4+
"independent_holdout": false,
5+
"native_tool_sha256": "f9b046eb45d522f6e41d0b7eef03d4a1d0f898e0b54f1eac0a1b254be81d0e36",
6+
"native_version": "javac 21.0.12.1\n",
7+
"schema_version": "0.1.0",
8+
"scope": "java21_new_local_cases",
9+
"observations": [
10+
{
11+
"case": "pattern_switch",
12+
"source_sha256": "7cff38df8d68315bc79af1147df3a612703cd8379a065c9c5f3f1e1c848fbad0",
13+
"native_valid": true,
14+
"candidate_valid": true,
15+
"candidate_report_sha256": "da60a866f6a3eca50c79e58bd32c04bef20e44dbf1e653bbef11c004dace0f3b"
16+
},
17+
{
18+
"case": "guarded_switch",
19+
"source_sha256": "a9aa35d2e450397b94864ca9f84fa9cb08b63d05d396fe9bdc9f75e0cc91cf03",
20+
"native_valid": true,
21+
"candidate_valid": true,
22+
"candidate_report_sha256": "5783d9c3539830f2f8a025e3ddef31ffb403dbdc04d26e4caf2bb1694667ae90"
23+
},
24+
{
25+
"case": "record_pattern",
26+
"source_sha256": "daa326a9f452f3ffa0f0fbfa0effefde9d41b2af0a736c4a11e59c5509a7f3f3",
27+
"native_valid": true,
28+
"candidate_valid": true,
29+
"candidate_report_sha256": "32678be9e5e901dd2769903b1e70707916e6f085e414869c6b78e58fbd5165d8"
30+
},
31+
{
32+
"case": "sealed_record",
33+
"source_sha256": "914ba8be2750d45b3c1eaea1720b23d7fdbdb0f1252d10ef3e50838c758de27b",
34+
"native_valid": true,
35+
"candidate_valid": true,
36+
"candidate_report_sha256": "a2df8d26bfa8d1239ed1c5954893a5f9ec04a23b10a84fb41558f62cc8d1c615"
37+
},
38+
{
39+
"case": "text_block",
40+
"source_sha256": "1d48239dbc78baafcc7554ff7c16758feed86ba75fae4f0e05c1723c92604145",
41+
"native_valid": true,
42+
"candidate_valid": true,
43+
"candidate_report_sha256": "db81166cbdc1d41b3fef852bd3e174c6997bb0105d4286744acf3815dd039f69"
44+
},
45+
{
46+
"case": "missing_guard",
47+
"source_sha256": "61eb96df16c9d6b42aec9e07ce656a127b7c2658ad451aae3307a21709a170c1",
48+
"native_valid": false,
49+
"candidate_valid": false,
50+
"candidate_report_sha256": "4b8c219a5e578d042fa48dd7733f5c87c3c69b95b83d2f336020afde69fd34af"
51+
},
52+
{
53+
"case": "missing_record_parameter_type",
54+
"source_sha256": "97ebf921f8c089fe01a329e275af683fa78eac2903dbc22eb1a312cf49574de7",
55+
"native_valid": false,
56+
"candidate_valid": false,
57+
"candidate_report_sha256": "3de80c9512c7c694be24147466a1eb51ad3698024e20d844c372733ca9d44045"
58+
},
59+
{
60+
"case": "missing_switch_arrow",
61+
"source_sha256": "6b92578733f6c9150f13fd2dbabc641bbb59bf626c8ff54d0858c07b2cf9427a",
62+
"native_valid": false,
63+
"candidate_valid": false,
64+
"candidate_report_sha256": "c778312e404252f9ad08bfc09c0630b3ff28369a7908081ebc3fc111bb94aab1"
65+
}
66+
],
67+
"source_sha256": "02f223ed7f786163ffd7034c0e88247b92cedf80b1e2ff3ac5e6f6ea1d774c11",
68+
"raw_report_sha256": "9d3a53ae5cdb591bda2a95713a854e627551e1a08492f0c4886ff9866f4c96f8",
69+
"logs_sha256": {
70+
"cg-java-native-recovery-classification.log": "be447ba7f5ebb2268587daebc5a38c9c48479874fa8298d4cd72c29ca76c3683",
71+
"cg-java21-native-new-cases.log": "eec0c7208969f34613c88bab650f8f1b3135b457fb5d8070849733b7b8fc1eb4",
72+
"cg-java-native-current-final.log": "3c38d39fec3e34d0ea42ce631788167dfc470289a90a92de210b2bdb22b75c9c",
73+
"cg-java-native-clippy.log": "7816be6d8b455bec1730c6a157aa750cb2310e6195ee15d56f30cd41355f6147"
74+
},
75+
"tests": {
76+
"passed": 4,
77+
"failed": 0,
78+
"ignored": 0
79+
},
80+
"java21_counts": {
81+
"tp": 3,
82+
"tn": 5,
83+
"fp": 0,
84+
"fn": 0,
85+
"unknown": 0
86+
}
87+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Java 原生差分截断判定与 Java21 增量样本
2+
3+
对应 introduce-rust-codeguard-cli 的14.17/14.19及12.11局部验收。Java差分测试此前只按recoveries是否为空判定合法,可能把隐藏/截断恢复误计为TN。测试分类器现在先读取truncated_files,非零保留unknown,不将空恢复转为干净;反例验证unknown结果。这是验收分类修正,产品仍保持未完成,不改grammar或发行资格。
4+
5+
显式使用本机Microsoft JDK21.0.12.1的javac,原13例语料继续通过原生对照(8合法、5语法错误,--release17);新增独立登记的Java21版本组8例(--release21)包含模式switch、when guard、record pattern、sealed record、text block五个合法样本,以及缺guard表达式、缺record参数类型、缺switch箭头三个语法错误。实际javac与WASM可判定结果全部一致,新增组3TP/5TN/0FP/0FN/0unknown。两组不与固定358例或其它工具指标混合。
6+
7+
新增组由本轮作者编写,independent_holdout=false,不是独立第三方盲测。原生编译器字节在运行前后核对,报告绑定版本及摘要,每例绑定源码摘要和原grammar报告。不下载工具。新增组不含注解处理器或外部依赖,javac禁止注解处理;结果不覆盖完整Java语义或全部语言版本。
8+
9+
```bash
10+
CODEGUARD_JAVAC_BIN=/absolute/jdk21/bin/javac CODEGUARD_JAVA21_REPORT=/private/local-report.json cargo test --offline --locked -p codeguard-cli --features wasm-precheck --test java_native_differential -- --include-ignored --test-threads=1
11+
```
12+
13+
整个Java差分目标4项通过(基础worker、原13例原生、隐藏恢复分类、新8例原生)。没有运行用户Erlang草稿或全WASM套件,没有重放358例或重建grammar。所有32 grammar资格仍未完成,既有六项争议和独立holdout/真实宿主/多平台继续开放。

0 commit comments

Comments
 (0)