Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArchUnits - get name of lambda method #1319

Open
dagmararaczak opened this issue Jun 13, 2024 · 1 comment
Open

ArchUnits - get name of lambda method #1319

dagmararaczak opened this issue Jun 13, 2024 · 1 comment

Comments

@dagmararaczak
Copy link

Hi,

Is it possible to do it in any way with ArchUnits ?

I have in one module classes whose names end with "Service" I would like to check if every public method from this is wrapped in different classes.

into something like this logger.loginfo("name of method").call(() → my service method) in some other method? Also, Logger class is in a different module.

So I would like to have something like check if my service method is called in the call method by loginfo.

@codecholeric
Copy link
Collaborator

I don't think what you want is completely possible, cause ArchUnit doesn't track any syntax tree. I.e. if one call is nested into another call in a lambda or something like that isn't really part of the available information. That being said, what you can test is that the origin of each call of a service has to be a lambda and you could also test that your loginfo is called in the same places. But that's of course just a heuristic. But maybe it's good enough (potentially with some fuzzy matching like the line number doesn't need to be an exact match or something like that 🤷). In any case, it's not the prettiest thing 😉

classes().that().haveSimpleNameEndingWith("Service")
  .should(new ArchCondition<JavaClass>("only be called through log infrastructure") {
    @Override
    public void check(JavaClass service, ConditionEvents events) {
      Set<JavaMethodCall> callsOfService = service.getMethodCallsToSelf();

      Set<JavaMethodCall> logInfoCalls = callsOfService.stream()
        .map(JavaMethodCall::getOrigin)
        .flatMap(clazz -> clazz.getMethodCallsFromSelf().stream())
        .filter(callFromSomeOrigin ->
          callFromSomeOrigin.getTargetOwner().isEquivalentTo(Loginfo.class) &&
            callFromSomeOrigin.getTarget().getName().equals("call")
        )
        .collect(toSet());

      Predicate<JavaMethodCall> existsLogInfoCallWithSameLineNumberAs = (JavaMethodCall call) ->
          logInfoCalls.stream().anyMatch(logInfoCall ->
            logInfoCall.getOrigin().equals(call.getOrigin()) &&
              logInfoCall.getLineNumber() == call.getLineNumber()
          );

      callsOfService.stream()
        .filter(call -> !call.isDeclaredInLambda() || !existsLogInfoCallWithSameLineNumberAs.test(call))
        .forEach(call -> events.add(SimpleConditionEvent.violated(call, call.getDescription())));
    }
  })

I assumed something like this:

class MyService {
  void action() {
  }
}

class MyOther {
  Logger logger;
  MyService myService;

  void callServiceOkay() {
    logger.loginfo("okay").call(() -> myService.action());
  }

  void callServiceWrong() {
    myService.action();
  }
}

class Logger {
  Loginfo loginfo(String info) {
    return new Loginfo();
  }
}

class Loginfo {
  void call(Runnable runnable) {
    runnable.run();
  }
}

You'd probably have to adjust it to your use case...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants