tests reorg
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,957 @@
|
|||||||
|
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.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@Tag("heuristic_guess_paths_and_scrape_regex_replace_with_data_flow_analysis")
|
||||||
|
class HeuristicCallGraphEngineGetterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromAlternativeGetterName() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus(DomainEvent domainEvent) {
|
||||||
|
service.process(domainEvent.get2Type());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DomainEvent {
|
||||||
|
public String get2Type() {
|
||||||
|
return "ALTERNATIVE_EVENT";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_alt_getter");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("ALTERNATIVE_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromInlineInstantiation() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new DomainEvent().getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DomainEvent {
|
||||||
|
public String getEvent() { return "INLINE_EVENT"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_inline_inst");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("INLINE_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromInlineInstantiationWithArguments() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new DomainEvent("PAYLOAD_ID").getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DomainEvent {
|
||||||
|
private String id;
|
||||||
|
public DomainEvent(String id) { this.id = id; }
|
||||||
|
public String getEvent() { return "INLINE_EVENT_WITH_ARGS"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_inline_inst_args");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("INLINE_EVENT_WITH_ARGS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromInlineInstantiationWithGenerics() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new DomainEvent<String>().getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DomainEvent<T> {
|
||||||
|
public String getEvent() { return "INLINE_EVENT_WITH_GENERICS"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_inline_inst_gen");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("INLINE_EVENT_WITH_GENERICS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromInlineInstantiationInheritedMethod() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new SpecificDomainEvent().getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BaseDomainEvent {
|
||||||
|
public String getEvent() { return "BASE_EVENT_RETURN"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class SpecificDomainEvent extends BaseDomainEvent {
|
||||||
|
// no getEvent here, it inherits it!
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_inline_inst_hier");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("BASE_EVENT_RETURN");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromInlineInstantiationWithComplexArguments() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus(Order o, String abc) {
|
||||||
|
service.process(new DomainEvent(o.getId(), abc).getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Order {
|
||||||
|
public String getId() { return "123"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class DomainEvent {
|
||||||
|
private String id;
|
||||||
|
private String extra;
|
||||||
|
public DomainEvent(String id, String extra) { this.id = id; this.extra = extra; }
|
||||||
|
public String getEvent() { return "INLINE_EVENT_COMPLEX_ARGS"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_inline_inst_complex_args");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("INLINE_EVENT_COMPLEX_ARGS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotIncludeSubclassesForInlineInstantiation() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new BaseEvent().getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BaseEvent {
|
||||||
|
public String getEvent() { return "BASE_EVENT_RETURN"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubEvent extends BaseEvent {
|
||||||
|
public String getEvent() { return "SUB_EVENT_RETURN"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_inline_inst_subclass");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
// Only BASE_EVENT_RETURN should be found. SUB_EVENT_RETURN is a subclass and should not be included for direct instantiations
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("BASE_EVENT_RETURN");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromFieldAssignedInConstructor() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new OrderPickedUpEvent().getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderPickedUpEvent {
|
||||||
|
private String type;
|
||||||
|
public OrderPickedUpEvent() {
|
||||||
|
this.type = "PICKED_UP_IN_CONSTRUCTOR";
|
||||||
|
}
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_field_const");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("PICKED_UP_IN_CONSTRUCTOR");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromFieldAssignedInSuperConstructor() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new OrderCancelledEvent().getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BaseEvent {
|
||||||
|
private String type;
|
||||||
|
public BaseEvent(String type, String otherVar) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderCancelledEvent extends BaseEvent {
|
||||||
|
public OrderCancelledEvent() {
|
||||||
|
super("CANCELLED_IN_SUPER", "someVar");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_super_const");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("CANCELLED_IN_SUPER");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromFieldAssignedToAnotherConstantInConstructor() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new EventWithIndirectConstant().getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventConstants {
|
||||||
|
public static final String INDIRECT_EVENT = "INDIRECT_EVENT_VALUE";
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventWithIndirectConstant {
|
||||||
|
private String type;
|
||||||
|
public EventWithIndirectConstant() {
|
||||||
|
this.type = EventConstants.INDIRECT_EVENT;
|
||||||
|
}
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_indirect_const");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("INDIRECT_EVENT_VALUE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromConstructorInvocation() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new EventWithThisCall().getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventWithThisCall {
|
||||||
|
private String type;
|
||||||
|
public EventWithThisCall() {
|
||||||
|
this("THIS_CALL_CONSTANT");
|
||||||
|
}
|
||||||
|
public EventWithThisCall(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_this_const");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("THIS_CALL_CONSTANT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromDynamicMethodAssignmentInConstructor() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new OrderCancelledEvent().getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderCancelledEvent {
|
||||||
|
private String cancelEventType;
|
||||||
|
public OrderCancelledEvent() {
|
||||||
|
this.cancelEventType = assertMatchingCancelEventType("some_sender");
|
||||||
|
}
|
||||||
|
private String assertMatchingCancelEventType(String sender) {
|
||||||
|
return "CANCELLED_BY_SENDER";
|
||||||
|
}
|
||||||
|
public String getType() { return cancelEventType; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_dynamic_rhs");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("CANCELLED_BY_SENDER");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromFieldDeclarationsAndInitializers() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus1() {
|
||||||
|
service.process(new EventWithFieldDecl().getType());
|
||||||
|
}
|
||||||
|
public void handleStatus2() {
|
||||||
|
service.process(new EventWithInitBlock().getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventWithFieldDecl {
|
||||||
|
private String event = "FIELD_DECL";
|
||||||
|
public String getType() { return event; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventWithInitBlock {
|
||||||
|
private String event;
|
||||||
|
{
|
||||||
|
event = "INIT_BLOCK";
|
||||||
|
}
|
||||||
|
public String getType() { return event; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_field_init");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint ep1 = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus1").build();
|
||||||
|
EntryPoint ep2 = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus2").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(ep1, ep2), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(2);
|
||||||
|
|
||||||
|
java.util.List<String> allEvents = new java.util.ArrayList<>();
|
||||||
|
chains.forEach(c -> allEvents.addAll(c.getTriggerPoint().getPolymorphicEvents()));
|
||||||
|
org.assertj.core.api.Assertions.assertThat(allEvents).containsExactlyInAnyOrder("FIELD_DECL", "INIT_BLOCK");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFallbackToScraperForComplexInlineInstantiations() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
// The AST tracer will trace EventWithConstructorArg.getType()
|
||||||
|
// It will find `this.type = typeArg`, which it cannot resolve because it's a parameter.
|
||||||
|
// The fallback scraper should catch "COMPLEX_INLINE_CONST".
|
||||||
|
service.process(new EventWithConstructorArg("COMPLEX_INLINE_CONST", 123).getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventWithConstructorArg {
|
||||||
|
private String type;
|
||||||
|
public EventWithConstructorArg(String typeArg, int other) {
|
||||||
|
this.type = typeArg;
|
||||||
|
}
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_scraper");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("COMPLEX_INLINE_CONST");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromConstructorAssignment() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
EventWithConstructorArg e = new EventWithConstructorArg("EVENT_FROM_CONSTRUCTOR", 123);
|
||||||
|
service.process(e.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventWithConstructorArg {
|
||||||
|
private String type;
|
||||||
|
public EventWithConstructorArg(String typeArg, int other) {
|
||||||
|
this.type = typeArg;
|
||||||
|
}
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_constructor_assignment");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("EVENT_FROM_CONSTRUCTOR");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPrioritizeSetterOverConstructor() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
EventWithConstructorArg e = new EventWithConstructorArg("EVENT_FROM_CONSTRUCTOR", 123);
|
||||||
|
e.setType("EVENT_FROM_SETTER");
|
||||||
|
service.process(e.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventWithConstructorArg {
|
||||||
|
private String type;
|
||||||
|
public EventWithConstructorArg(String typeArg, int other) {
|
||||||
|
this.type = typeArg;
|
||||||
|
}
|
||||||
|
public void setType(String t) { this.type = t; }
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_setter_priority");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("EVENT_FROM_SETTER");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromFieldConstructorAssignment() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
private EventWithConstructorArg e;
|
||||||
|
public OrderController() {
|
||||||
|
this.e = new EventWithConstructorArg("EVENT_FROM_FIELD", 123);
|
||||||
|
}
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(this.e.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventWithConstructorArg {
|
||||||
|
private String type;
|
||||||
|
public EventWithConstructorArg(String typeArg, int other) {
|
||||||
|
this.type = typeArg;
|
||||||
|
}
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_field_assignment");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
System.out.println("DEBUG CHAINS: " + (chains.isEmpty() ? "empty" : chains.get(0).getTriggerPoint().getPolymorphicEvents()));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("EVENT_FROM_FIELD");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromLombokDataAndAllArgsConstructor() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleLombok() {
|
||||||
|
LombokEvent e = new LombokEvent("LOMBOK_EVENT", 1);
|
||||||
|
service.process(e.getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
class LombokEvent {
|
||||||
|
private String event;
|
||||||
|
private int id;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_lombok");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleLombok").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("LOMBOK_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFailHeuristicAndRequireAccurateLombokMapping() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleLombok() {
|
||||||
|
MultiEvent e = new MultiEvent("CORRECT_EVENT", "WRONG_EVENT");
|
||||||
|
service.process(e.getEventType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
class MultiEvent {
|
||||||
|
private String eventType;
|
||||||
|
private String unrelatedString;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_lombok_heuristic_break");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleLombok").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactly("CORRECT_EVENT"); // If heuristic rips both, it will fail because it contains WRONG_EVENT too!
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventsWhenFieldTransformerMethodMatchesSetterPatternButArgIsNonConstant() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private Transformer transformer;
|
||||||
|
public void processEvent(String dto) {
|
||||||
|
Event e = transformer.transform(dto);
|
||||||
|
machine.fire(e.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Transformer {
|
||||||
|
Event transform(String input);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TransformerImpl implements Transformer {
|
||||||
|
public Event transform(String input) {
|
||||||
|
return new Event("ORDER_DISPATCHED");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Event {
|
||||||
|
private String type;
|
||||||
|
public Event(String type) { this.type = type; }
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Machine {
|
||||||
|
public void fire(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
java.nio.file.Path tempDir = java.nio.file.Files.createTempDirectory("callgraph_test_transformer");
|
||||||
|
java.nio.file.Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.Machine")
|
||||||
|
.methodName("fire")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
// Must resolve through TransformerImpl.transform() → "ORDER_DISPATCHED",
|
||||||
|
// NOT return empty polymorphicEvents due to early exit on the setter match.
|
||||||
|
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("ORDER_DISPATCHED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEnumPassedThroughWrapperObjectAndGetter() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class OrderController {
|
||||||
|
private OrderMapper mapper;
|
||||||
|
private StateMachine machine;
|
||||||
|
|
||||||
|
public void processOrder(String dto) {
|
||||||
|
OrderEvent event = mapper.toEvent(dto);
|
||||||
|
machine.fire(event.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrderMapper {
|
||||||
|
OrderEvent toEvent(String input);
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderMapperImpl implements OrderMapper {
|
||||||
|
public OrderEvent toEvent(String input) {
|
||||||
|
return new OrderEvent(States.PAYMENT_RECEIVED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderEvent {
|
||||||
|
private States type;
|
||||||
|
public OrderEvent(States type) { this.type = type; }
|
||||||
|
public States getType() { return type; } // bare field, no this.
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
public void fire(States event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum States { PAYMENT_RECEIVED, CANCELLED }
|
||||||
|
""";
|
||||||
|
java.nio.file.Path tempDir = java.nio.file.Files.createTempDirectory("callgraph_test_wrapper");
|
||||||
|
java.nio.file.Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processOrder")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("fire")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
System.out.println("DIAGNOSTIC polymorphicEvents: " +
|
||||||
|
chains.get(0).getTriggerPoint().getPolymorphicEvents());
|
||||||
|
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("States.PAYMENT_RECEIVED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveTransformingGetterThatMapsDtoEnumToStateMachineEnum() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class OrderController {
|
||||||
|
private OrderMapper mapper;
|
||||||
|
private StateMachine machine;
|
||||||
|
|
||||||
|
public void processOrder(String dto) {
|
||||||
|
OrderMsg msg = mapper.toMsg(dto);
|
||||||
|
machine.fire(msg.getSmEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrderMapper {
|
||||||
|
OrderMsg toMsg(String input);
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderMapperImpl implements OrderMapper {
|
||||||
|
public OrderMsg toMsg(String input) {
|
||||||
|
return new OrderMsg(OrderType.PAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderMsg {
|
||||||
|
private OrderType type;
|
||||||
|
public OrderMsg(OrderType type) { this.type = type; }
|
||||||
|
public SMEvent getSmEvent() {
|
||||||
|
return switch (type) {
|
||||||
|
case PAY -> SMEvent.PAY_RECEIVED;
|
||||||
|
case CANCEL -> SMEvent.ORDER_CANCELLED;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
public void fire(SMEvent event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderType { PAY, CANCEL }
|
||||||
|
enum SMEvent { PAY_RECEIVED, ORDER_CANCELLED }
|
||||||
|
""";
|
||||||
|
java.nio.file.Path tempDir = java.nio.file.Files.createTempDirectory("callgraph_test_twoenum");
|
||||||
|
java.nio.file.Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processOrder")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("fire")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
System.out.println("TWO-ENUM polymorphicEvents: " +
|
||||||
|
chains.get(0).getTriggerPoint().getPolymorphicEvents());
|
||||||
|
// Must return the SM enum, NOT the DTO enum (OrderType.PAY would be wrong)
|
||||||
|
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("SMEvent.PAY_RECEIVED");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,409 @@
|
|||||||
|
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.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@Tag("heuristic_guess_paths_and_scrape_regex_replace_with_data_flow_analysis")
|
||||||
|
class HeuristicCallGraphEnginePolymorphicTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolvePolymorphicInheritanceAcrossChain() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||||
|
doProcessOrderEvent(domainEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doProcessOrderEvent(RichOrderEvent domainEvent) {
|
||||||
|
OrderEvents eventType = assertSupportedOrderEvent(domainEvent.getType());
|
||||||
|
service.updateOrderState(eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEvents assertSupportedOrderEvent(OrderEvents e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {
|
||||||
|
// sendEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RichOrderEvent {
|
||||||
|
OrderEvents getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
class CancelOrderEvent implements RichOrderEvent {
|
||||||
|
public OrderEvents getType() { return OrderEvents.CANCELLED; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReceiveOrderEvent implements RichOrderEvent {
|
||||||
|
public OrderEvents getType() { return OrderEvents.RECEIVED; }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { CREATE, PAY, CANCELLED, RECEIVED }
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_test_poly");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processOrderEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.as("Resolved event was: " + chain.getTriggerPoint().getEvent())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.CANCELLED", "OrderEvents.RECEIVED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveTernaryAndStringPolymorphicReturns() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent() {
|
||||||
|
service.updateOrderState(new MysteryPayload().getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(Object event) {
|
||||||
|
// sendEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MysteryPayload {
|
||||||
|
public Object getType() {
|
||||||
|
int a = 5;
|
||||||
|
if (a > 10) {
|
||||||
|
return "RAW_STRING_EVENT";
|
||||||
|
}
|
||||||
|
return a > 5 ? OrderEvents.ABCD : OrderEvents.PAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { ABCD, PAY }
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_test_ternary_string");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processOrderEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.as("Resolved event was: " + chain.getTriggerPoint().getEvent())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.ABCD", "OrderEvents.PAY", "RAW_STRING_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolvePolymorphicInheritanceThroughDelegation() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(BaseEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseEvent {
|
||||||
|
OrderEvents getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class AbstractEvent implements BaseEvent {
|
||||||
|
protected OrderEvents internalGetType() {
|
||||||
|
return OrderEvents.PAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConcreteEvent extends AbstractEvent {
|
||||||
|
public OrderEvents getType() {
|
||||||
|
return internalGetType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnotherConcreteEvent implements BaseEvent {
|
||||||
|
public OrderEvents getType() {
|
||||||
|
return delegateToHelper();
|
||||||
|
}
|
||||||
|
private OrderEvents delegateToHelper() {
|
||||||
|
return OrderEvents.CANCELLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY, CANCELLED }
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_test_poly_delegation");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processOrderEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.PAY", "OrderEvents.CANCELLED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveMethodInheritedFromAbstractBaseClass() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(BaseEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseEvent {
|
||||||
|
OrderEvents getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class AbstractEvent implements BaseEvent {
|
||||||
|
public OrderEvents getType() {
|
||||||
|
return OrderEvents.PAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concrete subclass inherits getType() but doesn't override it!
|
||||||
|
class InheritingEvent extends AbstractEvent {
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_test_inherited");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processOrderEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromAbstractSuperclass() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new PostAcceptanceOrderModificationsRejectedEvent().getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class AbstractPostAcceptanceOrderModificationsEvent {
|
||||||
|
private String event;
|
||||||
|
public AbstractPostAcceptanceOrderModificationsEvent(String event) {
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
public String getType() { return event; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostAcceptanceOrderModificationsRejectedEvent extends AbstractPostAcceptanceOrderModificationsEvent {
|
||||||
|
public PostAcceptanceOrderModificationsRejectedEvent() {
|
||||||
|
super("POST_ACCEPTANCE_REJECTED");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_post_acc");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("POST_ACCEPTANCE_REJECTED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveMappedTransitionEnumThroughAbstractBaseClassAndMultipleArgs() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class FlowController {
|
||||||
|
private CommandFactory factory;
|
||||||
|
private StateMachine machine;
|
||||||
|
|
||||||
|
public void handleRequest(String payload) {
|
||||||
|
// Creates subclass with multiple args, including intermediate enum
|
||||||
|
BaseCommand cmd = factory.buildCommand(payload);
|
||||||
|
|
||||||
|
// Fires using the mapped enum from the inherited base method
|
||||||
|
machine.fire(cmd.getTransitionEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CommandFactory {
|
||||||
|
public BaseCommand buildCommand(String payload) {
|
||||||
|
// Passing multiple args: String, Enum1, String, int
|
||||||
|
return new WebCommand(payload, SourceType.WEB_API, "meta_v1", 42);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BaseCommand {
|
||||||
|
protected String id;
|
||||||
|
protected SourceType sourceType; // The intermediate enum
|
||||||
|
protected String metadata;
|
||||||
|
|
||||||
|
public BaseCommand(String id, SourceType sourceType, String metadata) {
|
||||||
|
this.id = id;
|
||||||
|
this.sourceType = sourceType;
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The tricky part: Inherited method doing the mapping
|
||||||
|
public TransitionEnum getTransitionEvent() {
|
||||||
|
return switch (this.sourceType) {
|
||||||
|
case WEB_API -> TransitionEnum.ON_WEB_START;
|
||||||
|
case MOBILE_APP -> TransitionEnum.ON_MOBILE_START;
|
||||||
|
case CRON_JOB -> TransitionEnum.ON_SYSTEM_START;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebCommand extends BaseCommand {
|
||||||
|
private int priorityLevel;
|
||||||
|
|
||||||
|
public WebCommand(String id, SourceType sourceType, String metadata, int priorityLevel) {
|
||||||
|
super(id, sourceType, metadata); // Engine must track through super()
|
||||||
|
this.priorityLevel = priorityLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
public void fire(TransitionEnum event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SourceType { WEB_API, MOBILE_APP, CRON_JOB }
|
||||||
|
enum TransitionEnum { ON_WEB_START, ON_MOBILE_START, ON_SYSTEM_START }
|
||||||
|
""";
|
||||||
|
java.nio.file.Path tempDir = java.nio.file.Files.createTempDirectory("callgraph_test_complex_inheritance");
|
||||||
|
java.nio.file.Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.FlowController")
|
||||||
|
.methodName("handleRequest")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("fire")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
|
||||||
|
System.out.println("COMPLEX INHERITANCE polymorphicEvents: " +
|
||||||
|
chains.get(0).getTriggerPoint().getPolymorphicEvents());
|
||||||
|
|
||||||
|
// The assertion to prove the bug is fixed.
|
||||||
|
// It MUST NOT contain "SourceType.WEB_API"
|
||||||
|
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("TransitionEnum.ON_WEB_START");
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,472 @@
|
|||||||
|
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.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@Tag("heuristic_guess_paths_and_scrape_regex_replace_with_data_flow_analysis")
|
||||||
|
class HeuristicCallGraphEngineTypeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveLocalSetterAndSwitchExpressionPolymorphism(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderService {
|
||||||
|
public void updateOrderState(String vv) {
|
||||||
|
StateMachine sm = new StateMachine();
|
||||||
|
MysteryPayload a = new MysteryPayload();
|
||||||
|
a.setType(my_func(vv));
|
||||||
|
sm.sendEvent(a.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEvents my_func(String q) {
|
||||||
|
return switch (q) {
|
||||||
|
case "a" -> OrderEvents.A8;
|
||||||
|
case "b" -> OrderEvents.A133;
|
||||||
|
default -> throw new IllegalArgumentException("Unknown");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { A8, A133 }
|
||||||
|
|
||||||
|
class MysteryPayload {
|
||||||
|
private OrderEvents type;
|
||||||
|
public void setType(OrderEvents type) { this.type = type; }
|
||||||
|
public OrderEvents getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
public void sendEvent(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Files.writeString(tempDir.resolve("OrderService.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
|
||||||
|
System.out.println("Resolved poly events: " + chain.getTriggerPoint().getPolymorphicEvents());
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.A8", "OrderEvents.A133");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveOldStyleSwitchStatementPolymorphism(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderService {
|
||||||
|
public void updateOrderState(String vv) {
|
||||||
|
StateMachine sm = new StateMachine();
|
||||||
|
sm.sendEvent(getEventFromOldSwitch(vv));
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEvents getEventFromOldSwitch(String q) {
|
||||||
|
switch (q) {
|
||||||
|
case "a": return OrderEvents.A8;
|
||||||
|
case "b":
|
||||||
|
case "c": return OrderEvents.A133;
|
||||||
|
default: return OrderEvents.PAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { A8, A133, PAY }
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
public void sendEvent(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Files.writeString(tempDir.resolve("OrderService.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderService").methodName("updateOrderState").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.StateMachine").methodName("sendEvent").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.A8", "OrderEvents.A133", "OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveMultiValueSwitchExpressionPolymorphism(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderService {
|
||||||
|
public void updateOrderState(String vv) {
|
||||||
|
StateMachine sm = new StateMachine();
|
||||||
|
sm.sendEvent(getEventMultiValue(vv));
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEvents getEventMultiValue(String q) {
|
||||||
|
return switch (q) {
|
||||||
|
case "a", "b" -> OrderEvents.A8;
|
||||||
|
case "c" -> OrderEvents.A133;
|
||||||
|
default -> OrderEvents.PAY;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { A8, A133, PAY }
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
public void sendEvent(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Files.writeString(tempDir.resolve("OrderService.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderService").methodName("updateOrderState").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.StateMachine").methodName("sendEvent").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.A8", "OrderEvents.A133", "OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveSwitchExpressionWithBlockAndYieldPolymorphism(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderService {
|
||||||
|
public void updateOrderState(String vv) {
|
||||||
|
StateMachine sm = new StateMachine();
|
||||||
|
sm.sendEvent(getEventWithYield(vv));
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEvents getEventWithYield(String q) {
|
||||||
|
return switch (q) {
|
||||||
|
case "a" -> {
|
||||||
|
System.out.println("Processing a");
|
||||||
|
yield OrderEvents.A8;
|
||||||
|
}
|
||||||
|
case "b" -> { yield OrderEvents.A133; }
|
||||||
|
default -> OrderEvents.PAY;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { A8, A133, PAY }
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
public void sendEvent(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Files.writeString(tempDir.resolve("OrderService.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderService").methodName("updateOrderState").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.StateMachine").methodName("sendEvent").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.A8", "OrderEvents.A133", "OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTraceEventsThroughMapStructInterfaceImpl() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
private OrderMapper mapper = new OrderMapperImpl();
|
||||||
|
public void processOrderEvent(String eventDtoStr) {
|
||||||
|
OrderEvent e = mapper.toEvent(eventDtoStr);
|
||||||
|
service.updateOrderState(e.getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrderMapper {
|
||||||
|
OrderEvent toEvent(String source);
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderMapperImpl implements OrderMapper {
|
||||||
|
@Override
|
||||||
|
public OrderEvent toEvent(String source) {
|
||||||
|
return new OrderEvent("MAPPED_EVENT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderEvent {
|
||||||
|
private String event;
|
||||||
|
public OrderEvent(String event) { this.event = event; }
|
||||||
|
public String getEvent() { return event; }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_test_mapstruct");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processOrderEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("MAPPED_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTraceArrayAndCollectionAccessGracefully() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent() {
|
||||||
|
OrderEvents[] events = new OrderEvents[]{OrderEvents.PAY};
|
||||||
|
service.updateOrderState(events[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_test_array");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processOrderEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
// We don't dynamically resolve array indexes, but it shouldn't crash.
|
||||||
|
// It returns the array access expression as a string.
|
||||||
|
assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("new OrderEvents[]{OrderEvents.PAY}[0]");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromSwitchStatementInMethod() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus(DomainEvent domainEvent) {
|
||||||
|
service.process(domainEvent.getEventForStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DomainEvent {
|
||||||
|
private OrderStatus status;
|
||||||
|
public String getEventForStatus() {
|
||||||
|
switch (status) {
|
||||||
|
case NEW: return "CREATE_EVENT";
|
||||||
|
case PAID: return "PAY_EVENT";
|
||||||
|
default: return "CANCEL_EVENT";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderStatus { NEW, PAID }
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_switch");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("CREATE_EVENT", "PAY_EVENT", "CANCEL_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromEnumReturn() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus(DomainEvent domainEvent) {
|
||||||
|
service.process(domainEvent.getEventEnum());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DomainEvent {
|
||||||
|
public String getEventEnum() {
|
||||||
|
return MyEvents.ORDER_PAID.name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MyEvents { ORDER_PAID }
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_enum_return");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("ORDER_PAID");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromArrayAndList() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
import java.util.List;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleArray() {
|
||||||
|
String[] events = {"ARRAY_EVENT"};
|
||||||
|
service.process(events[0]);
|
||||||
|
}
|
||||||
|
public void handleList() {
|
||||||
|
List<String> listEvents = List.of("LIST_EVENT");
|
||||||
|
service.process(listEvents.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_array");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint1 = EntryPoint.builder().className("com.example.OrderController").methodName("handleArray").build();
|
||||||
|
EntryPoint entryPoint2 = EntryPoint.builder().className("com.example.OrderController").methodName("handleList").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint1, entryPoint2), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(2);
|
||||||
|
List<String> resolvedEvents = chains.stream().flatMap(c -> c.getTriggerPoint().getPolymorphicEvents().stream()).toList();
|
||||||
|
org.assertj.core.api.Assertions.assertThat(resolvedEvents).containsExactlyInAnyOrder("ARRAY_EVENT", "LIST_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEnumValueOfFromString(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class Endpoint {
|
||||||
|
private OrderService service;
|
||||||
|
public void trigger(String eventString) {
|
||||||
|
service.doSomething(eventString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void doSomething(String str) {
|
||||||
|
MyEvents event = mapToEnum(str);
|
||||||
|
sendEvent(event);
|
||||||
|
}
|
||||||
|
private MyEvents mapToEnum(String str) {
|
||||||
|
return MyEvents.valueOf(str);
|
||||||
|
}
|
||||||
|
public void sendEvent(MyEvents e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MyEvents { A, B }
|
||||||
|
""";
|
||||||
|
Files.writeString(tempDir.resolve("Endpoint.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.Endpoint")
|
||||||
|
.methodName("trigger")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("e")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("MyEvents.A", "MyEvents.B");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
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.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@Tag("heuristic_guess_paths_and_scrape_regex_replace_with_data_flow_analysis")
|
||||||
|
class HeuristicCallGraphEngineWrapperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUnwrapDeepMethodWrappers() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||||
|
OrderEvents eventType = wrap3(wrap2(wrap1(domainEvent.getType())));
|
||||||
|
service.updateOrderState(eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEvents wrap1(OrderEvents e) { return e; }
|
||||||
|
private OrderEvents wrap2(OrderEvents e) { return e; }
|
||||||
|
private OrderEvents wrap3(OrderEvents e) { return e; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichOrderEvent {
|
||||||
|
public OrderEvents getType() { return OrderEvents.CREATE; }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { CREATE }
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_test_deep_wrap");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processOrderEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.as("Resolved event was: " + chain.getTriggerPoint().getEvent())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.CREATE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldProperlyUnwrapDeeplyNestedWrapperCallsForFix1() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(MyEvent event) {
|
||||||
|
OrderEvents eventType = wrap1(wrap2(wrap3(event.getType())));
|
||||||
|
service.updateOrderState(eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEvents wrap1(OrderEvents e) { return e; }
|
||||||
|
private OrderEvents wrap2(OrderEvents e) { return e; }
|
||||||
|
private OrderEvents wrap3(OrderEvents e) { return e; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyEvent {
|
||||||
|
public OrderEvents getType() { return OrderEvents.CREATE; }
|
||||||
|
}
|
||||||
|
enum OrderEvents { CREATE }
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_nested_wrap");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("processOrderEvent").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("updateOrderState").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("OrderEvents.CREATE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotExtractClassInstanceCreationEvenWithComplexArgsForFix2() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void createOrder(String oID) {
|
||||||
|
service.processOrderEvent(new OrderCancelledEvent(new OrderId(oID)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class OrderService {
|
||||||
|
public void processOrderEvent(OrderCancelledEvent event) { }
|
||||||
|
}
|
||||||
|
class OrderCancelledEvent {
|
||||||
|
public OrderCancelledEvent(OrderId id) {}
|
||||||
|
}
|
||||||
|
class OrderId {
|
||||||
|
public OrderId(String id) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_complex_cic");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("createOrder").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("processOrderEvent").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
assertThat(chains.get(0).getTriggerPoint().getEvent()).isEqualTo("new OrderCancelledEvent(new OrderId(oID))");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotCrashOnStaticMethodWrapping() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus(DomainEvent domainEvent) {
|
||||||
|
service.process(String.valueOf(domainEvent.getEvent()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DomainEvent {
|
||||||
|
public String getEvent() { return "STATIC_WRAP_EVENT"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_static_wrap");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotExtractConstantsFromNestedClassInstanceCreations() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleNestedConstructors() {
|
||||||
|
// The event contains a nested constructor. The heuristic should NOT extract WRONG_EVENT.
|
||||||
|
OuterEvent e = new OuterEvent("CORRECT_EVENT", new InnerPayload("WRONG_EVENT"));
|
||||||
|
service.process(e.getEventType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OuterEvent {
|
||||||
|
private String eventType;
|
||||||
|
private InnerPayload payload;
|
||||||
|
public OuterEvent(String eventType, InnerPayload payload) {
|
||||||
|
this.eventType = eventType;
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
public String getEventType() { return eventType; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class InnerPayload {
|
||||||
|
private String someData;
|
||||||
|
public InnerPayload(String someData) { this.someData = someData; }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_nested_constructor");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleNestedConstructors").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactly("CORRECT_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotExtractConstantsFromNestedArraysOfObjects() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleNestedArrays() {
|
||||||
|
// The event contains a nested array of objects. The heuristic should NOT extract WRONG_EVENT.
|
||||||
|
OuterEvent e = new OuterEvent("CORRECT_EVENT", new InnerPayload[] { new InnerPayload("WRONG_EVENT") });
|
||||||
|
service.process(e.getEventType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OuterEvent {
|
||||||
|
private String eventType;
|
||||||
|
private InnerPayload[] payloads;
|
||||||
|
public OuterEvent(String eventType, InnerPayload[] payloads) {
|
||||||
|
this.eventType = eventType;
|
||||||
|
this.payloads = payloads;
|
||||||
|
}
|
||||||
|
public String getEventType() { return eventType; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class InnerPayload {
|
||||||
|
private String someData;
|
||||||
|
public InnerPayload(String someData) { this.someData = someData; }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_nested_array");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleNestedArrays").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactly("CORRECT_EVENT");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user