Skip to content

Commit 54c2b6c

Browse files
committed
feat: record failure reasons in modernization metadata
1 parent 62ee6b7 commit 54c2b6c

3 files changed

Lines changed: 135 additions & 0 deletions

File tree

plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/extractor/ModernizationMetadata.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ public class ModernizationMetadata extends CacheEntry<ModernizationMetadata> {
8181
*/
8282
private String migrationStatus;
8383

84+
/**
85+
* Reasons why the migration failed, if any.
86+
* Null when migration succeeded (omitted from JSON).
87+
*/
88+
private List<String> failureReasons;
89+
8490
/**
8591
* Number of deprecated APIs removed by the migration
8692
*/
@@ -261,6 +267,14 @@ public void setMigrationStatus(String migrationStatus) {
261267
this.migrationStatus = migrationStatus;
262268
}
263269

270+
public List<String> getFailureReasons() {
271+
return failureReasons;
272+
}
273+
274+
public void setFailureReasons(List<String> failureReasons) {
275+
this.failureReasons = failureReasons;
276+
}
277+
264278
public String getJenkinsBaseline() {
265279
return jenkinsBaseline;
266280
}

plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/impl/PluginModernizer.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
import io.jenkins.tools.pluginmodernizer.core.model.JDK;
1111
import io.jenkins.tools.pluginmodernizer.core.model.ModernizerException;
1212
import io.jenkins.tools.pluginmodernizer.core.model.Plugin;
13+
import io.jenkins.tools.pluginmodernizer.core.model.PreconditionError;
1314
import io.jenkins.tools.pluginmodernizer.core.model.PluginProcessingException;
1415
import io.jenkins.tools.pluginmodernizer.core.model.RepoType;
1516
import io.jenkins.tools.pluginmodernizer.core.utils.PluginService;
1617
import io.jenkins.tools.pluginmodernizer.core.utils.StaticPomParser;
1718
import jakarta.inject.Inject;
19+
import java.util.ArrayList;
1820
import java.util.List;
1921
import java.util.Set;
2022
import org.slf4j.Logger;
@@ -534,6 +536,14 @@ private void collectModernizationMetadata(Plugin plugin) {
534536
modernizationMetadata.setChangedFiles(diffStats.changedFiles());
535537
if (plugin.hasErrors() || plugin.hasPreconditionErrors()) {
536538
modernizationMetadata.setMigrationStatus("fail");
539+
List<String> reasons = new ArrayList<>();
540+
plugin.getPreconditionErrors().stream()
541+
.map(PreconditionError::getError)
542+
.forEach(reasons::add);
543+
plugin.getErrors().stream()
544+
.map(Throwable::getMessage)
545+
.forEach(reasons::add);
546+
modernizationMetadata.setFailureReasons(reasons);
537547
} else {
538548
modernizationMetadata.setMigrationStatus("success");
539549
}

plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/impl/PluginModernizerTest.java

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
package io.jenkins.tools.pluginmodernizer.core.impl;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
46
import static org.junit.jupiter.api.Assertions.fail;
57
import static org.mockito.ArgumentMatchers.any;
68
import static org.mockito.ArgumentMatchers.eq;
79
import static org.mockito.Mockito.*;
810

911
import io.jenkins.tools.pluginmodernizer.core.config.Config;
1012
import io.jenkins.tools.pluginmodernizer.core.config.Settings;
13+
import io.jenkins.tools.pluginmodernizer.core.extractor.ModernizationMetadata;
14+
import io.jenkins.tools.pluginmodernizer.core.extractor.PluginMetadata;
1115
import io.jenkins.tools.pluginmodernizer.core.github.GHService;
16+
import io.jenkins.tools.pluginmodernizer.core.model.DiffStats;
1217
import io.jenkins.tools.pluginmodernizer.core.model.Plugin;
18+
import io.jenkins.tools.pluginmodernizer.core.model.PreconditionError;
19+
import io.jenkins.tools.pluginmodernizer.core.model.PluginProcessingException;
1320
import io.jenkins.tools.pluginmodernizer.core.model.PluginVersionData;
1421
import io.jenkins.tools.pluginmodernizer.core.model.Recipe;
1522
import io.jenkins.tools.pluginmodernizer.core.utils.PluginService;
@@ -18,9 +25,11 @@
1825
import java.nio.file.Paths;
1926
import java.util.Arrays;
2027
import java.util.List;
28+
import java.util.Set;
2129
import org.apache.maven.artifact.versioning.ComparableVersion;
2230
import org.junit.jupiter.api.BeforeEach;
2331
import org.junit.jupiter.api.Test;
32+
import org.mockito.ArgumentCaptor;
2433
import org.mockito.InjectMocks;
2534
import org.mockito.Mock;
2635
import org.mockito.MockitoAnnotations;
@@ -361,6 +370,108 @@ void testMetadataOperationsSkippedWhenModernizationMetadataIsNull() throws Excep
361370
verify(plugin, never()).openMetadataPullRequest(any());
362371
}
363372

373+
@Test
374+
void testCollectModernizationMetadata_WithPreconditionErrors_ShouldRecordFailureReasons() throws Exception {
375+
// Arrange
376+
Plugin plugin = mock(Plugin.class);
377+
PluginMetadata pluginMetadata = mock(PluginMetadata.class);
378+
Config pluginConfig = mock(Config.class);
379+
Recipe recipe = mock(Recipe.class);
380+
381+
when(plugin.getName()).thenReturn("test-plugin");
382+
when(plugin.getMetadata()).thenReturn(pluginMetadata);
383+
when(pluginMetadata.getPluginName()).thenReturn("test-plugin");
384+
when(pluginMetadata.getJenkinsVersion()).thenReturn(null);
385+
when(plugin.getJenkinsBaseline()).thenReturn("2.361");
386+
when(plugin.getJenkinsVersion()).thenReturn("2.361.1");
387+
when(plugin.getEffectiveBaseline()).thenReturn("2.361");
388+
when(plugin.getConfig()).thenReturn(pluginConfig);
389+
when(pluginConfig.getRecipe()).thenReturn(recipe);
390+
when(recipe.getDisplayName()).thenReturn("Test Recipe");
391+
when(recipe.getDescription()).thenReturn("Test description");
392+
when(recipe.getTags()).thenReturn(Set.of());
393+
when(recipe.getName()).thenReturn("test-recipe");
394+
when(plugin.getPullRequestUrl()).thenReturn(null);
395+
when(pluginService.extractVersion(plugin)).thenReturn("1.0");
396+
when(config.isDryRun()).thenReturn(false);
397+
when(plugin.getDiffStats(ghService, false)).thenReturn(new DiffStats(0, 0, 0));
398+
when(plugin.hasErrors()).thenReturn(false);
399+
when(plugin.hasPreconditionErrors()).thenReturn(true);
400+
when(plugin.getPreconditionErrors()).thenReturn(Set.of(PreconditionError.PARENT_POM_1X));
401+
when(plugin.getErrors()).thenReturn(List.of());
402+
// GHService is not needed here; the existing try-catch in collectModernizationMetadata handles this
403+
doThrow(new PluginProcessingException("no gh", plugin)).when(ghService).getRepository(any(), any());
404+
// Allow save() to proceed without NPE
405+
when(cacheManager.getLocation()).thenReturn(Path.of("target/test-cache"));
406+
407+
java.lang.reflect.Method method =
408+
PluginModernizer.class.getDeclaredMethod("collectModernizationMetadata", Plugin.class);
409+
method.setAccessible(true);
410+
411+
// Act
412+
method.invoke(pluginModernizer, plugin);
413+
414+
// Assert
415+
ArgumentCaptor<ModernizationMetadata> captor = ArgumentCaptor.forClass(ModernizationMetadata.class);
416+
verify(plugin).setModernizationMetadata(captor.capture());
417+
ModernizationMetadata saved = captor.getValue();
418+
419+
assertEquals("fail", saved.getMigrationStatus());
420+
assertNotNull(saved.getFailureReasons());
421+
assertTrue(saved.getFailureReasons().contains(PreconditionError.PARENT_POM_1X.getError()));
422+
}
423+
424+
@Test
425+
void testCollectModernizationMetadata_WithRuntimeErrors_ShouldRecordFailureReasons() throws Exception {
426+
// Arrange
427+
Plugin plugin = mock(Plugin.class);
428+
PluginMetadata pluginMetadata = mock(PluginMetadata.class);
429+
Config pluginConfig = mock(Config.class);
430+
Recipe recipe = mock(Recipe.class);
431+
432+
when(plugin.getName()).thenReturn("test-plugin");
433+
when(plugin.getMetadata()).thenReturn(pluginMetadata);
434+
when(pluginMetadata.getPluginName()).thenReturn("test-plugin");
435+
when(pluginMetadata.getJenkinsVersion()).thenReturn(null);
436+
when(plugin.getJenkinsBaseline()).thenReturn("2.361");
437+
when(plugin.getJenkinsVersion()).thenReturn("2.361.1");
438+
when(plugin.getEffectiveBaseline()).thenReturn("2.361");
439+
when(plugin.getConfig()).thenReturn(pluginConfig);
440+
when(pluginConfig.getRecipe()).thenReturn(recipe);
441+
when(recipe.getDisplayName()).thenReturn("Test Recipe");
442+
when(recipe.getDescription()).thenReturn("Test description");
443+
when(recipe.getTags()).thenReturn(Set.of());
444+
when(recipe.getName()).thenReturn("test-recipe");
445+
when(plugin.getPullRequestUrl()).thenReturn(null);
446+
when(pluginService.extractVersion(plugin)).thenReturn("1.0");
447+
when(config.isDryRun()).thenReturn(false);
448+
when(plugin.getDiffStats(ghService, false)).thenReturn(new DiffStats(0, 0, 0));
449+
when(plugin.hasErrors()).thenReturn(true);
450+
when(plugin.hasPreconditionErrors()).thenReturn(false);
451+
when(plugin.getPreconditionErrors()).thenReturn(Set.of());
452+
when(plugin.getErrors()).thenReturn(List.of(new PluginProcessingException("Build failed with code: 1", plugin)));
453+
// GHService is not needed here; the existing try-catch in collectModernizationMetadata handles this
454+
doThrow(new PluginProcessingException("no gh", plugin)).when(ghService).getRepository(any(), any());
455+
// Allow save() to proceed without NPE
456+
when(cacheManager.getLocation()).thenReturn(Path.of("target/test-cache"));
457+
458+
java.lang.reflect.Method method =
459+
PluginModernizer.class.getDeclaredMethod("collectModernizationMetadata", Plugin.class);
460+
method.setAccessible(true);
461+
462+
// Act
463+
method.invoke(pluginModernizer, plugin);
464+
465+
// Assert
466+
ArgumentCaptor<ModernizationMetadata> captor = ArgumentCaptor.forClass(ModernizationMetadata.class);
467+
verify(plugin).setModernizationMetadata(captor.capture());
468+
ModernizationMetadata saved = captor.getValue();
469+
470+
assertEquals("fail", saved.getMigrationStatus());
471+
assertNotNull(saved.getFailureReasons());
472+
assertTrue(saved.getFailureReasons().contains("Build failed with code: 1"));
473+
}
474+
364475
private Recipe createMockRecipe(String name, String description) {
365476
Recipe recipe = mock(Recipe.class);
366477
when(recipe.getName()).thenReturn(name);

0 commit comments

Comments
 (0)