Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.

Commit 431beb7

Browse files
Merge pull request #154 from sauravbhattacharya001/test/dimacs-exporter-tests
test: add comprehensive tests for DimacsExporter
2 parents 63eec6b + c7f04db commit 431beb7

56 files changed

Lines changed: 3934 additions & 283 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docker.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ jobs:
3737
type=sha,prefix=sha-
3838
type=raw,value=latest,enable={{is_default_branch}}
3939
40+
- name: Set up QEMU
41+
uses: docker/setup-qemu-action@v3
42+
with:
43+
platforms: linux/amd64,linux/arm64
44+
4045
- name: Set up Docker Buildx
4146
uses: docker/setup-buildx-action@v4
4247

@@ -54,11 +59,12 @@ jobs:
5459
with:
5560
context: .
5661
push: ${{ github.event_name != 'pull_request' }}
62+
platforms: ${{ github.event_name != 'pull_request' && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
5763
tags: ${{ steps.meta.outputs.tags }}
5864
labels: ${{ steps.meta.outputs.labels }}
5965
cache-from: type=gha
6066
cache-to: type=gha,mode=max
61-
# Load image locally for scanning even on PRs
67+
# Load image locally for scanning even on PRs (load only works with single platform)
6268
load: ${{ github.event_name == 'pull_request' }}
6369

6470
- name: Determine scan image

Gvisual/src/app/Network.java

Lines changed: 37 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package app;
22

3+
import gvisual.ExportUtils;
34
import java.io.BufferedWriter;
45
import java.io.File;
56
import java.io.FileWriter;
@@ -82,12 +83,7 @@ public static void generateFile(String path, String Month, String Date, int dThr
8283

8384
// Validate output path - prevent directory traversal attacks
8485
File outputFile = new File(path).getCanonicalFile();
85-
File workingDir = new File(".").getCanonicalFile();
86-
if (!outputFile.toPath().startsWith(workingDir.toPath())) {
87-
throw new SecurityException(
88-
"Output path must be within the working directory. "
89-
+ "Resolved path: " + outputFile.getAbsolutePath());
90-
}
86+
ExportUtils.validateOutputPath(outputFile);
9187

9288
System.out.println("connecting...");
9389

@@ -100,63 +96,26 @@ public static void generateFile(String path, String Month, String Date, int dThr
10096
// "location = 'public'" filter silently dropped long meetings at
10197
// cafés, libraries, paths, etc., under-counting friend edges.
10298
// See: https://github.com/sauravbhattacharya001/GraphVisual/issues/134
103-
String friendSql = " SELECT x.id , y.id , C , d "
104-
+ " FROM ( SELECT imei1 , imei2, count(*) as C,avg(duration) as d"
105-
+ " FROM ( SELECT imei1, imei2, duration"
106-
+ " FROM meeting"
107-
+ " WHERE month = ? AND date = ? AND location NOT IN ('class', 'unknown', '') AND duration > ?) as b"
108-
+ " GROUP BY imei1, imei2) as a, deviceID as x, deviceID as y"
109-
+ " WHERE C >= ? AND a.imei1= x.imei AND a.imei2 = y.imei";
110-
111-
// --- Study groups query ---
112-
String studygSql = " SELECT x.id , y.id , C , d "
113-
+ " FROM ( SELECT imei1, imei2, count(*) as C,avg(duration) as d"
114-
+ " FROM ( SELECT imei1, imei2, duration"
115-
+ " FROM meeting"
116-
+ " WHERE month = ? AND date = ? AND location= 'class' AND duration > ?) as b"
117-
+ " GROUP BY imei1, imei2) as a, deviceID as x, deviceID as y"
118-
+ " WHERE C <= ? AND a.imei1= x.imei AND a.imei2 = y.imei";
119-
120-
// --- Classmates query ---
121-
String cmateSql = " SELECT x.id , y.id, C , d "
122-
+ " FROM ( SELECT imei1, imei2, count(*) as C, avg(duration) as d"
123-
+ " FROM ( SELECT imei1, imei2, duration"
124-
+ " FROM meeting"
125-
+ " WHERE month = ? AND date = ? AND location= 'class' AND duration > ?) as b"
126-
+ " GROUP BY imei1, imei2) as a, deviceID as x, deviceID as y"
127-
+ " WHERE C >= ? AND a.imei1= x.imei AND a.imei2 = y.imei";
128-
129-
// --- Strangers query ---
130-
// Exclude both 'class' and 'unknown' locations so only meetings with
131-
// a resolved location (e.g. 'public', 'path') are considered.
132-
String strangerSql = " SELECT x.id , y.id , C , d "
133-
+ " FROM ( SELECT imei1, imei2, count(*) as C,avg(duration) as d"
134-
+ " FROM ( SELECT imei1, imei2, duration"
135-
+ " FROM meeting"
136-
+ " WHERE month = ? AND date = ? AND location NOT IN ('class', 'unknown', '') AND duration < ?) as b"
137-
+ " GROUP BY imei1, imei2) as a, deviceID as x, deviceID as y"
138-
+ " WHERE C < ? AND a.imei1= x.imei AND a.imei2 = y.imei";
139-
140-
// --- Familiar strangers query ---
141-
String famstrangerSql = " SELECT x.id , y.id , C , d "
142-
+ " FROM ( SELECT imei1, imei2, count(*) as C, avg(duration) as d"
143-
+ " FROM ( SELECT imei1, imei2, duration"
144-
+ " FROM meeting"
145-
+ " WHERE month = ? AND date = ? AND location NOT IN ('class', 'unknown', '') AND duration < ?) as b"
146-
+ " GROUP BY imei1, imei2) as a , deviceID as x, deviceID as y"
147-
+ " WHERE C > ? AND a.imei1= x.imei AND a.imei2 = y.imei";
14899

149100
try (Connection conn = Util.getAppConnection()) {
150101

151102
// Use StringBuilder instead of String concatenation for performance
152103
StringBuilder sb = new StringBuilder("edges");
153104

154-
// Execute each relationship query using the shared helper
155-
appendEdges(conn, sb, friendSql, "f", Month, Date, dThresF, CThresF);
156-
appendEdges(conn, sb, studygSql, "sg", Month, Date, dThresSg, CThresSg);
157-
appendEdges(conn, sb, cmateSql, "c", Month, Date, dThresC, CThresC);
158-
appendEdges(conn, sb, strangerSql, "s", Month, Date, dThresS, CThresS);
159-
appendEdges(conn, sb, famstrangerSql, "fs", Month, Date, dThresFS, CThresFS);
105+
// All five relationship queries share the same structure, differing
106+
// only in location filter, duration comparison, and count comparison.
107+
// buildMeetingSql() generates the parameterized SQL from these axes,
108+
// eliminating the duplicated query strings.
109+
appendEdges(conn, sb, buildMeetingSql("NOT IN ('class', 'unknown', '')", ">", ">="),
110+
"f", Month, Date, dThresF, CThresF);
111+
appendEdges(conn, sb, buildMeetingSql("= 'class'", ">", "<="),
112+
"sg", Month, Date, dThresSg, CThresSg);
113+
appendEdges(conn, sb, buildMeetingSql("= 'class'", ">", ">="),
114+
"c", Month, Date, dThresC, CThresC);
115+
appendEdges(conn, sb, buildMeetingSql("NOT IN ('class', 'unknown', '')", "<", "<"),
116+
"s", Month, Date, dThresS, CThresS);
117+
appendEdges(conn, sb, buildMeetingSql("NOT IN ('class', 'unknown', '')", "<", ">"),
118+
"fs", Month, Date, dThresFS, CThresFS);
160119

161120
// Write output file - use validated outputFile, not raw path
162121
if (outputFile.exists()) {
@@ -168,6 +127,27 @@ public static void generateFile(String path, String Month, String Date, int dThr
168127
}
169128
}
170129

130+
/**
131+
* Builds a parameterized meeting SQL query from the three axes that
132+
* vary between relationship types: location filter, duration comparison
133+
* operator, and count comparison operator.
134+
*
135+
* @param locationFilter SQL fragment for location (e.g. {@code "= 'class'"})
136+
* @param durationOp comparison operator for duration threshold
137+
* @param countOp comparison operator for count threshold
138+
* @return parameterized SQL with 4 placeholders: month, date, duration, count
139+
*/
140+
private static String buildMeetingSql(String locationFilter, String durationOp, String countOp) {
141+
return "SELECT x.id, y.id, C, d"
142+
+ " FROM (SELECT imei1, imei2, count(*) AS C, avg(duration) AS d"
143+
+ " FROM (SELECT imei1, imei2, duration"
144+
+ " FROM meeting"
145+
+ " WHERE month = ? AND date = ? AND location " + locationFilter
146+
+ " AND duration " + durationOp + " ?) AS b"
147+
+ " GROUP BY imei1, imei2) AS a, deviceID AS x, deviceID AS y"
148+
+ " WHERE C " + countOp + " ? AND a.imei1 = x.imei AND a.imei2 = y.imei";
149+
}
150+
171151
/**
172152
* Executes a parameterized meeting query and appends edges to the output buffer.
173153
*

Gvisual/src/app/findMeetings.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public static void main(String[] args) throws Exception {
145145

146146
rs.last();
147147
System.out.println("fetched " + rs.getRow() + " number of entries....still working...");
148-
rs.first();
148+
rs.beforeFirst();
149149
while (rs.next()) {
150150
if (rs.getRow() % 1000 == 0) {
151151
System.out.println("added " + rs.getRow() + " number of entries to map");

Gvisual/src/gvisual/AdjacencyMatrixHeatmap.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ private void sortByCommunity() {
9595
// Start with highest degree node
9696
nodeOrder.sort((a, b) -> Integer.compare(graph.degree(b), graph.degree(a)));
9797

98-
Queue<String> queue = new LinkedList<>();
98+
Queue<String> queue = new ArrayDeque<>();
9999
queue.add(nodeOrder.get(0));
100100
visited.add(nodeOrder.get(0));
101101

Gvisual/src/gvisual/ArticulationPointAnalyzer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ private int countBiconnectedComponents(String vertex) {
374374
if (assigned.contains(neighbor)) continue;
375375
// BFS from neighbor, excluding the articulation point
376376
Set<String> component = new HashSet<String>();
377-
Queue<String> queue = new LinkedList<String>();
377+
Queue<String> queue = new ArrayDeque<String>();
378378
queue.add(neighbor);
379379
component.add(neighbor);
380380
while (!queue.isEmpty()) {
@@ -408,7 +408,7 @@ private int[] estimateComponentSizes(String v1, String v2, Edge bridgeEdge) {
408408
*/
409409
private Set<String> bfsExcludingEdge(String start, Edge excluded) {
410410
Set<String> visited = new HashSet<String>();
411-
Queue<String> queue = new LinkedList<String>();
411+
Queue<String> queue = new ArrayDeque<String>();
412412
queue.add(start);
413413
visited.add(start);
414414

Gvisual/src/gvisual/BandwidthMinimizer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ private static <V, E> List<V> cuthillMcKeeOrdering(Graph<V, E> graph) {
221221

222222
// Reset visited for this component traversal
223223
Set<V> componentVisited = new HashSet<>();
224-
Queue<V> queue = new LinkedList<>();
224+
Queue<V> queue = new ArrayDeque<>();
225225
queue.add(start);
226226
componentVisited.add(start);
227227

@@ -281,7 +281,7 @@ private static <V, E> V findPseudoPeripheral(Graph<V, E> graph, V seed,
281281
/** BFS distance map from a source vertex. */
282282
private static <V, E> Map<V, Integer> bfsDistances(Graph<V, E> graph, V source) {
283283
Map<V, Integer> dist = new HashMap<>();
284-
Queue<V> queue = new LinkedList<>();
284+
Queue<V> queue = new ArrayDeque<>();
285285
dist.put(source, 0);
286286
queue.add(source);
287287

Gvisual/src/gvisual/BipartiteAnalyzer.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public BipartiteAnalyzer compute() {
8686

8787
color.put(start, LEFT);
8888
parent.put(start, null);
89-
Queue<String> queue = new LinkedList<String>();
89+
Queue<String> queue = new ArrayDeque<String>();
9090
queue.add(start);
9191

9292
while (!queue.isEmpty() && bipartite) {
@@ -317,7 +317,7 @@ public List<MatchingEdge> getMaximumMatching() {
317317
private boolean bfs(List<String> leftVerts, Map<String, List<String>> adj,
318318
Map<String, String> matchL, Map<String, String> matchR,
319319
Map<String, Integer> dist) {
320-
Queue<String> queue = new LinkedList<String>();
320+
Queue<String> queue = new ArrayDeque<String>();
321321

322322
for (String l : leftVerts) {
323323
if (matchL.get(l).equals(NIL)) {
@@ -430,7 +430,7 @@ public List<String> getMinimumVertexCover() {
430430
// Alternate: unmatched Edge to right, matched Edge back to left
431431
Set<String> visitedL = new LinkedHashSet<String>(unmatchedLeft);
432432
Set<String> visitedR = new LinkedHashSet<String>();
433-
Queue<String> queue = new LinkedList<String>(unmatchedLeft);
433+
Queue<String> queue = new ArrayDeque<String>(unmatchedLeft);
434434

435435
while (!queue.isEmpty()) {
436436
String l = queue.poll();

Gvisual/src/gvisual/ChordalGraphAnalyzer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ private static List<String> findChordlessCycle(Map<String, Set<String>> adj,
300300
String v, String u, String w) {
301301
// Simple approach: BFS from u to w avoiding v and direct u-w Edge
302302
// to find shortest path, then cycle is v → u → path → w → v
303-
Queue<String> queue = new LinkedList<>();
303+
Queue<String> queue = new ArrayDeque<>();
304304
Map<String, String> parent = new HashMap<>();
305305
Set<String> visited = new HashSet<>();
306306
visited.add(v);

Gvisual/src/gvisual/ChromaticPolynomialCalculator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ private List<Set<String>> findConnectedComponents() {
378378
for (String v : graph.getVertices()) {
379379
if (!visited.contains(v)) {
380380
Set<String> component = new HashSet<>();
381-
Queue<String> queue = new LinkedList<>();
381+
Queue<String> queue = new ArrayDeque<>();
382382
queue.add(v);
383383
visited.add(v);
384384
while (!queue.isEmpty()) {
@@ -594,7 +594,7 @@ private String detectSpecialType(int n, int m) {
594594
private boolean isConnected() {
595595
if (graph.getVertexCount() == 0) return true;
596596
Set<String> visited = new HashSet<>();
597-
Queue<String> queue = new LinkedList<>();
597+
Queue<String> queue = new ArrayDeque<>();
598598
String start = graph.getVertices().iterator().next();
599599
queue.add(start);
600600
visited.add(start);

Gvisual/src/gvisual/CircularLayout.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ private List<String> orderByCommunity(List<String> vertices) {
199199

200200
for (String v : vertices) {
201201
if (!visited.contains(v)) {
202-
Queue<String> queue = new LinkedList<>();
202+
Queue<String> queue = new ArrayDeque<>();
203203
queue.add(v);
204204
visited.add(v);
205205
while (!queue.isEmpty()) {
@@ -233,7 +233,7 @@ private List<String> orderByBfs(List<String> vertices) {
233233

234234
List<String> ordered = new ArrayList<>();
235235
Set<String> visited = new HashSet<>();
236-
Queue<String> queue = new LinkedList<>();
236+
Queue<String> queue = new ArrayDeque<>();
237237

238238
queue.add(start);
239239
visited.add(start);

0 commit comments

Comments
 (0)