-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAnalyzerTest.java
More file actions
206 lines (167 loc) · 7.96 KB
/
Copy pathAnalyzerTest.java
File metadata and controls
206 lines (167 loc) · 7.96 KB
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
package io.github.randomcodespace.iq.analyzer;
import io.github.randomcodespace.iq.analyzer.linker.Linker;
import io.github.randomcodespace.iq.config.CodeIqConfig;
import io.github.randomcodespace.iq.detector.Detector;
import io.github.randomcodespace.iq.detector.DetectorContext;
import io.github.randomcodespace.iq.detector.DetectorRegistry;
import io.github.randomcodespace.iq.detector.DetectorResult;
import io.github.randomcodespace.iq.model.CodeNode;
import io.github.randomcodespace.iq.model.NodeKind;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
class AnalyzerTest {
@TempDir
Path tempDir;
private Analyzer analyzer;
private List<String> progressMessages;
@BeforeEach
void setUp() {
progressMessages = new ArrayList<>();
// A simple test detector that creates one CLASS node per Java file
Detector testDetector = new Detector() {
@Override
public String getName() {
return "test-detector";
}
@Override
public Set<String> getSupportedLanguages() {
return Set.of("java");
}
@Override
public DetectorResult detect(DetectorContext ctx) {
var node = new CodeNode(
"class:" + ctx.filePath(),
NodeKind.CLASS,
ctx.filePath()
);
node.setFilePath(ctx.filePath());
node.setModule(ctx.moduleName());
return DetectorResult.of(List.of(node), List.of());
}
};
var registry = new DetectorRegistry(List.of(testDetector));
var parser = new StructuredParser();
var fileDiscovery = new FileDiscovery(new CodeIqConfig());
var layerClassifier = new LayerClassifier();
List<Linker> linkers = List.of();
analyzer = new Analyzer(registry, parser, fileDiscovery, layerClassifier, linkers, new CodeIqConfig());
}
@Test
void analyzesJavaFiles() throws IOException {
Files.writeString(tempDir.resolve("App.java"), "public class App {}");
Files.writeString(tempDir.resolve("Service.java"), "public class Service {}");
AnalysisResult result = analyzer.run(tempDir, progressMessages::add);
assertEquals(2, result.totalFiles());
assertEquals(2, result.filesAnalyzed());
// 2 CLASS nodes + 1 SERVICE node (auto-detected root service)
assertEquals(3, result.nodeCount());
// 2 CONTAINS edges (service -> each class node)
assertEquals(2, result.edgeCount());
assertTrue(result.languageBreakdown().containsKey("java"));
assertEquals(2, result.languageBreakdown().get("java"));
assertTrue(result.elapsed().toMillis() >= 0);
}
/**
* Regression: AnalysisResult breakdown maps must iterate in deterministic
* sorted order so that JSON serialization is byte-stable across runs.
*/
@Test
void breakdownMapsAreSortedDeterministically() throws IOException {
// File names chosen so SERVICE / CLASS / and the kind values would not
// appear in sorted order under the previous HashMap implementation.
Files.writeString(tempDir.resolve("Zeta.java"), "public class Zeta {}");
Files.writeString(tempDir.resolve("Alpha.java"), "public class Alpha {}");
Files.writeString(tempDir.resolve("Mu.java"), "public class Mu {}");
AnalysisResult result = analyzer.run(tempDir, progressMessages::add);
assertSortedKeys(result.languageBreakdown().keySet().stream().toList(),
"languageBreakdown");
assertSortedKeys(result.nodeBreakdown().keySet().stream().toList(),
"nodeBreakdown");
assertSortedKeys(result.edgeBreakdown().keySet().stream().toList(),
"edgeBreakdown");
assertSortedKeys(result.frameworkBreakdown().keySet().stream().toList(),
"frameworkBreakdown");
// Sanity: at least the kinds we expect should be present.
assertTrue(result.nodeBreakdown().keySet().contains("class"));
assertTrue(result.nodeBreakdown().keySet().contains("service"));
}
private static void assertSortedKeys(List<String> keys, String name) {
for (int i = 1; i < keys.size(); i++) {
String prev = keys.get(i - 1);
String cur = keys.get(i);
assertTrue(prev.compareTo(cur) < 0,
name + " not in sorted order at index " + i + ": '"
+ prev + "' >= '" + cur + "' (full: " + keys + ")");
}
}
@Test
void reportsProgress() throws IOException {
Files.writeString(tempDir.resolve("App.java"), "public class App {}");
analyzer.run(tempDir, progressMessages::add);
assertFalse(progressMessages.isEmpty());
assertTrue(progressMessages.stream().anyMatch(m -> m.contains("Discovering")));
assertTrue(progressMessages.stream().anyMatch(m -> m.contains("complete")));
}
@Test
void emptyDirectoryProducesEmptyResult() {
AnalysisResult result = analyzer.run(tempDir, null);
assertEquals(0, result.totalFiles());
assertEquals(0, result.filesAnalyzed());
// Even with no files, ServiceDetector creates a root service node
assertEquals(1, result.nodeCount());
assertEquals(0, result.edgeCount());
}
@Test
void skipsFilesWithNoMatchingDetector() throws IOException {
Files.writeString(tempDir.resolve("script.py"), "print('hello')");
AnalysisResult result = analyzer.run(tempDir, null);
assertEquals(1, result.totalFiles());
assertEquals(0, result.filesAnalyzed()); // No python detector registered
}
@Test
void nodeBreakdownIsPopulated() throws IOException {
Files.writeString(tempDir.resolve("App.java"), "public class App {}");
AnalysisResult result = analyzer.run(tempDir, null);
assertTrue(result.nodeBreakdown().containsKey("class"));
assertEquals(1, result.nodeBreakdown().get("class"));
}
@Test
void resultIsDeterministic() throws IOException {
Files.writeString(tempDir.resolve("A.java"), "public class A {}");
Files.writeString(tempDir.resolve("B.java"), "public class B {}");
Files.writeString(tempDir.resolve("C.java"), "public class C {}");
AnalysisResult result1 = analyzer.run(tempDir, null);
AnalysisResult result2 = analyzer.run(tempDir, null);
assertEquals(result1.totalFiles(), result2.totalFiles());
assertEquals(result1.filesAnalyzed(), result2.filesAnalyzed());
assertEquals(result1.nodeCount(), result2.nodeCount());
assertEquals(result1.edgeCount(), result2.edgeCount());
assertEquals(result1.languageBreakdown(), result2.languageBreakdown());
assertEquals(result1.nodeBreakdown(), result2.nodeBreakdown());
}
@Test
void nullProgressCallbackIsHandled() throws IOException {
Files.writeString(tempDir.resolve("App.java"), "public class App {}");
// Should not throw with null callback
assertDoesNotThrow(() -> analyzer.run(tempDir, null));
}
@Test
void classifiesLayersOnNodes() throws IOException {
Path srcDir = tempDir.resolve("src/controllers");
Files.createDirectories(srcDir);
Files.writeString(srcDir.resolve("UserController.java"), "public class UserController {}");
AnalysisResult result = analyzer.run(tempDir, null);
// 1 CLASS node + 1 SERVICE node (auto-detected root service)
assertEquals(2, result.nodeCount());
// The layer classifier should have run (we can't easily inspect nodes from here,
// but the pipeline completing without error confirms it ran)
}
}