1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//! Regression tests for the entropy quality-gate contract (#683).
//!
//! On the shipped artifact `quality-gate --checks entropy --min-entropy 0.0`
//! ("require zero diversity", which cannot be unmet) reported `Status: FAILED`,
//! exactly as `--min-entropy 0.99` did, and no emitted violation named any
//! threshold: `[warning] entropy: ApiCall pattern repeated 10 times (saves 302
//! lines)`.
//!
//! NOTE: `tests_core_part1.rs` also contains entropy tests, but that file is not
//! included by any module and therefore never compiles or runs — these live here
//! so they actually execute.
#[cfg(test)]
mod entropy_gate_tests {
use super::super::check_entropy;
use tempfile::TempDir;
/// A project with enough structural repetition to produce real violations.
fn repetitive_project(file_count: usize) -> TempDir {
let temp_dir = TempDir::new().expect("tempdir");
let src_dir = temp_dir.path().join("src");
std::fs::create_dir_all(&src_dir).expect("create src");
for f in 0..file_count {
let mut body =
String::from("pub fn dispatch(v: i32) -> i32 {\n if v == 0 {\n 0\n");
for i in 1..8 {
body.push_str(&format!(" }} else if v == {i} {{\n {i}\n"));
}
body.push_str(" } else {\n -1\n }\n}\n");
std::fs::write(src_dir.join(format!("m{f}.rs")), body).expect("write");
}
temp_dir
}
/// Rows produced by the diversity threshold, identified by the annotation
/// only that row carries.
fn diversity_rows(
violations: &[crate::cli::analysis_utilities::QualityViolation],
) -> Vec<&crate::cli::analysis_utilities::QualityViolation> {
violations
.iter()
.filter(|v| v.message.contains("Low pattern diversity"))
.collect()
}
/// #683: a zero diversity requirement can never be unmet, so it must not
/// raise a diversity violation.
///
/// UPDATED (round 3): this used to assert the whole result was empty, which
/// pinned a worse defect — `--min-entropy` acted as a master switch over
/// findings it has nothing to do with. On the real tree `--min-entropy 0.3`
/// against a measured 62.6% diversity suppressed six High-severity concrete
/// repetition violations and certified "Total violations: 0" for code
/// `analyze entropy` flags 14 times. The flag gates the diversity finding;
/// repetition findings answer to `max_pattern_repetition`.
#[tokio::test]
async fn test_zero_threshold_raises_no_diversity_violation() {
let project = repetitive_project(6);
let violations = check_entropy(project.path(), 0.0).await.expect("check");
assert!(
diversity_rows(&violations).is_empty(),
"min_entropy 0.0 requires zero diversity and can never be unmet, \
got: {:?}",
violations.iter().map(|v| &v.message).collect::<Vec<_>>()
);
}
/// A concrete repetition finding is not suppressed by a lenient diversity
/// threshold: the same repetition rows appear at 0.0 and at 0.99.
#[tokio::test]
async fn test_repetition_findings_survive_a_lenient_diversity_threshold() {
let project = repetitive_project(6);
let at_zero = check_entropy(project.path(), 0.0).await.expect("check");
let at_high = check_entropy(project.path(), 0.99).await.expect("check");
let reps_at_zero: Vec<_> = at_zero
.iter()
.filter(|v| !v.message.contains("Low pattern diversity"))
.map(|v| v.message.clone())
.collect();
let reps_at_high: Vec<_> = at_high
.iter()
.filter(|v| !v.message.contains("Low pattern diversity"))
.map(|v| v.message.clone())
.collect();
assert!(
!reps_at_zero.is_empty(),
"a tree with 8-arm if/else chains repeated across 6 files has \
repetition findings regardless of the diversity threshold"
);
assert_eq!(
reps_at_zero, reps_at_high,
"the diversity threshold must not change repetition findings"
);
}
/// #683: the threshold must actually gate, so a demanding threshold on the
/// same project must produce a violation that a zero threshold does not.
#[tokio::test]
async fn test_threshold_changes_the_outcome() {
let project = repetitive_project(6);
let at_zero = check_entropy(project.path(), 0.0).await.expect("check");
let at_high = check_entropy(project.path(), 0.99).await.expect("check");
assert!(diversity_rows(&at_zero).is_empty());
assert_eq!(
diversity_rows(&at_high).len(),
1,
"a 99% diversity requirement must not be silently satisfied"
);
assert!(at_high.len() > at_zero.len());
}
/// #683: the diversity row names the threshold that was applied and the value
/// it was compared against.
///
/// UPDATED (round 3): the loop used to require this annotation on *every*
/// violation, which pinned the defect of stamping "[pattern diversity 62.6% <
/// required 30%]" onto an `ApiCall pattern repeated 17 times` row that the
/// diversity threshold did not produce.
#[tokio::test]
async fn test_diversity_violation_names_the_applied_threshold() {
let project = repetitive_project(6);
let violations = check_entropy(project.path(), 0.99).await.expect("check");
let rows = diversity_rows(&violations);
assert_eq!(rows.len(), 1);
for violation in rows {
assert!(
violation.message.contains("--min-entropy 0.99"),
"violation must name the applied threshold, got: {}",
violation.message
);
assert!(
violation.message.contains("required 99.0%"),
"violation must state what was required, got: {}",
violation.message
);
assert!(
violation.message.contains("pattern diversity"),
"violation must state what was measured, got: {}",
violation.message
);
}
}
/// The flag echo must name the value the user typed. `--min-entropy 0.999`
/// rendered as "required 99.9% (--min-entropy 1.00)": one sentence naming two
/// different numbers, the second of which was never passed.
#[tokio::test]
async fn test_flag_echo_is_not_rounded_to_two_decimals() {
let project = repetitive_project(6);
let violations = check_entropy(project.path(), 0.999).await.expect("check");
let rows = diversity_rows(&violations);
assert_eq!(rows.len(), 1);
let message = &rows[0].message;
assert!(
message.contains("--min-entropy 0.999"),
"flag echo must be the value passed, got: {message}"
);
assert!(
!message.contains("--min-entropy 1"),
"flag echo must not round 0.999 up to 1, got: {message}"
);
}
/// `analyze entropy` and `quality-gate --checks entropy` must look at the
/// same files, or they report different values for the same metric on the
/// same tree (939 vs 1328 files; 62.6% vs 63.9% diversity).
#[test]
fn test_gate_and_analyze_share_one_exclusion_list() {
use crate::entropy::EntropyConfig;
let analyze = crate::cli::handlers::analysis_handlers::create_entropy_config(
crate::cli::EntropySeverity::Medium,
false,
);
let shared = EntropyConfig::analysis_excludes(false);
assert_eq!(
analyze.exclude_paths, shared,
"analyze entropy must use the shared exclusion list"
);
assert!(
shared.iter().any(|p| p == "**/*test*.rs"),
"test-named files are excluded by both entry points"
);
}
/// NONDETERMINISM: `quality-gate --checks entropy` reported '- Entropy: 8',
/// then '6', then '7' across three runs on one path. Five runs, one answer.
#[tokio::test]
async fn test_entropy_check_is_deterministic_across_five_runs() {
let project = repetitive_project(10);
let mut runs = Vec::new();
for _ in 0..5 {
let violations = check_entropy(project.path(), 0.99).await.expect("check");
runs.push(
violations
.iter()
.map(|v| (v.severity.clone(), v.file.clone(), v.message.clone()))
.collect::<Vec<_>>(),
);
}
for (i, run) in runs.iter().enumerate().skip(1) {
assert_eq!(
runs[0].len(),
run.len(),
"run {i} reported {} violations, run 0 reported {}",
run.len(),
runs[0].len()
);
assert_eq!(
&runs[0], run,
"run {i} disagreed with run 0 on identical input"
);
}
}
}