171 lines
7.1 KiB
Java
171 lines
7.1 KiB
Java
package unit;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* pulsar-0001: GetTopicsResult.getTopics() — ArrayList.contains() in dedup for loop.
|
|
*
|
|
* grouped is ArrayList<String>; .contains(partitionedTopic) is O(N) per call.
|
|
* For N topics, this is O(N^2). Fix: use LinkedHashSet for O(N) dedup.
|
|
*/
|
|
public class PulsarGetTopicsResultTest {
|
|
|
|
// Slow path: exact replica of GetTopicsResult.getTopics() logic
|
|
// Returns (dedupedList, opCount)
|
|
static Object[] slowGetTopics(List<String> nonPartitionedOrPartitionTopics) {
|
|
List<String> grouped = new ArrayList<>();
|
|
long ops = 0;
|
|
for (String topic : nonPartitionedOrPartitionTopics) {
|
|
// Simulate getPartitionedTopicName: strip "-partition-N" suffix
|
|
String partitionedTopic = stripPartitionSuffix(topic);
|
|
// grouped.contains: O(N) scan of ArrayList
|
|
boolean found = false;
|
|
for (String g : grouped) {
|
|
ops++;
|
|
if (g.equals(partitionedTopic)) { found = true; break; }
|
|
}
|
|
if (!found) {
|
|
grouped.add(partitionedTopic);
|
|
}
|
|
}
|
|
return new Object[]{grouped, ops};
|
|
}
|
|
|
|
// Fast path: LinkedHashSet for O(1) dedup, preserving insertion order
|
|
static Object[] fastGetTopics(List<String> nonPartitionedOrPartitionTopics) {
|
|
LinkedHashSet<String> grouped = new LinkedHashSet<>();
|
|
long ops = 0;
|
|
for (String topic : nonPartitionedOrPartitionTopics) {
|
|
String partitionedTopic = stripPartitionSuffix(topic);
|
|
ops++; // O(1) HashSet.add (dedup is implicit)
|
|
grouped.add(partitionedTopic);
|
|
}
|
|
return new Object[]{new ArrayList<>(grouped), ops};
|
|
}
|
|
|
|
static String stripPartitionSuffix(String topic) {
|
|
int idx = topic.lastIndexOf("-partition-");
|
|
if (idx >= 0) return topic.substring(0, idx);
|
|
return topic;
|
|
}
|
|
|
|
// Generate a list of partition topics for a given base topic
|
|
static List<String> makePartitions(String baseTopic, int numPartitions) {
|
|
List<String> result = new ArrayList<>();
|
|
for (int i = 0; i < numPartitions; i++) {
|
|
result.add(baseTopic + "-partition-" + i);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
int passed = 0;
|
|
int total = 0;
|
|
|
|
// Test 1: correctness — single topic with partitions
|
|
{
|
|
total++;
|
|
List<String> inputs = makePartitions("persistent://tenant/ns/my-topic", 5);
|
|
@SuppressWarnings("unchecked")
|
|
List<String> slowResult = (List<String>) slowGetTopics(inputs)[0];
|
|
@SuppressWarnings("unchecked")
|
|
List<String> fastResult = (List<String>) fastGetTopics(inputs)[0];
|
|
|
|
assert slowResult.equals(fastResult)
|
|
: "Single-topic: slow and fast must agree. slow=" + slowResult + " fast=" + fastResult;
|
|
assert slowResult.size() == 1
|
|
: "Should deduplicate to 1 topic, got " + slowResult.size();
|
|
assert slowResult.get(0).equals("persistent://tenant/ns/my-topic")
|
|
: "Wrong base topic: " + slowResult.get(0);
|
|
System.out.println(" Test 1 PASS: single topic deduplicated to " + slowResult);
|
|
passed++;
|
|
}
|
|
|
|
// Test 2: correctness — multiple topics, mixed partitioned and non-partitioned
|
|
{
|
|
total++;
|
|
List<String> inputs = new ArrayList<>();
|
|
inputs.addAll(makePartitions("topic-A", 3));
|
|
inputs.add("topic-B"); // non-partitioned
|
|
inputs.addAll(makePartitions("topic-C", 2));
|
|
|
|
@SuppressWarnings("unchecked")
|
|
List<String> slowResult = (List<String>) slowGetTopics(inputs)[0];
|
|
@SuppressWarnings("unchecked")
|
|
List<String> fastResult = (List<String>) fastGetTopics(inputs)[0];
|
|
|
|
assert slowResult.equals(fastResult)
|
|
: "Multi-topic: slow and fast must agree. slow=" + slowResult + " fast=" + fastResult;
|
|
assert slowResult.size() == 3
|
|
: "Should have 3 unique topics, got " + slowResult.size() + ": " + slowResult;
|
|
assert slowResult.contains("topic-A") : "Must contain topic-A";
|
|
assert slowResult.contains("topic-B") : "Must contain topic-B";
|
|
assert slowResult.contains("topic-C") : "Must contain topic-C";
|
|
System.out.println(" Test 2 PASS: multi-topic case, result=" + slowResult);
|
|
passed++;
|
|
}
|
|
|
|
// Test 3: O(N^2) vs O(N) — op count scaling
|
|
{
|
|
total++;
|
|
int topicsCount = 50;
|
|
int partitionsPerTopic = 20; // 50 * 20 = 1000 inputs
|
|
List<String> inputs = new ArrayList<>();
|
|
for (int t = 0; t < topicsCount; t++) {
|
|
inputs.addAll(makePartitions("topic-" + t, partitionsPerTopic));
|
|
}
|
|
|
|
Object[] slowOut = slowGetTopics(inputs);
|
|
Object[] fastOut = fastGetTopics(inputs);
|
|
|
|
@SuppressWarnings("unchecked")
|
|
List<String> slowResult = (List<String>) slowOut[0];
|
|
@SuppressWarnings("unchecked")
|
|
List<String> fastResult = (List<String>) fastOut[0];
|
|
|
|
long slowOps = (Long) slowOut[1];
|
|
long fastOps = (Long) fastOut[1];
|
|
|
|
assert slowResult.equals(fastResult)
|
|
: "Scaling test: slow and fast must agree on results";
|
|
assert slowOps > fastOps
|
|
: "Slow must do more ops: slowOps=" + slowOps + " fastOps=" + fastOps;
|
|
assert slowResult.size() == topicsCount
|
|
: "Must deduplicate to " + topicsCount + " topics, got " + slowResult.size();
|
|
|
|
long speedup = slowOps / Math.max(fastOps, 1);
|
|
System.out.println(" Test 3 PASS: N=" + inputs.size() + " inputs → slowOps=" + slowOps
|
|
+ " fastOps=" + fastOps + " speedup=" + speedup + "x");
|
|
passed++;
|
|
}
|
|
|
|
// Test 4: worst-case O(N^2) — all unique topics (no dedup possible)
|
|
{
|
|
total++;
|
|
int N = 100;
|
|
// Each input is unique (already a base topic name, no partitions)
|
|
List<String> inputs = new ArrayList<>();
|
|
for (int i = 0; i < N; i++) inputs.add("unique-topic-" + i);
|
|
|
|
long expectedSlowOps = 0;
|
|
// After adding k elements, contains() scans k elements
|
|
// Total: 0 + 1 + 2 + ... + (N-1) = N*(N-1)/2
|
|
for (int k = 0; k < N; k++) expectedSlowOps += k;
|
|
|
|
Object[] slowOut = slowGetTopics(inputs);
|
|
long actualSlowOps = (Long) slowOut[1];
|
|
|
|
assert actualSlowOps == expectedSlowOps
|
|
: "Expected " + expectedSlowOps + " slow ops for unique N=" + N + " inputs, got " + actualSlowOps;
|
|
|
|
long fastOps = N;
|
|
long speedup = actualSlowOps / Math.max(fastOps, 1);
|
|
System.out.println(" Test 4 PASS: O(N*(N-1)/2)=" + actualSlowOps + " vs O(N)=" + fastOps
|
|
+ " speedup=" + speedup + "x (N=" + N + ")");
|
|
passed++;
|
|
}
|
|
|
|
System.out.println(passed + "/" + total + " PASS");
|
|
if (passed != total) System.exit(1);
|
|
}
|
|
}
|