Skip to content

Commit

Permalink
spring-projectsGH-31 Streamline state handling and move it to its own…
Browse files Browse the repository at this point in the history
… class
  • Loading branch information
davidbilge committed Sep 3, 2024
1 parent 653333b commit 21cf5fe
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-junit</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
package org.springframework.modulith;

import com.tngtech.archunit.core.domain.JavaClass;
import org.eclipse.jgit.api.errors.GitAPIException;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.test.context.AnnotatedClassFinder;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.modulith.core.ApplicationModule;
import org.springframework.modulith.core.ApplicationModuleDependency;
import org.springframework.modulith.core.ApplicationModules;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static org.springframework.modulith.FileModificationDetector.CLASS_FILE_SUFFIX;
import static org.springframework.modulith.FileModificationDetector.PACKAGE_PREFIX;

// add logging to explain what happens (and why)

/**
Expand All @@ -34,11 +25,10 @@
* @author Lukas Dohmen, David Bilge
*/
public class ModulithExecutionExtension implements ExecutionCondition {
private static final Logger log = LoggerFactory.getLogger(ModulithExecutionExtension.class);

public static final String CONFIG_PROPERTY_PREFIX = "spring.modulith.test";
public static final String PROJECT_ERROR = ModulithExecutionExtension.class.getName() + ".ERROR";
public static final String PROJECT_ID = ModulithExecutionExtension.class.getName();
final AnnotatedClassFinder spaClassFinder = new AnnotatedClassFinder(SpringBootApplication.class);
private static final Logger log = LoggerFactory.getLogger(ModulithExecutionExtension.class);

@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
Expand All @@ -47,22 +37,14 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con
return ConditionEvaluationResult.enabled("Enabled, only evaluating per class");
}

ExtensionContext.Store store = context.getRoot().getStore(Namespace.create(ModulithExecutionExtension.class));
this.writeChangedFilesToStore(store);
Exception e = store.get(PROJECT_ERROR, Exception.class);
if (e != null) {
log.error("ModulithExecutionExtension: Error while evaluating test execution", e);
return ConditionEvaluationResult.enabled(
"ModulithExecutionExtension: Error while evaluation test execution, enable Tests");
}

Set<Class<?>> modifiedFiles = (Set<Class<?>>) store.get(PROJECT_ID, Set.class);
if (modifiedFiles.isEmpty()) {
log.trace("No files changed, running tests");
StateStore stateStore = new StateStore(context);
Set<Class<?>> changedClasses = stateStore.getChangedClasses();
if (changedClasses.isEmpty()) {
log.trace("No class changes found, running tests");
return ConditionEvaluationResult.enabled("ModulithExecutionExtension: No changes detected");
}

log.trace("Found following changed files {}", modifiedFiles);
log.trace("Found following changed classes {}", changedClasses);

Optional<Class<?>> testClass = context.getTestClass();
if (testClass.isPresent()) {
Expand All @@ -83,15 +65,15 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con
Set<JavaClass> dependentClasses = getAllDependentClasses(optionalApplicationModule.get(),
applicationModules);

for (Class<?> modifiedFile : modifiedFiles) {
for (Class<?> changedClass : changedClasses) {

if (optionalApplicationModule.get().contains(modifiedFile)) {
if (optionalApplicationModule.get().contains(changedClass)) {
return ConditionEvaluationResult.enabled(
"ModulithExecutionExtension: Changes in module detected, Executing tests");
}

if (dependentClasses.stream()
.anyMatch(applicationModule -> applicationModule.isEquivalentTo(modifiedFile))) {
.anyMatch(applicationModule -> applicationModule.isEquivalentTo(changedClass))) {
return ConditionEvaluationResult.enabled(
"ModulithExecutionExtension: Changes in dependent module detected, Executing tests");
}
Expand All @@ -103,48 +85,11 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con
"ModulithExtension: No Changes detected in current module, executing tests");
}

public void writeChangedFilesToStore(ExtensionContext.Store store) {

var environment = new StandardEnvironment();
ConfigDataEnvironmentPostProcessor.applyTo(environment);

var strategy = FileModificationDetector.loadFileModificationDetector(environment);

store.getOrComputeIfAbsent(PROJECT_ID, s -> {
Set<Class<?>> changedClasses = new HashSet<>();
try {
Set<ModifiedFilePath> modifiedFiles = strategy.getModifiedFiles(environment);

Set<String> changedClassNames = modifiedFiles.stream()
.map(ModifiedFilePath::path)
.map(ClassUtils::convertResourcePathToClassName)
.filter(path -> path.contains(PACKAGE_PREFIX))
.filter(path -> path.endsWith(CLASS_FILE_SUFFIX))
.map(path -> path.substring(path.lastIndexOf(PACKAGE_PREFIX) + PACKAGE_PREFIX.length() + 1,
path.length() - CLASS_FILE_SUFFIX.length()))
.collect(Collectors.toSet());

for (String className : changedClassNames) {
try {
Class<?> aClass = ClassUtils.forName(className, null);
changedClasses.add(aClass);
} catch (ClassNotFoundException e) {
log.trace("ModulithExecutionExtension: Unable to find class \"{}\"", className);
}
}
return changedClasses;
} catch (IOException | GitAPIException e) {
log.error("ModulithExecutionExtension: Unable to fetch changed files, executing all tests", e);
store.put(PROJECT_ERROR, e);
return changedClasses;
}
});
}

private Set<JavaClass> getAllDependentClasses(ApplicationModule applicationModule,
ApplicationModules applicationModules) {

Set<ApplicationModule> dependentModules = Set.of(applicationModule);
Set<ApplicationModule> dependentModules = new HashSet<>();
dependentModules.add(applicationModule);
this.getDependentModules(applicationModule, applicationModules, dependentModules);

return dependentModules.stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.springframework.modulith;

import java.util.HashSet;
import java.util.Set;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.modulith.Change.JavaClassChange;
import org.springframework.modulith.Change.JavaTestClassChange;
import org.springframework.modulith.Change.OtherFileChange;
import org.springframework.util.ClassUtils;

class StateStore {
private static final Logger log = LoggerFactory.getLogger(StateStore.class);

private final ExtensionContext.Store store;

StateStore(ExtensionContext context) {
store = context.getRoot().getStore(Namespace.create(ModulithExecutionExtension.class));
}

Set<Class<?>> getChangedClasses() {
//noinspection unchecked
return (Set<Class<?>>) store.getOrComputeIfAbsent("changed-files", s -> {
var environment = new StandardEnvironment();
ConfigDataEnvironmentPostProcessor.applyTo(environment);

var detector = FileModificationDetector.loadFileModificationDetector(environment);
try {
Set<ModifiedFilePath> modifiedFiles = detector.getModifiedFiles(environment);
Set<Change> changes = Changes.toChanges(modifiedFiles);
return toChangedClasses(changes);
} catch (Exception e) {
log.error("ModulithExecutionExtension: Unable to fetch changed files, executing all tests", e);
return Set.of();
}
});
}

private static Set<Class<?>> toChangedClasses(Set<Change> changes) {
Set<Class<?>> changedClasses = new HashSet<>();
for (Change change : changes) {
if (change instanceof OtherFileChange) {
continue;
}

String className;
if (change instanceof JavaClassChange jcc) {
className = jcc.fullyQualifiedClassName();
} else if (change instanceof JavaTestClassChange jtcc) {
className = jtcc.fullyQualifiedClassName();
} else {
throw new IllegalStateException("Unexpected change type: " + change.getClass());
}

try {
Class<?> aClass = ClassUtils.forName(className, null);
changedClasses.add(aClass);
} catch (ClassNotFoundException e) {
log.trace("ModulithExecutionExtension: Unable to find class \"{}\"", className);
}
}
return changedClasses;
}
}

0 comments on commit 21cf5fe

Please sign in to comment.