Fix map-routed dispatcher events to respect path-bound keys.

Resolve static map lookups along the call path, guard enum widening when
keys are bound, and add isolation tests for warmed-cache map routing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-12 08:31:08 +02:00
parent 9ede60049b
commit 7a5fc60ace
4 changed files with 417 additions and 32 deletions

View File

@@ -0,0 +1,128 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Guards map-routed endpoint resolution and per-machine polymorphic event isolation when the
* call graph and accessor caches are reused across multiple {@link HeuristicCallGraphEngine#findChains}
* invocations on the same {@link CodebaseContext}.
*/
class PerformanceCacheMultiMachineIsolationTest {
private static final String ORDER_CONTROLLER = "com.example.OrderController";
private static final String SHIPMENT_CONTROLLER = "com.example.ShipmentController";
private static final String ORDER_MACHINE = "com.example.OrderMachine";
private static final String SHIPMENT_MACHINE = "com.example.ShipmentMachine";
@Test
void mapRoutedEndpointsShouldResolveSingleEventPerLiteralEndpoint(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
import java.util.Map;
public class OrderController {
OrderMachine sm;
public void payOrder() { sm.sendEvent(Routes.ROUTES.get("order.pay")); }
public void shipOrder() { sm.sendEvent(Routes.ROUTES.get("order.ship")); }
}
class Routes {
static final Map<String, OrderEvent> ROUTES = Map.of(
"order.pay", OrderEvent.PAY,
"order.ship", OrderEvent.SHIP
);
}
enum OrderEvent { PAY, SHIP, CANCEL }
class OrderMachine { public void sendEvent(OrderEvent e) {} }
""";
Files.writeString(tempDir.resolve("App.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
TriggerPoint trigger = TriggerPoint.builder()
.className(ORDER_MACHINE)
.methodName("sendEvent")
.event("e")
.build();
EntryPoint payEntry = EntryPoint.builder().className(ORDER_CONTROLLER).methodName("payOrder").build();
EntryPoint shipEntry = EntryPoint.builder().className(ORDER_CONTROLLER).methodName("shipOrder").build();
CallChain payChain = engine.findChains(List.of(payEntry), List.of(trigger)).get(0);
CallChain shipChain = engine.findChains(List.of(shipEntry), List.of(trigger)).get(0);
assertThat(payChain.getTriggerPoint().getPolymorphicEvents()).containsExactly("OrderEvent.PAY");
assertThat(shipChain.getTriggerPoint().getPolymorphicEvents()).containsExactly("OrderEvent.SHIP");
assertThat(context.getCache()).containsKey("heuristicCallGraph");
CallChain payAgain = engine.findChains(List.of(payEntry), List.of(trigger)).get(0);
CallChain shipAgain = engine.findChains(List.of(shipEntry), List.of(trigger)).get(0);
assertThat(payAgain.getTriggerPoint().getPolymorphicEvents())
.isEqualTo(payChain.getTriggerPoint().getPolymorphicEvents());
assertThat(shipAgain.getTriggerPoint().getPolymorphicEvents())
.isEqualTo(shipChain.getTriggerPoint().getPolymorphicEvents());
}
@Test
void separateMachinesShouldNotCrossContaminateMapRoutedResolution(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
import java.util.Map;
public class OrderController {
OrderMachine orderMachine;
public void pay() { orderMachine.sendEvent(OrderRoutes.ROUTES.get("order.pay")); }
}
public class ShipmentController {
ShipmentMachine shipmentMachine;
public void dispatch() { shipmentMachine.sendEvent(ShipmentRoutes.ROUTES.get("shipment.dispatch")); }
}
class OrderRoutes {
static final Map<String, OrderEvent> ROUTES = Map.of("order.pay", OrderEvent.PAY);
}
class ShipmentRoutes {
static final Map<String, ShipmentEvent> ROUTES = Map.of("shipment.dispatch", ShipmentEvent.DISPATCH);
}
enum OrderEvent { PAY }
enum ShipmentEvent { DISPATCH }
class OrderMachine { public void sendEvent(OrderEvent e) {} }
class ShipmentMachine { public void sendEvent(ShipmentEvent e) {} }
""";
Files.writeString(tempDir.resolve("App.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
EntryPoint orderEntry = EntryPoint.builder().className(ORDER_CONTROLLER).methodName("pay").build();
EntryPoint shipmentEntry = EntryPoint.builder().className(SHIPMENT_CONTROLLER).methodName("dispatch").build();
TriggerPoint orderTrigger = TriggerPoint.builder()
.className(ORDER_MACHINE)
.methodName("sendEvent")
.event("e")
.build();
TriggerPoint shipmentTrigger = TriggerPoint.builder()
.className(SHIPMENT_MACHINE)
.methodName("sendEvent")
.event("e")
.build();
engine.findChains(List.of(orderEntry), List.of(orderTrigger));
CallChain shipmentChain = engine.findChains(List.of(shipmentEntry), List.of(shipmentTrigger)).get(0);
assertThat(shipmentChain.getTriggerPoint().getPolymorphicEvents()).containsExactly("ShipmentEvent.DISPATCH");
CallChain orderChain = engine.findChains(List.of(orderEntry), List.of(orderTrigger)).get(0);
assertThat(orderChain.getTriggerPoint().getPolymorphicEvents()).containsExactly("OrderEvent.PAY");
}
}