diff --git a/.github/workflows/check-license-headers.yml b/.github/workflows/check-license-headers.yml deleted file mode 100644 index 67b1deefb..000000000 --- a/.github/workflows/check-license-headers.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Check License Lines -on: - push: - branches: '**' -# pull_request: -# branches: -# - main -jobs: - check-license-lines: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3 - - name: Check Copyright License - run: mvn license:check diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/MavenProject.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/MavenProject.java index 300488531..d2cc242a7 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/MavenProject.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/MavenProject.java @@ -129,8 +129,11 @@ public String getArtifactId() { return pomModel.getArtifactId(); } + /** + * FIXME: when the version of parent pom is null (inherited by it's parent) the version will be null. + */ public String getVersion() { - return pomModel.getVersion(); + return pomModel.getVersion() == null ? pomModel.getParent().getVersion() : pomModel.getVersion(); } @Override @@ -223,4 +226,8 @@ public Path getModulePath() { public ProjectId getProjectId() { return projectId; } + + public Object getProjectEncoding() { + return getPomModel().getProperties().get("project.build.sourceEncoding"); + } } diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ModuleParser.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ModuleParser.java index 96f57dc0e..106c48222 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ModuleParser.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ModuleParser.java @@ -18,12 +18,15 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.openrewrite.ExecutionContext; +import org.openrewrite.FileAttributes; import org.openrewrite.Parser; import org.openrewrite.SourceFile; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaParser; import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; import org.openrewrite.marker.Generated; import org.openrewrite.marker.Marker; import org.openrewrite.marker.Markers; @@ -31,13 +34,13 @@ import org.springframework.core.io.Resource; import org.springframework.sbm.utils.ResourceUtil; +import java.io.InputStream; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -73,7 +76,7 @@ public UnaryOperator addProvenance( /** * Parse Java sources and resources under {@code src/main} of current module. */ - public List processMainSources( + public SourceSetParsingResult processMainSources( Path baseDir, List resources, Xml.Document moduleBuildFile, @@ -116,20 +119,40 @@ public List processMainSources( javaParserBuilder.typeCache(typeCache); Iterable inputs = mainJavaSources.stream() - .map(r -> new Parser.Input(ResourceUtil.getPath(r), () -> ResourceUtil.getInputStream(r))) + .map(r -> { + FileAttributes fileAttributes = null; + Path path = ResourceUtil.getPath(r); + boolean isSynthetic = Files.exists(path); + Supplier inputStreamSupplier = () -> ResourceUtil.getInputStream(r); + Parser.Input input = new Parser.Input(path, fileAttributes, inputStreamSupplier, isSynthetic); + return input; + }) .toList(); - Stream cus = Stream.of(javaParserBuilder) - .map(JavaParser.Builder::build) - .flatMap(parser -> parser.parseInputs(inputs, baseDir, executionContext)) - .peek(s -> alreadyParsed.add(baseDir.resolve(s.getSourcePath()))); + Set localClassesCp = new HashSet<>(); + JavaSourceSet javaSourceSet = sourceSet("main", dependencies, typeCache); + List cus = javaParserBuilder.build() + .parseInputs(inputs, baseDir, executionContext) + .peek(s -> { + ((J.CompilationUnit)s).getClasses() + .stream() + .map(J.ClassDeclaration::getType) + .forEach(localClassesCp::add); + + alreadyParsed.add(baseDir.resolve(s.getSourcePath())); + }) + .toList(); + // TODO: This is a hack: + // Parsed java sources are not themselves on the classpath (here). + // The actual parsing happens when the stream is terminated (toList), + // therefore the toList() must be called before the parsed compilation units can be added to the classpath List mainProjectProvenance = new ArrayList<>(provenanceMarkers); - mainProjectProvenance.add(sourceSet("main", dependencies, typeCache)); + javaSourceSet = appendToClasspath(localClassesCp, javaSourceSet); + mainProjectProvenance.add(javaSourceSet); - // FIXME: 945 Why target and not all main? List parsedJavaPaths = javaSourcesInTarget.stream().map(ResourceUtil::getPath).toList(); - Stream parsedJava = cus.map(addProvenance(baseDir, mainProjectProvenance, parsedJavaPaths)); + Stream parsedJava = cus.stream().map(addProvenance(baseDir, mainProjectProvenance, parsedJavaPaths)); log.debug("[%s] Scanned %d java source files in main scope.".formatted(currentProject, mainJavaSources.size())); //Filter out any generated source files from the returned list, as we do not want to apply the recipe to the @@ -148,7 +171,22 @@ public List processMainSources( log.debug("[%s] Scanned %d resource files in main scope.".formatted(currentProject, (alreadyParsed.size() - sourcesParsedBefore))); // Any resources parsed from "main/resources" should also have the main source set added to them. sourceFiles.addAll(parsedResourceFiles); - return sourceFiles; + return new SourceSetParsingResult(sourceFiles, javaSourceSet.getClasspath()); + } + + /** + * Add entries that don't exist in the classpath of {@code javaSourceSet} from {@code appendingClasspath}. + */ + @NotNull + private static JavaSourceSet appendToClasspath(Set appendingClasspath, JavaSourceSet javaSourceSet) { + List curCp = javaSourceSet.getClasspath(); + appendingClasspath.forEach(f -> { + if(!curCp.contains(f)) { + curCp.add(f); + } + }); + javaSourceSet = javaSourceSet.withClasspath(new ArrayList<>(curCp)); + return javaSourceSet; } @NotNull @@ -160,7 +198,7 @@ private static JavaSourceSet sourceSet(String name, List dependencies, Jav /** * Parse Java sources and resource files under {@code src/test}. */ - public List processTestSources( + public SourceSetParsingResult processTestSources( Path baseDir, Xml.Document moduleBuildFile, JavaParser.Builder javaParserBuilder, @@ -169,8 +207,8 @@ public List processTestSources( Set alreadyParsed, ExecutionContext executionContext, MavenProject currentProject, - List resources - ) { + List resources, + List classpath) { log.info("Processing test sources in module '%s'".formatted(currentProject.getProjectId())); List testDependencies = currentProject.getTestClasspathElements(); @@ -186,14 +224,25 @@ public List processTestSources( .map(r -> new Parser.Input(ResourceUtil.getPath(r), () -> ResourceUtil.getInputStream(r))) .toList(); - Stream cus = Stream.of(javaParserBuilder) - .map(JavaParser.Builder::build) - .flatMap(parser -> parser.parseInputs(inputs, baseDir, executionContext)); + final List localClassesCp = new ArrayList<>(); + List cus = javaParserBuilder.build() + .parseInputs(inputs, baseDir, executionContext) + .peek(s -> { + ((J.CompilationUnit) s).getClasses() + .stream() + .map(J.ClassDeclaration::getType) + .forEach(localClassesCp::add); + alreadyParsed.add(baseDir.resolve(s.getSourcePath())); + }) + .toList(); List markers = new ArrayList<>(provenanceMarkers); - markers.add(sourceSet("test", testDependencies, typeCache)); - Stream parsedJava = cus.map(addProvenance(baseDir, markers, null)); + JavaSourceSet javaSourceSet = sourceSet("test", testDependencies, typeCache); + Set curClasspath = Stream.concat(classpath.stream(), localClassesCp.stream()).collect(Collectors.toSet()); + javaSourceSet = appendToClasspath(curClasspath, javaSourceSet); + markers.add(javaSourceSet); + Stream parsedJava = cus.stream().map(addProvenance(baseDir, markers, null)); log.debug("[%s] Scanned %d java source files in test scope.".formatted(currentProject, testJavaSources.size())); Stream sourceFiles = parsedJava; @@ -205,7 +254,7 @@ public List processTestSources( log.debug("[%s] Scanned %d resource files in test scope.".formatted(currentProject, (alreadyParsed.size() - sourcesParsedBefore))); sourceFiles = Stream.concat(sourceFiles, parsedResourceFiles); List result = sourceFiles.toList(); - return result; + return new SourceSetParsingResult(result, javaSourceSet.getClasspath()); } diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserContext.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserContext.java index 2e3b052f6..aa1893707 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserContext.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserContext.java @@ -79,4 +79,5 @@ private void addSourceFileToModel(Path baseDir, List sortedProject .filter(p -> ResourceUtil.getPath(p.getPomFile()).toString().equals(baseDir.resolve(s.getSourcePath()).toString())) .forEach(p -> p.setSourceFile(s)); } + } diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserProperties.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserProperties.java index 1aa8cdd67..22686b6b4 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserProperties.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserProperties.java @@ -19,6 +19,7 @@ import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; +import java.nio.file.Path; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -47,7 +48,7 @@ public class ParserProperties { /** * Directory used by RocksdbMavenPomCache when pomCacheEnabled is true */ - private String pomCacheDirectory; + private String pomCacheDirectory = Path.of(System.getProperty("user.home")).resolve(".rewrite-cache").toAbsolutePath().normalize().toString(); /** * Comma-separated list of patterns used to create PathMatcher diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ProjectScanner.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ProjectScanner.java index cb90eafb0..f5b688566 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ProjectScanner.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ProjectScanner.java @@ -85,6 +85,11 @@ private List filterIgnoredResources(Path baseDir, Resource[] resources List resultingResources = Stream.of(resources) .filter(r -> isAccepted(baseDir, r, pathMatchers)) .toList(); + + if(resultingResources.isEmpty()) { + throw new IllegalArgumentException("No resources were scanned. Check directory and ignore patterns."); + } + return resultingResources; } @@ -101,7 +106,8 @@ private boolean isAccepted(Path baseDir, Resource r, List pathMatch }) .findFirst(); if(isIgnored.isPresent() && log.isInfoEnabled()) { - log.info("Ignoring scanned resource '%s'.".formatted(baseDir.relativize(ResourceUtil.getPath(r)))); + Set ignoredPathPatterns = parserProperties.getIgnoredPathPatterns(); + log.info("Ignoring scanned resource '%s' given these path matchers: %s.".formatted(baseDir.relativize(ResourceUtil.getPath(r)), ignoredPathPatterns)); } return isIgnored.isEmpty(); } diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteParserConfiguration.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteParserConfiguration.java index 325c3533c..ba10f96cc 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteParserConfiguration.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteParserConfiguration.java @@ -17,6 +17,7 @@ import lombok.extern.slf4j.Slf4j; import org.openrewrite.ExecutionContext; +import org.openrewrite.maven.MavenExecutionContextView; import org.openrewrite.maven.cache.*; import org.openrewrite.maven.utilities.MavenArtifactDownloader; import org.openrewrite.tree.ParsingEventListener; @@ -54,14 +55,6 @@ @Import({ScanScope.class, ScopeConfiguration.class}) public class RewriteParserConfiguration { - @Autowired - private ParserProperties parserProperties; - -// @Bean -// ProvenanceMarkerFactory provenanceMarkerFactory(MavenMojoProjectParserFactory projectParserFactory) { -// return new ProvenanceMarkerFactory(projectParserFactory); -// } - @Bean MavenPasswordDecrypter mavenPasswordDecrypter() { return new MavenPasswordDecrypter(); diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteProjectParser.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteProjectParser.java index 3756f35d0..31f52e867 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteProjectParser.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteProjectParser.java @@ -21,6 +21,8 @@ import org.openrewrite.ExecutionContext; import org.openrewrite.SourceFile; import org.openrewrite.marker.Marker; +import org.openrewrite.maven.MavenExecutionContextView; +import org.openrewrite.maven.MavenSettings; import org.openrewrite.style.NamedStyles; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; @@ -35,6 +37,8 @@ import org.springframework.sbm.parsers.maven.ProvenanceMarkerFactory; import org.springframework.sbm.scopes.ScanScope; +import java.net.URI; +import java.nio.charset.Charset; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -117,7 +121,6 @@ public RewriteProjectParsingResult parse(Path givenBaseDir, List resou List parsedBuildFiles = buildFileParser.parseBuildFiles(baseDir, parserContext.getBuildFileResources(), parserContext.getActiveProfiles(), executionContext, parserProperties.isSkipMavenParsing(), provenanceMarkers); parserContext.setParsedBuildFiles(parsedBuildFiles); - log.trace("Start to parse %d source files in %d modules".formatted(resources.size() + parsedBuildFiles.size(), parsedBuildFiles.size())); List otherSourceFiles = sourceFileParser.parseOtherSourceFiles(baseDir, parserContext, resources, provenanceMarkers, styles, executionContext); diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/MavenMojoProjectParserPrivateMethods.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/SourceSetParsingResult.java similarity index 62% rename from sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/MavenMojoProjectParserPrivateMethods.java rename to sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/SourceSetParsingResult.java index 1237d34ce..cba9db43b 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/MavenMojoProjectParserPrivateMethods.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/SourceSetParsingResult.java @@ -13,22 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.sbm.parsers.maven; +package org.springframework.sbm.parsers; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.openrewrite.maven.utilities.MavenArtifactDownloader; +import org.openrewrite.SourceFile; +import org.openrewrite.java.tree.JavaType; + +import java.util.List; /** * @author Fabian Krüger */ -@Slf4j -@RequiredArgsConstructor -class MavenMojoProjectParserPrivateMethods { - - private final MavenMojoProjectParserFactory mavenMojoProjectParserFactory; - private final MavenArtifactDownloader artifactDownloader; - - - +public record SourceSetParsingResult(List sourceFiles, List classpath) { } diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/BuildFileParser.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/BuildFileParser.java index 466f1944a..74c3e70ad 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/BuildFileParser.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/BuildFileParser.java @@ -21,7 +21,9 @@ import org.openrewrite.Parser; import org.openrewrite.SourceFile; import org.openrewrite.marker.Marker; +import org.openrewrite.maven.MavenExecutionContextView; import org.openrewrite.maven.MavenParser; +import org.openrewrite.maven.MavenSettings; import org.openrewrite.xml.tree.Xml; import org.springframework.core.io.Resource; import org.springframework.sbm.utils.ResourceUtil; @@ -46,14 +48,14 @@ public class BuildFileParser { /** - * Parse a list of Maven Pom files to a Map of {@code Path} and their parsed {@link Xml.Document}s. - * The {@link Xml.Document}s are marked with {@link org.openrewrite.maven.tree.MavenResolutionResult} and the provided provenance markers. + * Parse a list of Maven Pom files to a {@code List} of {@link Xml.Document}s. + * The {@link Xml.Document}s get marked with {@link org.openrewrite.maven.tree.MavenResolutionResult} and the provided provenance markers. * * @param baseDir the {@link Path} to the root of the scanned project * @param buildFiles the list of resources for relevant pom files. - * @param activeProfiles teh active Maven profiles + * @param activeProfiles the active Maven profiles * @param executionContext the ExecutionContext to use -* * @param skipMavenParsing skip parsing Maven files + * @param skipMavenParsing skip parsing Maven files * @param provenanceMarkers the map of markers to be added */ public List parseBuildFiles( @@ -138,7 +140,10 @@ private Stream parsePoms(Path baseDir, List pomFiles, Ma } private void initializeMavenSettings(ExecutionContext executionContext) { - + // FIXME: https://github.com/spring-projects-experimental/spring-boot-migrator/issues/880 + String repo = "file://" + Path.of(System.getProperty("user.home")).resolve(".m2/repository") + "/"; + MavenSettings mavenSettings = new MavenSettings(repo, null, null, null, null); + MavenExecutionContextView.view(executionContext).setMavenSettings(mavenSettings); } public List filterAndSortBuildFiles(List resources) { diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenModuleParser.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenModuleParser.java index 82d2cac65..e4fea1538 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenModuleParser.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenModuleParser.java @@ -20,15 +20,15 @@ import org.openrewrite.ExecutionContext; import org.openrewrite.SourceFile; import org.openrewrite.java.JavaParser; +import org.openrewrite.java.tree.JavaType; import org.openrewrite.marker.Marker; import org.openrewrite.style.NamedStyles; +import org.openrewrite.tree.ParsingExecutionContextView; import org.openrewrite.xml.tree.Xml; import org.springframework.core.io.Resource; -import org.springframework.sbm.parsers.MavenProject; -import org.springframework.sbm.parsers.ModuleParser; -import org.springframework.sbm.parsers.ParserProperties; -import org.springframework.sbm.parsers.RewriteResourceParser; +import org.springframework.sbm.parsers.*; +import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.util.ArrayList; @@ -65,6 +65,10 @@ public List parseModuleSourceFiles( // if (mavenSourceEncoding != null) { // ParsingExecutionContextView.view(ctx).setCharset(Charset.forName(mavenSourceEncoding.toString())); // } + Object mavenSourceEncoding = currentProject.getProjectEncoding(); + if (mavenSourceEncoding != null) { + ParsingExecutionContextView.view(executionContext).setCharset(Charset.forName(mavenSourceEncoding.toString())); + } // 150:153 JavaParser.Builder javaParserBuilder = JavaParser.fromJavaVersion() @@ -90,8 +94,8 @@ public List parseModuleSourceFiles( Path moduleBuildFilePath = baseDir.resolve(moduleBuildFile.getSourcePath()); alreadyParsed.add(moduleBuildFilePath); alreadyParsed.addAll(skipResourceScanDirs); - List mainSources = parseMainSources(baseDir, currentProject, moduleBuildFile, resources, javaParserBuilder.clone(), rp, provenanceMarkers, alreadyParsed, executionContext); - List testSources = parseTestSources(baseDir, currentProject, moduleBuildFile, javaParserBuilder.clone(), rp, provenanceMarkers, alreadyParsed, executionContext, resources); + SourceSetParsingResult mainSourcesParsingResult = parseMainSources(baseDir, currentProject, moduleBuildFile, resources, javaParserBuilder.clone(), rp, provenanceMarkers, alreadyParsed, executionContext); + SourceSetParsingResult testSourcesParsingResult = parseTestSources(baseDir, currentProject, moduleBuildFile, javaParserBuilder.clone(), rp, provenanceMarkers, alreadyParsed, executionContext, resources, mainSourcesParsingResult.classpath()); // Collect the dirs of modules parsed in previous steps // parse other project resources @@ -99,7 +103,7 @@ public List parseModuleSourceFiles( // FIXME: handle generated sources .map(mavenMojoProjectParserPrivateMethods.addProvenance(baseDir, provenanceMarkers, null)); // 157:169 - List mainAndTestSources = mergeAndFilterExcluded(baseDir, parserProperties.getIgnoredPathPatterns(), mainSources, testSources); + List mainAndTestSources = mergeAndFilterExcluded(baseDir, parserProperties.getIgnoredPathPatterns(), mainSourcesParsingResult.sourceFiles(), testSourcesParsingResult.sourceFiles()); List resourceFilesList = parsedResourceFiles.toList(); sourceFiles.addAll(mainAndTestSources); sourceFiles.addAll(resourceFilesList); @@ -124,13 +128,14 @@ private static boolean isNotExcluded(Path baseDir, List exclusions, .noneMatch(pm -> pm.matches(baseDir.resolve(s.getSourcePath()).toAbsolutePath().normalize())); } - private List parseTestSources(Path baseDir, MavenProject mavenProject, Xml.Document moduleBuildFile, JavaParser.Builder javaParserBuilder, RewriteResourceParser rp, List provenanceMarkers, Set alreadyParsed, ExecutionContext executionContext, List resources) { - return mavenMojoProjectParserPrivateMethods.processTestSources(baseDir, moduleBuildFile, javaParserBuilder, rp, provenanceMarkers, alreadyParsed, executionContext, mavenProject, resources); + private SourceSetParsingResult parseTestSources(Path baseDir, MavenProject mavenProject, Xml.Document moduleBuildFile, JavaParser.Builder javaParserBuilder, RewriteResourceParser rp, List provenanceMarkers, Set alreadyParsed, ExecutionContext executionContext, List resources, List classpath) { + return mavenMojoProjectParserPrivateMethods.processTestSources(baseDir, moduleBuildFile, javaParserBuilder, rp, provenanceMarkers, alreadyParsed, executionContext, mavenProject, resources, classpath); } /** + * */ - private List parseMainSources(Path baseDir, MavenProject mavenProject, Xml.Document moduleBuildFile, List resources, JavaParser.Builder javaParserBuilder, RewriteResourceParser rp, List provenanceMarkers, Set alreadyParsed, ExecutionContext executionContext) { + private SourceSetParsingResult parseMainSources(Path baseDir, MavenProject mavenProject, Xml.Document moduleBuildFile, List resources, JavaParser.Builder javaParserBuilder, RewriteResourceParser rp, List provenanceMarkers, Set alreadyParsed, ExecutionContext executionContext) { // MavenMojoProjectParser#processMainSources(..) takes MavenProject // it reads from it: // - mavenProject.getBuild().getDirectory() diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProjectAnalyzer.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProjectAnalyzer.java index 30de7eb4e..e3f67b02e 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProjectAnalyzer.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProjectAnalyzer.java @@ -292,7 +292,11 @@ public String getModelVersion() { @Override public String getName() { - return delegate.getName(); + String name = delegate.getName(); + if(name == null) { + name = delegate.getArtifactId(); + } + return name; } @Override diff --git a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProvenanceMarkerFactory.java b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProvenanceMarkerFactory.java index 7de77d695..68cd8fe72 100644 --- a/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProvenanceMarkerFactory.java +++ b/sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/MavenProvenanceMarkerFactory.java @@ -100,7 +100,15 @@ public List generateProvenance(Path baseDir, MavenProject mavenProject) } BuildEnvironment buildEnvironment = BuildEnvironment.build(System::getenv); - return (List) Stream.of(buildEnvironment, this.gitProvenance(baseDir, buildEnvironment), OperatingSystemProvenance.current(), buildTool, new JavaVersion(Tree.randomId(), javaRuntimeVersion, javaVendor, sourceCompatibility, targetCompatibility), new JavaProject(Tree.randomId(), mavenProject.getName(), new JavaProject.Publication(mavenProject.getGroupId(), mavenProject.getArtifactId(), mavenProject.getVersion()))).filter(Objects::nonNull).collect(Collectors.toList()); + return (List) Stream.of( + buildEnvironment, + this.gitProvenance(baseDir, buildEnvironment), + OperatingSystemProvenance.current(), + buildTool, + new JavaVersion(Tree.randomId(), javaRuntimeVersion, javaVendor, sourceCompatibility, targetCompatibility), + new JavaProject(Tree.randomId(), mavenProject.getName(), new JavaProject.Publication(mavenProject.getGroupId(), mavenProject.getArtifactId(), mavenProject.getVersion()))) + .filter(Objects::nonNull) + .toList(); } private @Nullable GitProvenance gitProvenance(Path baseDir, @Nullable BuildEnvironment buildEnvironment) { diff --git a/sbm-support-rewrite/src/main/resources/META-INF/sbm-support-rewrite.properties b/sbm-support-rewrite/src/main/resources/META-INF/sbm-support-rewrite.properties index caaf7f4eb..f059bb0c0 100644 --- a/sbm-support-rewrite/src/main/resources/META-INF/sbm-support-rewrite.properties +++ b/sbm-support-rewrite/src/main/resources/META-INF/sbm-support-rewrite.properties @@ -1,19 +1,3 @@ -# -# Copyright 2021 - 2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - # Enable/Disable the MavenPomCache # With 'false' OpenRewrite's InMemoryMavenPomCache will be used. # With 'true' a composite cache of RocksdbMavenPomCache and InMemoryMavenPomCache will be used. @@ -27,4 +11,4 @@ parser.sizeThresholdMb=10 parser.runPerSubmodule=false parser.failOnInvalidActiveRecipes=true parser.activeProfiles=default -parser.ignoredPathPatterns=**.idea/**,**.git,**/target/**,target/** +parser.ignoredPathPatterns=**.idea/**,**.git/**,**/target/**,target/** diff --git a/sbm-support-rewrite/src/test/java/org/openrewrite/JavaParserTest.java b/sbm-support-rewrite/src/test/java/org/openrewrite/JavaParserTest.java new file mode 100644 index 000000000..73c255557 --- /dev/null +++ b/sbm-support-rewrite/src/test/java/org/openrewrite/JavaParserTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.internal.JavaTypeCache; +import org.openrewrite.java.internal.TypesInUse; +import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +import java.nio.file.Path; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Fabian Krüger + */ +public class JavaParserTest { + @Test + @DisplayName("shouldHaveTypeInUse") + @Disabled("Examination test") + void shouldHaveTypeInUse() { + + String localM2Repo = Path.of(System.getProperty("user.home")).resolve(".m2/repository").toString(); + List classpath = List.of( + Path.of(localM2Repo + "/org/springframework/boot/spring-boot-starter/3.1.1/spring-boot-starter-3.1.1.jar"), + Path.of(localM2Repo + "/org/springframework/boot/spring-boot/3.1.1/spring-boot-3.1.1.jar"), + Path.of(localM2Repo + "/org/springframework/spring-context/6.0.10/spring-context-6.0.10.jar"), + Path.of(localM2Repo + "/org/springframework/spring-aop/6.0.10/spring-aop-6.0.10.jar"), + Path.of(localM2Repo + "/org/springframework/spring-beans/6.0.10/spring-beans-6.0.10.jar"), + Path.of(localM2Repo + "/org/springframework/spring-expression/6.0.10/spring-expression-6.0.10.jar"), + Path.of(localM2Repo + "/org/springframework/boot/spring-boot-autoconfigure/3.1.1/spring-boot-autoconfigure-3.1.1.jar"), + Path.of(localM2Repo + "/org/springframework/boot/spring-boot-starter-logging/3.1.1/spring-boot-starter-logging-3.1.1.jar"), + Path.of(localM2Repo + "/ch/qos/logback/logback-classic/1.4.8/logback-classic-1.4.8.jar"), + Path.of(localM2Repo + "/ch/qos/logback/logback-core/1.4.8/logback-core-1.4.8.jar"), + Path.of(localM2Repo + "/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar"), + Path.of(localM2Repo + "/org/apache/logging/log4j/log4j-to-slf4j/2.20.0/log4j-to-slf4j-2.20.0.jar"), + Path.of(localM2Repo + "/org/apache/logging/log4j/log4j-api/2.20.0/log4j-api-2.20.0.jar"), + Path.of(localM2Repo + "/org/slf4j/jul-to-slf4j/2.0.7/jul-to-slf4j-2.0.7.jar"), + Path.of(localM2Repo + "/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar"), + Path.of(localM2Repo + "/org/springframework/spring-core/6.0.10/spring-core-6.0.10.jar"), + Path.of(localM2Repo + "/org/springframework/spring-jcl/6.0.10/spring-jcl-6.0.10.jar"), + Path.of(localM2Repo + "/org/yaml/snakeyaml/1.33/snakeyaml-1.33.jar") + ); + JavaTypeCache javaTypeCache = new JavaTypeCache(); + SourceFile sourceFile = JavaParser.fromJavaVersion().classpath(classpath).typeCache(javaTypeCache) + .build() + .parse(""" + package com.example; + import org.springframework.boot.SpringApplication; + import org.springframework.boot.autoconfigure.SpringBootApplication; + + @SpringBootApplication + public class MyMain { + public static void main(String[] args){ + SpringApplication.run(MyMain.class, args); + } + } + """) + .toList() + .get(0); + + J.CompilationUnit compilationUnit = (J.CompilationUnit) sourceFile; + List typesInUse = compilationUnit.getTypesInUse().getTypesInUse().stream().map(s -> s.toString()).toList(); + assertThat(typesInUse).contains("org.springframework.boot.SpringApplication", "org.springframework.boot.SpringApplication", "com.example.MyMain"); + JavaSourceSet main = JavaSourceSet.build("main", classpath, javaTypeCache, true); + List typesOnClasspath = main.getClasspath().stream().map(JavaType.FullyQualified::getFullyQualifiedName).toList(); + assertThat(typesOnClasspath).doesNotContain("com.example.MyMain"); // By design + + javaTypeCache.put("com.example.MyMain", sourceFile); + + main = JavaSourceSet.build("main", classpath, javaTypeCache, true); + List mainCp = main.getClasspath(); + TypesInUse typesInUseBefore = compilationUnit.getTypesInUse(); + + typesInUse = typesInUseBefore.getTypesInUse().stream().map(s -> s.toString()).toList(); + assertThat(typesInUse).contains("org.springframework.boot.SpringApplication", "org.springframework.boot.SpringApplication", "com.example.MyMain"); + compilationUnit.getClasses().stream().map(c -> c.getType()).forEach(mainCp::add); + + main = main.withClasspath(mainCp); + assertThat(main.getClasspath().stream().map(JavaType.FullyQualified::getFullyQualifiedName).toList()).contains("org.springframework.boot.SpringApplication"); + assertThat(main.getClasspath().stream().map(JavaType.FullyQualified::getFullyQualifiedName).toList()).contains("com.example.MyMain"); + } +} diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/CompareParserRecipeRunTest.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/CompareParserRecipeRunTest.java index 800e406fa..8c7002bbe 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/CompareParserRecipeRunTest.java +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/CompareParserRecipeRunTest.java @@ -33,6 +33,8 @@ import org.springframework.sbm.boot.autoconfigure.SbmSupportRewriteConfiguration; import org.springframework.sbm.parsers.maven.RewriteMavenProjectParser; import org.springframework.sbm.parsers.maven.SbmTestConfiguration; +import org.springframework.sbm.test.util.ParallelParsingResult; +import org.springframework.sbm.test.util.ParserExecutionHelper; import org.springframework.sbm.test.util.TestProjectHelper; import java.nio.file.Path; @@ -60,8 +62,9 @@ public class CompareParserRecipeRunTest { @DisplayName("Running a recipe with RewriteMavenParser should yield the same result as with RewriteProjectParser") void runningARecipeWithRewriteMavenParserYieldsTheSameResultAsWithRewriteProjectParser() { Path baseDir = TestProjectHelper.getMavenProject("parser-recipe-run"); - RewriteProjectParsingResult sutParsingResult = sut.parse(baseDir); - RewriteProjectParsingResult compParsingResult = comparingParser.parse(baseDir); + ParallelParsingResult parallelParsingResult = new ParserExecutionHelper().parseParallel(baseDir); + RewriteProjectParsingResult sutParsingResult = parallelParsingResult.testedParsingResult(); + RewriteProjectParsingResult compParsingResult = parallelParsingResult.comparingParsingResult(); AtomicInteger counter = new AtomicInteger(0); diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/ParserPropertiesTest.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/ParserPropertiesTest.java index d76276720..b7c9e6988 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/ParserPropertiesTest.java +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/ParserPropertiesTest.java @@ -87,7 +87,7 @@ void defaultActiveProfiles() { @Test @DisplayName("parser.ignoredPathPatterns") void defaultIgnoredPathPatterns() { - assertThat(parserProperties.getIgnoredPathPatterns()).containsExactlyInAnyOrder("**.idea/**", "**.git", "**/target/**", "target/**"); + assertThat(parserProperties.getIgnoredPathPatterns()).containsExactlyInAnyOrder("**.idea/**", "**.git/**", "**/target/**", "target/**"); } } diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/RewriteProjectParserIntegrationTest.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/RewriteProjectParserIntegrationTest.java index 5375546b6..fe24a3231 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/RewriteProjectParserIntegrationTest.java +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/RewriteProjectParserIntegrationTest.java @@ -24,6 +24,7 @@ import org.springframework.sbm.boot.autoconfigure.SbmSupportRewriteConfiguration; import org.springframework.sbm.parsers.maven.RewriteMavenProjectParser; import org.springframework.sbm.parsers.maven.SbmTestConfiguration; +import org.springframework.sbm.test.util.ParserParityTestHelper; import org.springframework.sbm.test.util.TestProjectHelper; import java.nio.file.Path; @@ -46,47 +47,41 @@ public class RewriteProjectParserIntegrationTest { @Autowired RewriteMavenProjectParser mavenProjectParser; - @Test - @DisplayName("parseCheckstyle") - void parseCheckstyle() { - Path baseDir = TestProjectHelper.getMavenProject("checkstyle"); - List resources = projectScanner.scan(baseDir); - RewriteProjectParsingResult parsingResult = sut.parse(baseDir, resources); - assertThat(parsingResult.sourceFiles().stream().map(sf -> sf.getSourcePath().toString()).toList()).contains("checkstyle/rules.xml"); - assertThat(parsingResult.sourceFiles().stream().map(sf -> sf.getSourcePath().toString()).toList()).contains("checkstyle/suppressions.xml"); - } - @Test @DisplayName("testFailingProject") - // FIXME: Succeeds with RewriteMavenProjectParser void testFailingProject() { Path baseDir = Path.of("./testcode/maven-projects/failing"); - RewriteProjectParsingResult parsingResult = sut.parse(baseDir); - assertThat(parsingResult.sourceFiles().get(1)).isInstanceOf(J.CompilationUnit.class); - J.CompilationUnit cu = (J.CompilationUnit) parsingResult.sourceFiles().get(1); - assertThat(cu.getTypesInUse().getTypesInUse().stream().map(t -> t.toString()).anyMatch(t -> t.equals("javax.validation.constraints.Min"))).isTrue(); + ParserParityTestHelper.scanProjectDir(baseDir) + .verifyParity((comparingParsingResult, testedParsingResult) -> { + assertThat(comparingParsingResult.sourceFiles().get(1)).isInstanceOf(J.CompilationUnit.class); + J.CompilationUnit cu = (J.CompilationUnit) comparingParsingResult.sourceFiles().get(1); + assertThat(cu.getTypesInUse().getTypesInUse().stream().map(t -> t.toString()).anyMatch(t -> t.equals("javax.validation.constraints.Min"))).isTrue(); + + assertThat(testedParsingResult.sourceFiles().get(1)).isInstanceOf(J.CompilationUnit.class); + J.CompilationUnit cu2 = (J.CompilationUnit) testedParsingResult.sourceFiles().get(1); + assertThat(cu2.getTypesInUse().getTypesInUse().stream().map(t -> t.toString()).anyMatch(t -> t.equals("javax.validation.constraints.Min"))).isTrue(); + }); } @Test @DisplayName("parseResources") void parseResources() { Path baseDir = TestProjectHelper.getMavenProject("resources"); - List resources = projectScanner.scan(baseDir); - - RewriteProjectParsingResult parsingResult = sut.parse(baseDir, resources); - assertThat(parsingResult.sourceFiles()).hasSize(5); + ParserParityTestHelper.scanProjectDir(baseDir) + .verifyParity((comparingParsingResult, testedParsingResult) -> { + assertThat(comparingParsingResult.sourceFiles()).hasSize(5); + }); } @Test @DisplayName("parse4Modules") void parse4Modules() { Path baseDir = TestProjectHelper.getMavenProject("4-modules"); - List resources = projectScanner.scan(baseDir); - - assertThat(resources).hasSize(4); - - RewriteProjectParsingResult parsingResult = sut.parse(baseDir, resources); - assertThat(parsingResult.sourceFiles()).hasSize(4); + ParserParityTestHelper.scanProjectDir(baseDir) + .verifyParity((comparingParsingResult, testedParsingResult) -> { + assertThat(comparingParsingResult.sourceFiles()).hasSize(4); + assertThat(testedParsingResult.sourceFiles()).hasSize(4); + }); } } diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/RewriteMavenProjectParserTest.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/RewriteProjectParserParityTest.java similarity index 70% rename from sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/RewriteMavenProjectParserTest.java rename to sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/RewriteProjectParserParityTest.java index 86c57e224..7d7107173 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/RewriteMavenProjectParserTest.java +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/RewriteProjectParserParityTest.java @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.sbm.parsers.maven; +package org.springframework.sbm.parsers; import org.intellij.lang.annotations.Language; -import org.junit.jupiter.api.BeforeEach; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junitpioneer.jupiter.Issue; -import org.mockito.Mockito; import org.openrewrite.ExecutionContext; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Parser; import org.openrewrite.SourceFile; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.marker.JavaProject; import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.marker.JavaVersion; @@ -40,25 +41,18 @@ import org.openrewrite.maven.MavenSettings; import org.openrewrite.maven.cache.CompositeMavenPomCache; import org.openrewrite.maven.tree.MavenResolutionResult; -import org.openrewrite.shaded.jgit.api.Git; import org.openrewrite.shaded.jgit.api.errors.GitAPIException; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; import org.openrewrite.xml.style.Autodetect; import org.openrewrite.xml.tree.Xml; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.FileSystemResourceLoader; -import org.springframework.core.io.Resource; -import org.springframework.sbm.parsers.*; -import org.springframework.sbm.parsers.events.RewriteParsingEventListenerAdapter; -import org.springframework.sbm.scopes.ScanScope; +import org.springframework.sbm.parsers.maven.ComparingParserFactory; +import org.springframework.sbm.parsers.maven.RewriteMavenProjectParser; +import org.springframework.sbm.test.util.TestProjectHelper; import org.springframework.sbm.test.util.DummyResource; -import org.springframework.sbm.utils.ResourceUtil; +import org.springframework.sbm.test.util.ParserParityTestHelper; import org.springframework.test.util.ReflectionTestUtils; -import java.io.File; import java.nio.charset.Charset; import java.nio.file.Path; import java.time.Duration; @@ -74,35 +68,16 @@ import static org.assertj.core.api.Fail.fail; import static org.mockito.Mockito.mock; + + /** + * Test parity between OpenRewrite parser logic and RewriteProjectParser. + * + * RewriteMavenProjectParser resembles the parser logic from OpenRewrite's Maven plugin + * * @author Fabian Krüger */ -class RewriteMavenProjectParserTest { - - - MavenExecutionRequestFactory requestFactory = new MavenExecutionRequestFactory( - new MavenConfigFileParser() - ); - MavenPlexusContainer plexusContainer = new MavenPlexusContainer(); - private ParserProperties parserProperties = new ParserProperties(); - private RewriteMavenProjectParser sut; - private ConfigurableListableBeanFactory beanFactory; - private ScanScope scanScope; - - @BeforeEach - void beforeEach() { - beanFactory = mock(ConfigurableListableBeanFactory.class); - scanScope = mock(ScanScope.class); - sut = new RewriteMavenProjectParser( - plexusContainer, - new RewriteParsingEventListenerAdapter(mock(ApplicationEventPublisher.class)), - new MavenExecutor(requestFactory, plexusContainer), - new MavenMojoProjectParserFactory(parserProperties), - scanScope, - beanFactory, - new InMemoryExecutionContext(t -> {throw new RuntimeException(t);}) - ); - } +class RewriteProjectParserParityTest { @Test @DisplayName("Parsing Simplistic Maven Project ") @@ -110,9 +85,7 @@ void parsingSimplisticMavenProject(@TempDir Path tempDir) throws GitAPIException @Language("xml") String pomXml = """ - + 4.0.0 org.example root-project @@ -158,23 +131,63 @@ public static void main(String[] args){ } """; - // init GIT repo to trigger adding of GitProvenance marker - Git.init().setDirectory(tempDir.toFile()).call(); - List resources = List.of( - new DummyResource(tempDir.resolve("src/main/java/com/example/MyMain.java"), javaClass), - new DummyResource(tempDir.resolve("pom.xml"), pomXml) - ); - ResourceUtil.write(tempDir, resources); - - parserProperties.setIgnoredPathPatterns(Set.of("**/testcode/**", "testcode/**", ".rewrite-cache/**")); - parserProperties.setPomCacheEnabled(true); // org.openrewrite.maven.pomCache will be CompositeMavenPomCache - // call SUT - RewriteProjectParsingResult parsingResult = sut.parse( - tempDir, - new InMemoryExecutionContext(t -> t.printStackTrace()) + List classpath = List.of( + Path.of("/Users/fkrueger/.m2/repository/org/springframework/boot/spring-boot-starter/3.1.1/spring-boot-starter-3.1.1.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/springframework/boot/spring-boot/3.1.1/spring-boot-3.1.1.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/springframework/spring-context/6.0.10/spring-context-6.0.10.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/springframework/spring-aop/6.0.10/spring-aop-6.0.10.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/springframework/spring-beans/6.0.10/spring-beans-6.0.10.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/springframework/spring-expression/6.0.10/spring-expression-6.0.10.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/3.1.1/spring-boot-autoconfigure-3.1.1.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/springframework/boot/spring-boot-starter-logging/3.1.1/spring-boot-starter-logging-3.1.1.jar"), + Path.of("/Users/fkrueger/.m2/repository/ch/qos/logback/logback-classic/1.4.8/logback-classic-1.4.8.jar"), + Path.of("/Users/fkrueger/.m2/repository/ch/qos/logback/logback-core/1.4.8/logback-core-1.4.8.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.20.0/log4j-to-slf4j-2.20.0.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/apache/logging/log4j/log4j-api/2.20.0/log4j-api-2.20.0.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/slf4j/jul-to-slf4j/2.0.7/jul-to-slf4j-2.0.7.jar"), + Path.of("/Users/fkrueger/.m2/repository/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/springframework/spring-core/6.0.10/spring-core-6.0.10.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/springframework/spring-jcl/6.0.10/spring-jcl-6.0.10.jar"), + Path.of("/Users/fkrueger/.m2/repository/org/yaml/snakeyaml/1.33/snakeyaml-1.33.jar") ); + JavaTypeCache javaTypeCache = new JavaTypeCache(); + SourceFile sourceFile = JavaParser.fromJavaVersion().classpath(classpath).typeCache(javaTypeCache) + .build() + .parse(javaClass) + .toList() + .get(0); + + JavaSourceSet.build("main", classpath, javaTypeCache, true); + + + TestProjectHelper.createTestProject(tempDir) + .withResources( + new DummyResource(tempDir.resolve("src/main/java/com/example/MyMain.java"), javaClass), + new DummyResource(tempDir.resolve("pom.xml"), pomXml) + ) + .initializeGitRepo() // trigger creation of GIT related marker + .writeToFilesystem(); + + ParserProperties comparingParserProperties = new ParserProperties(); + Set ignoredPathPatterns = Set.of("**/testcode/**", "testcode/**", ".rewrite-cache/**", "**/target/**", "**/.git/**"); + comparingParserProperties.setIgnoredPathPatterns(ignoredPathPatterns); + comparingParserProperties.setPomCacheEnabled(true); + + ParserParityTestHelper + .scanProjectDir(tempDir) + .withParserProperties(comparingParserProperties) + .parseSequentially() + .verifyParity(); + } + @NotNull + private static InMemoryExecutionContext createExecutionContext() { + return new InMemoryExecutionContext(t -> t.printStackTrace()); + } + + private void verifyParsingResult(RewriteProjectParsingResult parsingResult, ParserType parserType) { // Verify result List sourceFiles = parsingResult.sourceFiles(); assertThat(sourceFiles).isNotEmpty(); @@ -182,7 +195,7 @@ public static void main(String[] args){ SourceFile pom = sourceFiles.get(0); assertThat(pom).isInstanceOf(Xml.Document.class); int expectedNumMarkers = 7; - if(System.getenv("GITHUB_ACTION_REF") != null) { + if (System.getenv("GITHUB_ACTION_REF") != null) { expectedNumMarkers = 8; } assertThat(pom.getMarkers().getMarkers()).hasSize(expectedNumMarkers); @@ -232,11 +245,17 @@ public static void main(String[] args){ "java.math.BigInteger" ); - verifyExecutionContext(parsingResult); + verifyExecutionContext(parsingResult, parserType); + // TODO: Add test that uses Maven settings and encrypted passwords + } - Mockito.verify(scanScope).clear(beanFactory); + @Test + @DisplayName("Parse multi-module-1") + void parseMultiModule1() { + Path baseDir = getMavenProject("multi-module-1"); - // TODO: Add test that uses Maven settings and encrypted passwords + ParserParityTestHelper.scanProjectDir(baseDir) + .verifyParity(); } @Test @@ -244,65 +263,47 @@ public static void main(String[] args){ @Disabled("https://github.com/openrewrite/rewrite/issues/3409") void shouldParseMavenConfigProject() { Path baseDir = Path.of("./testcode/maven-projects/maven-config").toAbsolutePath().normalize(); + ParserProperties parserProperties = new ParserProperties(); parserProperties.setIgnoredPathPatterns(Set.of(".mvn")); - RewriteProjectParsingResult parsingResult = sut.parse( + RewriteMavenProjectParser mavenProjectParser = new ComparingParserFactory().createComparingParser(); + RewriteProjectParsingResult parsingResult = mavenProjectParser.parse( baseDir, new InMemoryExecutionContext(t -> fail(t.getMessage())) ); assertThat(parsingResult.sourceFiles()).hasSize(2); } - @Test - @DisplayName("Parse multi-module-1") - void parseMultiModule1_withIntegratedParser() { - ExecutionContext ctx = new InMemoryExecutionContext(t -> t.printStackTrace()); - Path baseDir = getMavenProject("multi-module-1"); - parserProperties.setIgnoredPathPatterns(Set.of("README.adoc")); - RewriteProjectParsingResult parsingResult = sut.parse( - baseDir, - ctx); - verifyMavenParser(parsingResult); - } @Test - void parseMultiModule1_WithCustomParser() { - Path baseDir = getMavenProject("multi-module-1"); - ModuleParser moduleParser = new ModuleParser(); - - RewriteProjectParser rpp = new RewriteProjectParser( - new ProvenanceMarkerFactory(new MavenProvenanceMarkerFactory()), - new BuildFileParser(), - new SourceFileParser(new MavenModuleParser(parserProperties, moduleParser)), - new StyleDetector(), - parserProperties, - mock(ParsingEventListener.class), - mock(ApplicationEventPublisher.class), - scanScope, - beanFactory, - new ProjectScanner(new DefaultResourceLoader(), parserProperties), - new RewriteExecutionContext(), - new MavenProjectAnalyzer(mock(RewriteMavenArtifactDownloader.class)) - ); - - Set ignoredPatters = Set.of(); - ProjectScanner projectScanner = new ProjectScanner(new FileSystemResourceLoader(), parserProperties); - List resources = projectScanner.scan(baseDir); - RewriteProjectParsingResult parsingResult1 = rpp.parse(baseDir, resources); - - verifyMavenParser(parsingResult1); - Mockito.verify(scanScope).clear(beanFactory); + @DisplayName("parseCheckstyle") + @Issue("https://github.com/spring-projects-experimental/spring-boot-migrator/issues/875") + void parseCheckstyle() { + Path baseDir = getMavenProject("checkstyle"); + ParserParityTestHelper.scanProjectDir(baseDir) + .verifyParity((comparingParsingResult, testedParsingResult) -> { + assertThat(comparingParsingResult.sourceFiles().stream().map(sf -> sf.getSourcePath().toString()).toList()).contains("checkstyle/rules.xml"); + assertThat(comparingParsingResult.sourceFiles().stream().map(sf -> sf.getSourcePath().toString()).toList()).contains("checkstyle/suppressions.xml"); + assertThat(testedParsingResult.sourceFiles().stream().map(sf -> sf.getSourcePath().toString()).toList()).contains("checkstyle/rules.xml"); + assertThat(testedParsingResult.sourceFiles().stream().map(sf -> sf.getSourcePath().toString()).toList()).contains("checkstyle/suppressions.xml"); + }); } @Test @DisplayName("Parse complex Maven reactor project") @Disabled("https://github.com/openrewrite/rewrite/issues/3409") void parseComplexMavenReactorProject() { - String target = "./testcode/maven-projects/cwa-server"; - cloneProject("https://github.com/corona-warn-app/cwa-server.git", target, "v3.2.0"); - Path projectRoot = Path.of(target).toAbsolutePath().normalize(); // SBM root - RewriteMavenProjectParser projectParser = sut; - ExecutionContext executionContext = new InMemoryExecutionContext(t -> t.printStackTrace()); + Path projectRoot = Path.of("./testcode/maven-projects/cwa-server").toAbsolutePath().normalize(); + TestProjectHelper.createTestProject(projectRoot) + .deleteDirIfExists() + .cloneGitProject("https://github.com/corona-warn-app/cwa-server.git") + .checkoutTag("v3.2.0") + .writeToFilesystem(); + + ParserProperties parserProperties = new ParserProperties(); + parserProperties.setIgnoredPathPatterns(Set.of(".rewrite/**", "internal/**")); + List parsedFiles = new ArrayList<>(); + ExecutionContext executionContext = createExecutionContext(); ParsingExecutionContextView.view(executionContext).setParsingListener(new ParsingEventListener() { @Override public void parsed(Parser.Input input, SourceFile sourceFile) { @@ -315,33 +316,18 @@ public void parsed(Parser.Input input, SourceFile sourceFile) { } }); - parserProperties.setIgnoredPathPatterns(Set.of("**/testcode/**", ".rewrite/**", "internal/**")); - RewriteProjectParsingResult parsingResult = projectParser.parse( - projectRoot, - executionContext - ); - - parsingResult.sourceFiles().stream() - .map(SourceFile::getSourcePath) - .forEach(System.out::println); + ParserParityTestHelper.scanProjectDir(projectRoot) + .parseSequentially() + .withExecutionContextForComparingParser(executionContext) + .withParserProperties(parserProperties) + .verifyParity(); } - @Test - @DisplayName("parseCheckstyle") - @Issue("https://github.com/spring-projects-experimental/spring-boot-migrator/issues/875") - void parseCheckstyle() { - Path baseDir = getMavenProject("checkstyle"); - RewriteProjectParsingResult parsingResult = sut.parse(baseDir); - assertThat(parsingResult.sourceFiles().stream().map(sf -> sf.getSourcePath().toString()).toList()).contains("checkstyle/rules.xml"); - assertThat(parsingResult.sourceFiles().stream().map(sf -> sf.getSourcePath().toString()).toList()).contains("checkstyle/suppressions.xml"); - } - - private static void verifyExecutionContext(RewriteProjectParsingResult parsingResult) { + private static void verifyExecutionContext(RewriteProjectParsingResult parsingResult, ParserType parserType) { ExecutionContext resultingExecutionContext = parsingResult.executionContext(); assertThat(resultingExecutionContext).isNotNull(); Map messages = (Map) ReflectionTestUtils.getField(resultingExecutionContext, "messages"); -// assertThat(messages).hasSize(10); // 1 assertThat( @@ -399,7 +385,7 @@ private static void verifyExecutionContext(RewriteProjectParsingResult parsingRe assertThat( MavenExecutionContextView.view(resultingExecutionContext).getLocalRepository().getUri() ).isEqualTo( - "file://" + Path.of(System.getProperty("user.home")).resolve(".m2").resolve("repository").toAbsolutePath().normalize().toString() + "/" + "file://" + Path.of(System.getProperty("user.home")).resolve(".m2").resolve("repository").toAbsolutePath().normalize() + "/" ); // 8 @@ -407,12 +393,9 @@ private static void verifyExecutionContext(RewriteProjectParsingResult parsingRe messages.get("org.openrewrite.maven.pomCache") ).isInstanceOf(CompositeMavenPomCache.class); assertThat(MavenExecutionContextView.view(resultingExecutionContext).getPomCache()).isInstanceOf(CompositeMavenPomCache.class); -// assertThat(MavenExecutionContextView.view(resultingExecutionContext).getPomCache()).isInstanceOf(CompositeMavenPomCache.class); // 9 - // FIXME: This fails sometimes when multiple tests are run together. The resolution time has been 0 and null - // The test succeeds with a useful resolution time when the test runs in isolation. - // The property is set by MavenArtifactDownloader (I think) and an unresolvable pom might ne the reason. + // This fails sometimes when multiple tests are run together. The resolution time has been 0 and null /*assertThat( messages.get("org.openrewrite.maven.resolutionTime") ).isEqualTo( @@ -430,25 +413,6 @@ private static void verifyExecutionContext(RewriteProjectParsingResult parsingRe assertThat(MavenExecutionContextView.view(resultingExecutionContext).getRepositories()).isEmpty(); } - private void cloneProject(String url, String target, String tag) { - File directory = Path.of(target).toFile(); - if (directory.exists()) { - return; - } - try { - Git git = Git.cloneRepository() - .setDirectory(directory) - .setURI(url) - .call(); - - git.checkout() - .setName("refs/tags/" + tag) - .call(); - - } catch (GitAPIException e) { - throw new RuntimeException(e); - } - } private void verifyMavenParser(RewriteProjectParsingResult parsingResult) { verify(parsingResult.sourceFiles().get(0), Xml.Document.class, "pom.xml", document -> { @@ -473,7 +437,7 @@ private void verify(SourceFile sourceFile, Class clazz if (Xml.Document.class == clazz) { Xml.Document pom = Xml.Document.class.cast(sourceFile); int numExpectedMarkers = 7; - if(System.getenv("GITHUB_ACTION_REF") != null) { + if (System.getenv("GITHUB_ACTION_REF") != null) { numExpectedMarkers = 8; } assertThat(pom.getMarkers().getMarkers()) @@ -481,7 +445,7 @@ private void verify(SourceFile sourceFile, Class clazz .hasSize(numExpectedMarkers); assertThat(pom.getMarkers().findFirst(MavenResolutionResult.class)).isPresent(); - if(System.getenv("GITHUB_ACTION_REF") != null) { + if (System.getenv("GITHUB_ACTION_REF") != null) { assertThat(pom.getMarkers().findFirst(GithubActionsBuildEnvironment.class)).isPresent(); } assertThat(pom.getMarkers().findFirst(GitProvenance.class)).isNotNull(); @@ -498,4 +462,8 @@ private Path getMavenProject(String s) { return Path.of("./testcode/maven-projects/").resolve(s).toAbsolutePath().normalize(); } + + private enum ParserType { + SBM, COMPARING + } } \ No newline at end of file diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/ComparingParserFactory.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/ComparingParserFactory.java new file mode 100644 index 000000000..37412ef36 --- /dev/null +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/ComparingParserFactory.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.sbm.parsers.maven; + +import org.jetbrains.annotations.NotNull; +import org.openrewrite.InMemoryExecutionContext; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.sbm.parsers.ParserProperties; +import org.springframework.sbm.parsers.events.RewriteParsingEventListenerAdapter; +import org.springframework.sbm.scopes.ScanScope; + +import static org.mockito.Mockito.mock; + +/** + * @author Fabian Krüger + */ +public class ComparingParserFactory { + @NotNull + public RewriteMavenProjectParser createComparingParser() { + return createComparingParser(new ParserProperties()); + } + + public RewriteMavenProjectParser createComparingParser(ParserProperties parserProperties) { + MavenPlexusContainer plexusContainer = new MavenPlexusContainer(); + ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class); + ScanScope scanScope = mock(ScanScope.class); + ApplicationEventPublisher eventPublisher = mock(ApplicationEventPublisher.class); + RewriteParsingEventListenerAdapter parsingListener = new RewriteParsingEventListenerAdapter(eventPublisher); + MavenExecutionRequestFactory requestFactory = new MavenExecutionRequestFactory(new MavenConfigFileParser()); + RewriteMavenProjectParser mavenProjectParser1 = new RewriteMavenProjectParser( + plexusContainer, + parsingListener, + new MavenExecutor(requestFactory, plexusContainer), + new MavenMojoProjectParserFactory(parserProperties), + scanScope, + beanFactory, + new InMemoryExecutionContext(t -> { + throw new RuntimeException(t); + }) + ); + return mavenProjectParser1; + } +} diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/MavenExecutionRequestFactory.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/MavenExecutionRequestFactory.java index 924dcd341..5f8159dae 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/MavenExecutionRequestFactory.java +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/MavenExecutionRequestFactory.java @@ -19,6 +19,7 @@ import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.repository.ArtifactRepositoryFactory; import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; +import org.apache.maven.artifact.repository.MavenArtifactRepository; import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout; import org.apache.maven.execution.DefaultMavenExecutionRequest; import org.apache.maven.execution.MavenExecutionRequest; @@ -75,6 +76,8 @@ public MavenExecutionRequest createMavenExecutionRequest(PlexusContainer plexusC userProperties.put("skipTests", "true"); request.setUserProperties(userProperties); + request.setRemoteRepositories(List.of(new MavenArtifactRepository("central", "https://repo.maven.apache.org/maven2", new DefaultRepositoryLayout(), new ArtifactRepositoryPolicy(true, "never", "warn"), new ArtifactRepositoryPolicy(true, "never", "warn")))); + // TODO: make profile configurable // fixes the maven run when plugins depending on Java version are encountered. // This is the case for some transitive dependencies when running against the SBM code base itself. diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/ProvenanceMarkerFactoryTest.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/ProvenanceMarkerFactoryTest.java index ecc9c6e07..2e0ab18f6 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/ProvenanceMarkerFactoryTest.java +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/ProvenanceMarkerFactoryTest.java @@ -94,8 +94,6 @@ void testMavenMojoProjectParserGenerateProvenance() { // and assert markers int numExpectedMarkers = 5; - System.out.println(System.getenv()); - if(System.getenv("GITHUB_ACTION_REF") != null) { numExpectedMarkers = 6; // CI marker } diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/SbmTestConfiguration.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/SbmTestConfiguration.java index 367cb5941..bba153f12 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/SbmTestConfiguration.java +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/parsers/maven/SbmTestConfiguration.java @@ -39,12 +39,6 @@ public class SbmTestConfiguration { @Autowired private ParserProperties parserProperties; - @Bean - MavenMojoProjectParserPrivateMethods mavenMojoProjectParserPrivateMethods(MavenMojoProjectParserFactory parserFactory, MavenArtifactDownloader artifactDownloader) { - return new MavenMojoProjectParserPrivateMethods(parserFactory, artifactDownloader); - } - - @Bean MavenConfigFileParser configFileParser() { return new MavenConfigFileParser(); diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/SetOrderTest.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/ParallelParsingResult.java similarity index 51% rename from sbm-support-rewrite/src/test/java/org/springframework/sbm/SetOrderTest.java rename to sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/ParallelParsingResult.java index 1b38ea30e..b709e06fc 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/SetOrderTest.java +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/ParallelParsingResult.java @@ -13,34 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.sbm; +package org.springframework.sbm.test.util; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.LinkedHashSet; -import java.util.Set; +import org.springframework.sbm.parsers.RewriteProjectParsingResult; /** * @author Fabian Krüger */ -public class SetOrderTest { - - @Test - @DisplayName("SetShouldKeppOrder") - void setShouldKeepOrder() { - Set orderedSet = new LinkedHashSet<>(); - orderedSet.add("A"); - orderedSet.add("B"); - orderedSet.add("C"); - - addElement(orderedSet); - orderedSet.stream().forEach(System.out::println); - } - - private Set addElement(Set orderedSet) { - orderedSet.add("D"); - return orderedSet; - } - +public record ParallelParsingResult(RewriteProjectParsingResult comparingParsingResult, RewriteProjectParsingResult testedParsingResult) { } diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/ParserExecutionHelper.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/ParserExecutionHelper.java new file mode 100644 index 000000000..969a52370 --- /dev/null +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/ParserExecutionHelper.java @@ -0,0 +1,101 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.sbm.test.util; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.sbm.boot.autoconfigure.SbmSupportRewriteConfiguration; +import org.springframework.sbm.parsers.ParserProperties; +import org.springframework.sbm.parsers.RewriteProjectParser; +import org.springframework.sbm.parsers.RewriteProjectParsingResult; +import org.springframework.sbm.parsers.maven.ComparingParserFactory; +import org.springframework.sbm.parsers.maven.RewriteMavenProjectParser; + +import java.nio.file.Path; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author Fabian Krüger + */ +public class ParserExecutionHelper { + + public ParallelParsingResult parseParallel(Path baseDir) { + return parseParallel(baseDir, new ParserProperties(), new InMemoryExecutionContext(t -> {throw new RuntimeException(t);})); + } + + public ParallelParsingResult parseParallel(Path baseDir, ParserProperties parserProperties) { + return parseParallel(baseDir, parserProperties, new InMemoryExecutionContext(t -> {throw new RuntimeException(t);})); + } + + public ParallelParsingResult parseParallel(Path baseDir, ExecutionContext executionContext) { + return parseParallel(baseDir, new ParserProperties(), executionContext); + } + + public ParallelParsingResult parseParallel(Path baseDir, ParserProperties parserProperties, ExecutionContext executionContext) { + try { + CountDownLatch latch = new CountDownLatch(2); + + ExecutorService threadPool = Executors.newFixedThreadPool(2); + + AtomicReference testedParsingResultRef = new AtomicReference<>(); + AtomicReference comparingParsingResultRef = new AtomicReference<>(); + + threadPool.submit(() -> { + System.out.println("Start parsing with RewriteProjectParser"); + RewriteProjectParsingResult tmpTestedParserResult = parseWithRewriteProjectParser(baseDir, parserProperties, executionContext); + testedParsingResultRef.set(tmpTestedParserResult); + latch.countDown(); + }); + + threadPool.submit(() -> { + System.out.println("Start parsing with RewriteMavenProjectParser"); + RewriteProjectParsingResult tmpComparingParserResult = parseWithComparingParser(baseDir, parserProperties, executionContext); + comparingParsingResultRef.set(tmpComparingParserResult); + latch.countDown(); + }); + + latch.await(); + return new ParallelParsingResult(comparingParsingResultRef.get(), testedParsingResultRef.get()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public RewriteProjectParsingResult parseWithComparingParser(Path baseDir, ParserProperties parserProperties, ExecutionContext executionContext) { + RewriteMavenProjectParser comparingParser = new ComparingParserFactory().createComparingParser(parserProperties); + if (executionContext != null) { + return comparingParser.parse(baseDir, executionContext); + } else { + return comparingParser.parse(baseDir); + } + } + + public RewriteProjectParsingResult parseWithRewriteProjectParser(Path baseDir, ParserProperties parserProperties, ExecutionContext executionContext) { + AtomicReference atomicRef = new AtomicReference<>(); + new ApplicationContextRunner().withUserConfiguration(SbmSupportRewriteConfiguration.class) + .withBean("parser-org.springframework.sbm.parsers.ParserProperties", ParserProperties.class, () -> parserProperties) + .run(appCtx -> { + RewriteProjectParser sut = appCtx.getBean(RewriteProjectParser.class); + RewriteProjectParsingResult testedParserResult = sut.parse(baseDir); + atomicRef.set(testedParserResult); + }); + return atomicRef.get(); + } +} diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/ParserParityTestHelper.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/ParserParityTestHelper.java new file mode 100644 index 000000000..509b6a5c8 --- /dev/null +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/ParserParityTestHelper.java @@ -0,0 +1,200 @@ +/* + * Copyright 2021 - 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.sbm.test.util; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.SourceFile; +import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.marker.Marker; +import org.openrewrite.marker.Markers; +import org.openrewrite.maven.MavenSettings; +import org.openrewrite.style.Style; +import org.springframework.sbm.parsers.ParserProperties; +import org.springframework.sbm.parsers.RewriteProjectParsingResult; + +import java.nio.file.Path; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Fabian Krüger + */ +public class ParserParityTestHelper { + private final Path baseDir; + private ParserProperties parserProperties = new ParserProperties(); + private boolean isParallelParse = true; + private ExecutionContext executionContext; + + public ParserParityTestHelper(Path baseDir) { + this.baseDir = baseDir; + } + + public static ParserParityTestHelper scanProjectDir(Path baseDir) { + ParserParityTestHelper helper = new ParserParityTestHelper(baseDir); + return helper; + } + + /** + * Sequentially parse given project using tested parser and then comparing parser. + * The parsers are executed in parallel by default. + */ + public ParserParityTestHelper parseSequentially() { + this.isParallelParse = false; + return this; + } + + public ParserParityTestHelper withParserProperties(ParserProperties parserProperties) { + this.parserProperties = parserProperties; + return this; + } + + public void verifyParity() { + verifyParity((comparingParsingResult, testedParsingResult) -> { + // nothing extra to verify + }); + } + + public void verifyParity(ParserResultParityChecker parserResultParityChecker) { + RewriteProjectParsingResult comparingParserResult = null; + RewriteProjectParsingResult testedParserResult = null; + + ParserExecutionHelper parserExecutionHelper = new ParserExecutionHelper(); + if (isParallelParse) { + ParallelParsingResult result = parserExecutionHelper.parseParallel(baseDir, parserProperties, executionContext); + comparingParserResult = result.comparingParsingResult(); + testedParserResult = result.testedParsingResult(); + } else { + testedParserResult = parserExecutionHelper.parseWithRewriteProjectParser(baseDir, parserProperties, executionContext); + comparingParserResult = parserExecutionHelper.parseWithComparingParser(baseDir, parserProperties, executionContext); + } + + + // Number of parsed sources should always be the same + assertThat(comparingParserResult.sourceFiles().size()) + .as(renderErrorMessage(comparingParserResult, testedParserResult)) + .isEqualTo(testedParserResult.sourceFiles().size()); + + // The paths of sources should be the same + List comparingResultPaths = comparingParserResult.sourceFiles().stream().map(sf -> baseDir.resolve(sf.getSourcePath()).toAbsolutePath().normalize().toString()).toList(); + List testedResultPaths = testedParserResult.sourceFiles().stream().map(sf -> baseDir.resolve(sf.getSourcePath()).toAbsolutePath().normalize().toString()).toList(); + assertThat(testedResultPaths).containsExactlyInAnyOrder(comparingResultPaths.toArray(String[]::new)); + + // The Markers of all resources should be the same + verifyMarkersAreTheSame(comparingParserResult, testedParserResult); + + parserResultParityChecker.accept(testedParserResult, comparingParserResult); + } + + private static String renderErrorMessage(RewriteProjectParsingResult comparingParserResult, RewriteProjectParsingResult testedParserResult) { + List collect = new ArrayList<>(); + if (comparingParserResult.sourceFiles().size() > testedParserResult.sourceFiles().size()) { + collect = comparingParserResult.sourceFiles().stream() + .filter(element -> !testedParserResult.sourceFiles().contains(element)) + .collect(Collectors.toList()); + } else { + collect = testedParserResult.sourceFiles().stream() + .filter(element -> !comparingParserResult.sourceFiles().contains(element)) + .collect(Collectors.toList()); + } + + return "ComparingParserResult had %d sourceFiles whereas TestedParserResult had %d sourceFiles. Files were %s".formatted(comparingParserResult.sourceFiles().size(), testedParserResult.sourceFiles().size(), collect); + } + + private void verifyMarkersAreTheSame(RewriteProjectParsingResult comparingParserResult, RewriteProjectParsingResult testedParserResult) { + List comparingSourceFiles = comparingParserResult.sourceFiles(); + List testedSourceFiles = testedParserResult.sourceFiles(); + + // bring to same order + comparingSourceFiles.sort(Comparator.comparing(SourceFile::getSourcePath)); + testedSourceFiles.sort(Comparator.comparing(SourceFile::getSourcePath)); + + for (SourceFile curComparingSourceFile : comparingSourceFiles) { + int index = comparingSourceFiles.indexOf(curComparingSourceFile); + SourceFile curTestedSourceFile = testedSourceFiles.get(index); + + Markers comparingMarkers = curComparingSourceFile.getMarkers(); + List comparingMarkersList = comparingMarkers.getMarkers(); + Markers testedMarkers = curTestedSourceFile.getMarkers(); + List testedMarkersList = testedMarkers.getMarkers(); + + assertThat(comparingMarkersList) + .usingRecursiveComparison() + .withStrictTypeChecking() + .ignoringCollectionOrder() + .ignoringFields( + // classpath compared further down + "classpath", + // FIXME: https://github.com/spring-projects-experimental/spring-boot-migrator/issues/982 + "styles" + ) + .ignoringFieldsOfTypes( + UUID.class, + // FIXME: https://github.com/spring-projects-experimental/spring-boot-migrator/issues/880 + MavenSettings.class, + // FIXME: https://github.com/spring-projects-experimental/spring-boot-migrator/issues/982 + Style.class) + .isEqualTo(testedMarkersList); + + if (curComparingSourceFile.getMarkers().findFirst(JavaSourceSet.class).isPresent()) { + // Tested parser must have JavaSourceSet marker when comparing parser has it + assertThat(testedMarkers.findFirst(JavaSourceSet.class)).isPresent(); + + // assert classpath equality + List comparingClasspath = comparingMarkers.findFirst(JavaSourceSet.class).get().getClasspath().stream().map(JavaType.FullyQualified::getFullyQualifiedName).toList(); + List testedClasspath = testedMarkers.findFirst(JavaSourceSet.class).get().getClasspath().stream().map(JavaType.FullyQualified::getFullyQualifiedName).toList(); + + assertThat(comparingClasspath.size()).isEqualTo(testedClasspath.size()); + + assertThat(comparingClasspath) + .withFailMessage(() -> { + List additionalElementsInComparingClasspath = comparingClasspath.stream() + .filter(element -> !testedClasspath.contains(element)) + .collect(Collectors.toList()); + + if (!additionalElementsInComparingClasspath.isEmpty()) { + return "Classpath of comparing and tested parser differ: comparing classpath contains additional entries: %s".formatted(additionalElementsInComparingClasspath); + } + + List additionalElementsInTestedClasspath = testedClasspath.stream() + .filter(element -> !comparingClasspath.contains(element)) + .collect(Collectors.toList()); + + if (!additionalElementsInTestedClasspath.isEmpty()) { + return "Classpath of comparing and tested parser differ: tested classpath contains additional entries: %s".formatted(additionalElementsInTestedClasspath); + } + + return "Bang!"; + }) + .containsExactlyInAnyOrder(testedClasspath.toArray(String[]::new)); + } + + } + } + + public ParserParityTestHelper withExecutionContextForComparingParser(ExecutionContext executionContext) { + this.executionContext = executionContext; + return this; + } + + public interface ParserResultParityChecker extends BiConsumer { + @Override + void accept(RewriteProjectParsingResult comparingParsingResult, RewriteProjectParsingResult testedParsingResult); + } +} diff --git a/sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/TestProjectHelper.java b/sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/TestProjectHelper.java index fa323de37..d8ea29dba 100644 --- a/sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/TestProjectHelper.java +++ b/sbm-support-rewrite/src/test/java/org/springframework/sbm/test/util/TestProjectHelper.java @@ -15,13 +15,103 @@ */ package org.springframework.sbm.test.util; +import org.apache.commons.io.FileUtils; +import org.openrewrite.shaded.jgit.api.Git; +import org.openrewrite.shaded.jgit.api.errors.GitAPIException; +import org.springframework.core.io.Resource; +import org.springframework.sbm.utils.ResourceUtil; + +import java.io.File; +import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** * @author Fabian Krüger */ public class TestProjectHelper { + private final Path targetDir; + private List resources = new ArrayList<>(); + private boolean initializeGitRepo; + private String gitUrl; + private String gitTag; + private boolean deleteDirIfExists = false; + + public TestProjectHelper(Path targetDir) { + this.targetDir = targetDir; + } + public static Path getMavenProject(String s) { return Path.of("./testcode/maven-projects/").resolve(s).toAbsolutePath().normalize(); } + + public static TestProjectHelper createTestProject(Path targetDir) { + return new TestProjectHelper(targetDir); + } + + public static TestProjectHelper createTestProject(String targetDir) { + return new TestProjectHelper(Path.of(targetDir).toAbsolutePath().normalize()); + } + + public TestProjectHelper withResources(Resource... resources) { + this.resources.addAll(Arrays.asList(resources)); + return this; + } + + public TestProjectHelper initializeGitRepo() { + this.initializeGitRepo = true; + return this; + } + + public TestProjectHelper cloneGitProject(String url) { + this.gitUrl = url; + return this; + } + + public TestProjectHelper checkoutTag(String tag) { + this.gitTag = tag; + return this; + } + + public TestProjectHelper deleteDirIfExists() { + this.deleteDirIfExists = true; + return this; + } + + public void writeToFilesystem() { + if(deleteDirIfExists) { + try { + FileUtils.deleteDirectory(targetDir.toFile()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + if (initializeGitRepo) { + try { + Git.init().setDirectory(targetDir.toFile()).call(); + } catch (GitAPIException e) { + throw new RuntimeException(e); + } + } else if (gitUrl != null) { + try { + File directory = targetDir.toFile(); + Git git = Git.cloneRepository() + .setDirectory(directory) + .setURI(this.gitUrl) + .call(); + + if (gitTag != null) { + git.checkout() + .setName("refs/tags/" + gitTag) + .call(); + } + } catch (GitAPIException e) { + throw new RuntimeException(e); + } + } + ResourceUtil.write(targetDir, resources); + } } diff --git a/sbm-support-rewrite/testcode/maven-projects/parser-recipe-run/pom.xml b/sbm-support-rewrite/testcode/maven-projects/parser-recipe-run/pom.xml index 88839c124..e4a244ee7 100644 --- a/sbm-support-rewrite/testcode/maven-projects/parser-recipe-run/pom.xml +++ b/sbm-support-rewrite/testcode/maven-projects/parser-recipe-run/pom.xml @@ -21,7 +21,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.17 + 2.7.16 com.example