diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/engine/git/GitSupport.java b/components/sbm-core/src/main/java/org/springframework/sbm/engine/git/GitSupport.java index c06e3e331..087d66ee8 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/engine/git/GitSupport.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/engine/git/GitSupport.java @@ -107,7 +107,7 @@ public Commit commit(File repo, String message) { * * @param repo the location of the repo */ - public static Optional getLatestCommit(File repo) { + public Optional getLatestCommit(File repo) { try { Git git = getRepository(repo); Iterable revCommits = git.log() diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/Action.java b/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/Action.java index 1467aebe1..b6cc0bb16 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/Action.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/Action.java @@ -36,7 +36,11 @@ default void applyWithStatusEvent(ProjectContext context) { try { apply(context); } catch(Exception e) { - throw new ActionFailedException("'"+this.getDescription()+"' failed: " + e.getMessage(), e); + String message = "'" + this.getDescription() + "' failed: " + e.getMessage(); + if (eventPublisher != null) { + eventPublisher.publishEvent(new ActionFailedEvent(message)); + } + throw new ActionFailedException(message, e); } if (eventPublisher != null) { eventPublisher.publishEvent(new ActionFinishedEvent(getDescription())); diff --git a/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/OpenRewriteDeclarativeRecipeAdapter.java b/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/OpenRewriteDeclarativeRecipeAdapter.java index e102cca1a..dc76644a4 100644 --- a/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/OpenRewriteDeclarativeRecipeAdapter.java +++ b/components/sbm-core/src/main/java/org/springframework/sbm/engine/recipe/OpenRewriteDeclarativeRecipeAdapter.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.*; +import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Recipe; import org.openrewrite.Result; import org.openrewrite.SourceFile; @@ -71,7 +72,9 @@ public void apply(ProjectContext context) { } Recipe recipe = rewriteYamlRecipe.iterator().next(); List rewriteSourceFiles = context.search(new OpenRewriteSourceFilesFinder()); - List results = recipe.run(rewriteSourceFiles); + List results = recipe.run(rewriteSourceFiles, new InMemoryExecutionContext((t) -> { + throw new RuntimeException(t); + })); resultMerger.mergeResults(context, results); } diff --git a/components/sbm-core/src/test/java/org/springframework/sbm/engine/recipe/ActionTest.java b/components/sbm-core/src/test/java/org/springframework/sbm/engine/recipe/ActionTest.java new file mode 100644 index 000000000..ed87c4212 --- /dev/null +++ b/components/sbm-core/src/test/java/org/springframework/sbm/engine/recipe/ActionTest.java @@ -0,0 +1,75 @@ +package org.springframework.sbm.engine.recipe; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.sbm.engine.context.ProjectContext; +import org.springframework.sbm.engine.events.ActionFailedEvent; +import org.springframework.sbm.engine.events.ActionStartedEvent; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; + +@ExtendWith(MockitoExtension.class) +class ActionTest { + + @Spy + private ApplicationEventPublisher publisher; + + @Mock + ProjectContext projectContext; + + @InjectMocks + private TestActionImpl testAction; + + static class TestActionImpl implements Action { + private final ApplicationEventPublisher publisher; + + TestActionImpl(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + + @Override + public String getDescription() { + return "Test failing action"; + } + + @Override + public String getDetailedDescription() { + return null; + } + + @Override + public Condition getCondition() { + return null; + } + + @Override + public void apply(ProjectContext context) { + throw new RuntimeException("my exception"); + } + + @Override + public ApplicationEventPublisher getEventPublisher() { + return publisher; + } + + @Override + public boolean isAutomated() { + return false; + } + } + + @Test + void applyWithStatusEvent() { + assertThrows(RuntimeException.class, () -> testAction.applyWithStatusEvent(projectContext)); + + Mockito.verify(publisher).publishEvent(any(ActionStartedEvent.class)); + Mockito.verify(publisher).publishEvent(any(ActionFailedEvent.class)); + } +} diff --git a/components/sbm-core/src/test/java/org/springframework/sbm/engine/recipe/ErrorClass.java b/components/sbm-core/src/test/java/org/springframework/sbm/engine/recipe/ErrorClass.java new file mode 100644 index 000000000..3357a40a9 --- /dev/null +++ b/components/sbm-core/src/test/java/org/springframework/sbm/engine/recipe/ErrorClass.java @@ -0,0 +1,20 @@ +package org.springframework.sbm.engine.recipe; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.SourceFile; + +import java.util.List; + +public class ErrorClass extends org.openrewrite.Recipe { + + @Override + public String getDisplayName() { + return "NAME"; + } + + @Override + protected List visit(List before, ExecutionContext ctx) { + ctx.getOnError().accept(new RuntimeException("A problem happened whilst visiting")); + return super.visit(before, ctx); + } +} diff --git a/components/sbm-core/src/test/java/org/springframework/sbm/engine/recipe/OpenRewriteDeclarativeRecipeAdapterTest.java b/components/sbm-core/src/test/java/org/springframework/sbm/engine/recipe/OpenRewriteDeclarativeRecipeAdapterTest.java index f4065c1e8..52725cbe2 100644 --- a/components/sbm-core/src/test/java/org/springframework/sbm/engine/recipe/OpenRewriteDeclarativeRecipeAdapterTest.java +++ b/components/sbm-core/src/test/java/org/springframework/sbm/engine/recipe/OpenRewriteDeclarativeRecipeAdapterTest.java @@ -16,27 +16,23 @@ package org.springframework.sbm.engine.recipe; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; import org.junit.jupiter.api.Test; -import org.openrewrite.Result; -import org.openrewrite.config.YamlResourceLoader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.sbm.engine.context.ProjectContext; -import org.springframework.sbm.java.api.JavaSource; import org.springframework.sbm.project.RewriteSourceFileWrapper; import org.springframework.sbm.project.resource.ResourceHelper; import org.springframework.sbm.project.resource.TestProjectContext; +import org.springframework.stereotype.Component; import org.springframework.validation.beanvalidation.CustomValidatorBean; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.List; -import java.util.Properties; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; @SpringBootTest(classes = { RecipeParser.class, @@ -56,7 +52,7 @@ class OpenRewriteDeclarativeRecipeAdapterTest { @Test void recipeFromYaml() throws IOException { String yaml = - "- name: test-recipe\n" + + "- name: test-recipe\n" + " description: Replace deprecated spring.datasource.* properties\n" + " condition:\n" + " type: org.springframework.sbm.common.migration.conditions.TrueCondition\n" + @@ -91,4 +87,42 @@ void recipeFromYaml() throws IOException { "}" ); } -} \ No newline at end of file + + + @Test + public void propagateExceptionFromOpenRewriteRecipe() throws IOException { + + String actionDescription = + "- name: test-recipe\n" + + " description: Replace deprecated spring.datasource.* properties\n" + + " condition:\n" + + " type: org.springframework.sbm.common.migration.conditions.TrueCondition\n" + + " actions:\n" + + " - type: org.springframework.sbm.engine.recipe.OpenRewriteDeclarativeRecipeAdapter\n" + + " condition:\n" + + " type: org.springframework.sbm.common.migration.conditions.TrueCondition\n" + + " versionStartingWith: \"2.7.\"\n" + + " description: Add Spring Milestone Repository and bump parent pom to 3.0.0-M3\n" + + "\n" + + " openRewriteRecipe: |-\n" + + " type: specs.openrewrite.org/v1beta/recipe\n" + + " name: org.openrewrite.java.spring.boot3.data.UpgradeSpringData30\n" + + " displayName: Upgrade to Spring Data 3.0\n" + + " description: 'Upgrade to Spring Data to 3.0 from any prior version.'\n" + + " recipeList:\n" + + " - org.springframework.sbm.engine.recipe.ErrorClass\n"; + + Recipe[] recipes = recipeParser.parseRecipe(actionDescription); + assertThat(recipes[0].getActions().get(0)).isInstanceOf(OpenRewriteDeclarativeRecipeAdapter.class); + OpenRewriteDeclarativeRecipeAdapter recipeAdapter = (OpenRewriteDeclarativeRecipeAdapter) recipes[0].getActions().get(0); + + String javaSource = "@java.lang.Deprecated\n" + + "public class Foo {}"; + + ProjectContext context = TestProjectContext.buildProjectContext() + .addJavaSource("src/main/java", javaSource) + .build(); + + assertThrows(RuntimeException.class, () -> recipeAdapter.apply(context)); + } +} diff --git a/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_24_25/recipes/Boot_24_25_UpgradeReportRecipeTest.java b/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_24_25/recipes/Boot_24_25_UpgradeReportRecipeTest.java index 4dcc5836d..2fb72f0ee 100644 --- a/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_24_25/recipes/Boot_24_25_UpgradeReportRecipeTest.java +++ b/components/sbm-recipes-boot-upgrade/src/test/java/org/springframework/sbm/boot/upgrade_24_25/recipes/Boot_24_25_UpgradeReportRecipeTest.java @@ -15,15 +15,13 @@ */ package org.springframework.sbm.boot.upgrade_24_25.recipes; -import org.springframework.sbm.engine.git.Commit; import org.springframework.sbm.engine.git.GitSupport; +import org.springframework.sbm.project.resource.SbmApplicationProperties; import org.springframework.sbm.test.RecipeIntegrationTestSupport; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; - -import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -47,7 +45,8 @@ void generateReportTest() throws IOException { Path resultDir = RecipeIntegrationTestSupport.getResultDir(applicationDir); String generatedReportContent = replaceDynamicPartsInReport(resultDir.resolve("Upgrade-Spring-Boot-2.4-to-2.5.html"), resultDir); - String revision = GitSupport.getLatestCommit(resultDir.toAbsolutePath().toFile()).get().getHash(); + GitSupport gitSupport = new GitSupport(new SbmApplicationProperties()); + String revision = gitSupport.getLatestCommit(resultDir.toAbsolutePath().toFile()).get().getHash(); String expectedReport = getContent(new ClassPathResource("/expected-report").getFile().toPath()) @@ -70,5 +69,4 @@ private String getContent(Path report) throws IOException { Charset charset = StandardCharsets.UTF_8; return new String(Files.readAllBytes(report), charset).replaceAll("\n$", ""); } - }