java-topology/defects/ros2/unit/ListParametersAlgorithm.java

166 lines
7 KiB
Java

package unit;
import java.util.*;
/**
* Unit test for ros2-0002: NodeParameters::list_parameters() O(P²) → O(P)
*
* Simulates prefix deduplication in NodeParameters::list_parameters():
* Defective: std::find on result.prefixes (grows to P) inside loop over P parameters
* Fixed: unordered_set deduplication O(1) insert
*
* Compile: javac -d . ListParametersAlgorithm.java
* Run: java -ea unit.ListParametersAlgorithm
*/
public class ListParametersAlgorithm {
static class Result {
final List<String> names = new ArrayList<>();
final List<String> prefixes = new ArrayList<>();
}
// ── defective implementation ──────────────────────────────────────────────
static Result defectiveListParameters(List<String> parameterNames) {
Result result = new Result();
final char separator = '.';
for (String name : parameterNames) {
result.names.add(name);
int lastSep = name.lastIndexOf(separator);
if (lastSep != -1) {
String prefix = name.substring(0, lastSep);
// O(P) linear scan to deduplicate prefixes
boolean found = false;
for (String p : result.prefixes) {
if (p.equals(prefix)) { found = true; break; }
}
if (!found) result.prefixes.add(prefix);
}
}
return result;
}
// ── fixed implementation ──────────────────────────────────────────────────
static Result fixedListParameters(List<String> parameterNames) {
Result result = new Result();
final char separator = '.';
Set<String> seenPrefixes = new HashSet<>();
for (String name : parameterNames) {
result.names.add(name);
int lastSep = name.lastIndexOf(separator);
if (lastSep != -1) {
String prefix = name.substring(0, lastSep);
if (seenPrefixes.add(prefix)) { // O(1) insert + dedup
result.prefixes.add(prefix);
}
}
}
return result;
}
// ── tests ─────────────────────────────────────────────────────────────────
static int pass = 0, total = 0;
static void assertTrue(String name, boolean cond) {
total++;
if (cond) { pass++; System.out.println("PASS " + name); }
else System.out.println("FAIL " + name);
}
// Build nav2-style parameter names: namespace.group.param
static List<String> buildNav2Params(int namespaces, int groups, int paramsPerGroup) {
List<String> names = new ArrayList<>();
for (int ns = 0; ns < namespaces; ns++) {
for (int g = 0; g < groups; g++) {
for (int p = 0; p < paramsPerGroup; p++) {
names.add("ns" + ns + ".group" + g + ".param" + p);
}
}
}
return names;
}
public static void main(String[] args) {
// Test 1: empty parameters → empty result
{
Result d = defectiveListParameters(Collections.emptyList());
Result f = fixedListParameters(Collections.emptyList());
assertTrue("empty-defective", d.names.isEmpty() && d.prefixes.isEmpty());
assertTrue("empty-fixed", f.names.isEmpty() && f.prefixes.isEmpty());
}
// Test 2: parameters with no separator → no prefixes
{
List<String> params = Arrays.asList("foo", "bar", "baz");
Result d = defectiveListParameters(params);
Result f = fixedListParameters(params);
assertTrue("no-sep-defective", d.prefixes.isEmpty() && d.names.size() == 3);
assertTrue("no-sep-fixed", f.prefixes.isEmpty() && f.names.size() == 3);
}
// Test 3: all same prefix → only one prefix entry
{
List<String> params = Arrays.asList("nav2.speed", "nav2.timeout", "nav2.max_vel");
Result d = defectiveListParameters(params);
Result f = fixedListParameters(params);
assertTrue("same-prefix-dedup-defective", d.prefixes.size() == 1 && d.prefixes.get(0).equals("nav2"));
assertTrue("same-prefix-dedup-fixed", f.prefixes.size() == 1 && f.prefixes.get(0).equals("nav2"));
}
// Test 4: multiple distinct prefixes
{
List<String> params = Arrays.asList(
"nav2.speed", "nav2.timeout",
"moveit.planning_time", "moveit.max_vel",
"slam.resolution");
Result d = defectiveListParameters(params);
Result f = fixedListParameters(params);
assertTrue("multi-prefix-defective", d.prefixes.size() == 3);
assertTrue("multi-prefix-fixed", f.prefixes.size() == 3);
assertTrue("multi-prefix-names-match", d.names.equals(f.names));
// prefixes sets must be equal (order may differ)
assertTrue("multi-prefix-sets-match",
new HashSet<>(d.prefixes).equals(new HashSet<>(f.prefixes)));
}
// Test 5: nav2-style — 3 namespaces, 10 groups, 5 params each = 150 params, 30 unique prefixes
{
List<String> params = buildNav2Params(3, 10, 5);
Result d = defectiveListParameters(params);
Result f = fixedListParameters(params);
assertTrue("nav2-names-count-defective", d.names.size() == 150);
assertTrue("nav2-names-count-fixed", f.names.size() == 150);
// Each ns.group is a unique "ns_X.group_Y" prefix → 3*10 = 30 unique prefixes
assertTrue("nav2-prefix-count-defective", d.prefixes.size() == 30);
assertTrue("nav2-prefix-count-fixed", f.prefixes.size() == 30);
assertTrue("nav2-prefix-sets-match",
new HashSet<>(d.prefixes).equals(new HashSet<>(f.prefixes)));
}
// Test 6: performance at P=500 (500 unique prefixes worst case)
{
List<String> params = new ArrayList<>();
for (int i = 0; i < 500; i++) params.add("group" + i + ".param" + i);
long t0 = System.nanoTime();
for (int r = 0; r < 200; r++) defectiveListParameters(params);
long tDef = System.nanoTime() - t0;
t0 = System.nanoTime();
for (int r = 0; r < 200; r++) fixedListParameters(params);
long tFix = System.nanoTime() - t0;
double ratio = (double) tDef / tFix;
System.out.printf(" Perf P=500 unique: defective=%.1fms fixed=%.1fms ratio=%.1fx%n",
tDef / 1e6, tFix / 1e6, ratio);
assertTrue("perf-speedup", ratio > 3.0);
}
System.out.println(pass + "/" + total + " PASS");
assert pass == total : pass + "/" + total + " passed";
}
}