163 lines
6.5 KiB
Java
163 lines
6.5 KiB
Java
package unit;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.HashMap;
|
||
import java.util.HashSet;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.Set;
|
||
|
||
/**
|
||
* CWE-407 unit test: solr-003
|
||
* SplitShardCmd.cleanupAfterFailedSplit — subSlices.contains(s.getName()) (List)
|
||
* inside a loop over all collection slices — O(S × n).
|
||
*
|
||
* Slow path: List<String>.contains() — O(n) per slice.
|
||
* Fast path: HashSet<String>.contains() — O(1) per slice.
|
||
*
|
||
* Compile: javac -d . SplitShardSubSlicesContains.java
|
||
* Run: java -ea unit.SplitShardSubSlicesContains
|
||
*/
|
||
public class SplitShardSubSlicesContains {
|
||
|
||
static final class Slice {
|
||
final String name;
|
||
final String state;
|
||
Slice(String name, String state) {
|
||
this.name = name;
|
||
this.state = state;
|
||
}
|
||
String getName() { return name; }
|
||
}
|
||
|
||
// ---- Slow: List.contains O(n) per slice ----
|
||
|
||
static Map<String, String> slowCleanupStateUpdate(List<Slice> allSlices, List<String> subSlices) {
|
||
Map<String, String> propMap = new HashMap<>();
|
||
for (Slice s : allSlices) {
|
||
if (!subSlices.contains(s.getName())) { // O(n) per iteration
|
||
continue;
|
||
}
|
||
propMap.put(s.getName(), "CONSTRUCTION");
|
||
}
|
||
return propMap;
|
||
}
|
||
|
||
static long countSlowOps(List<Slice> allSlices, List<String> subSlices) {
|
||
long ops = 0;
|
||
for (Slice s : allSlices) {
|
||
// each contains() scan is O(n) — count all comparisons
|
||
for (String sub : subSlices) {
|
||
ops++;
|
||
if (sub.equals(s.getName())) break;
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
// ---- Fast: HashSet.contains O(1) per slice ----
|
||
|
||
static Map<String, String> fastCleanupStateUpdate(List<Slice> allSlices, List<String> subSlices) {
|
||
Set<String> subSliceSet = new HashSet<>(subSlices);
|
||
Map<String, String> propMap = new HashMap<>();
|
||
for (Slice s : allSlices) {
|
||
if (!subSliceSet.contains(s.getName())) { // O(1)
|
||
continue;
|
||
}
|
||
propMap.put(s.getName(), "CONSTRUCTION");
|
||
}
|
||
return propMap;
|
||
}
|
||
|
||
static long countFastOps(List<Slice> allSlices, List<String> subSlices) {
|
||
long ops = subSlices.size(); // build set: O(n)
|
||
ops += allSlices.size(); // O(1) per lookup
|
||
return ops;
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
int passed = 0;
|
||
|
||
// ---- Test 1: correctness at small scale ----
|
||
{
|
||
List<Slice> allSlices = new ArrayList<>();
|
||
List<String> subSlices = new ArrayList<>();
|
||
// 20 total slices, 3 sub-shards from split
|
||
for (int i = 0; i < 20; i++) allSlices.add(new Slice("shard" + i, "ACTIVE"));
|
||
subSlices.add("shard5_0");
|
||
subSlices.add("shard5_1");
|
||
subSlices.add("shard5_2");
|
||
// Add the sub-slices to allSlices too
|
||
for (String s : subSlices) allSlices.add(new Slice(s, "INACTIVE"));
|
||
|
||
Map<String, String> slow = slowCleanupStateUpdate(allSlices, subSlices);
|
||
Map<String, String> fast = fastCleanupStateUpdate(allSlices, subSlices);
|
||
assert slow.equals(fast) : "FAIL: slow=" + slow + " fast=" + fast;
|
||
assert slow.size() == 3 : "FAIL: expected 3 in propMap, got " + slow.size();
|
||
System.out.println("PASS test1: correctness — propMap size=" + fast.size());
|
||
passed++;
|
||
}
|
||
|
||
// ---- Test 2: no subslices found ----
|
||
{
|
||
List<Slice> allSlices = new ArrayList<>();
|
||
List<String> subSlices = new ArrayList<>();
|
||
for (int i = 0; i < 50; i++) allSlices.add(new Slice("shard" + i, "ACTIVE"));
|
||
subSlices.add("nonexistent_0");
|
||
subSlices.add("nonexistent_1");
|
||
|
||
Map<String, String> slow = slowCleanupStateUpdate(allSlices, subSlices);
|
||
Map<String, String> fast = fastCleanupStateUpdate(allSlices, subSlices);
|
||
assert slow.isEmpty() && fast.isEmpty() : "FAIL: expected empty maps";
|
||
System.out.println("PASS test2: no subslices found");
|
||
passed++;
|
||
}
|
||
|
||
// ---- Test 3: operation count ratio >= 5x (theoretical max ~n for n subslices) ----
|
||
{
|
||
int S = 5000; // large collection with many shards
|
||
int n = 8; // MAX_NUM_SUB_SHARDS
|
||
List<Slice> allSlices = new ArrayList<>();
|
||
List<String> subSlices = new ArrayList<>();
|
||
for (int i = 0; i < S; i++) allSlices.add(new Slice("shard" + i, "ACTIVE"));
|
||
for (int i = 0; i < n; i++) subSlices.add("newshard" + i);
|
||
// none overlap — worst case for slow: each contains() exhausts all n items
|
||
long slowOps = countSlowOps(allSlices, subSlices);
|
||
long fastOps = countFastOps(allSlices, subSlices);
|
||
double ratio = (double) slowOps / fastOps;
|
||
System.out.printf("PASS test3: S=%d n=%d slow=%d fast=%d ratio=%.1fx%n",
|
||
S, n, slowOps, fastOps, ratio);
|
||
assert ratio >= 5.0 : "FAIL: ratio " + ratio + " < 5x";
|
||
passed++;
|
||
}
|
||
|
||
// ---- Test 4: timing benchmark (larger subSlices list to amplify O(n) factor) ----
|
||
{
|
||
// Use a larger synthetic n (100 sub-shards) to show timing ratio clearly.
|
||
// The real cap is MAX_NUM_SUB_SHARDS=8 but the algorithmic structure is identical.
|
||
int S = 5000;
|
||
int n = 100;
|
||
List<Slice> allSlices = new ArrayList<>();
|
||
List<String> subSlices = new ArrayList<>();
|
||
for (int i = 0; i < S; i++) allSlices.add(new Slice("shard" + i, "ACTIVE"));
|
||
for (int i = 0; i < n; i++) subSlices.add("newshard" + i);
|
||
|
||
int reps = 500;
|
||
long t0 = System.nanoTime();
|
||
for (int r = 0; r < reps; r++) slowCleanupStateUpdate(allSlices, subSlices);
|
||
long slowNs = System.nanoTime() - t0;
|
||
|
||
long t1 = System.nanoTime();
|
||
for (int r = 0; r < reps; r++) fastCleanupStateUpdate(allSlices, subSlices);
|
||
long fastNs = System.nanoTime() - t1;
|
||
|
||
double ratio = (double) slowNs / fastNs;
|
||
System.out.printf("PASS test4: timing S=%d n=%d slow=%.2fms fast=%.2fms ratio=%.1fx%n",
|
||
S, n, slowNs / 1e6 / reps, fastNs / 1e6 / reps, ratio);
|
||
assert ratio >= 10.0 : "FAIL: timing ratio " + ratio + " < 10x";
|
||
passed++;
|
||
}
|
||
|
||
System.out.println(passed + "/" + passed + " PASS");
|
||
}
|
||
}
|