Skip to content

Commit d9c9bdb

Browse files
authored
Parser parity test (#984)
- Introduce `ParserParityTestHelper` which takes a dir and parses the given project with `RewriteMavenProjectParser` (comparing) and `RewritePropjectParser` (system under test). The parsing result is then compared using AssertJ's `usingRecursiveComparison` which verifies that all markers and their fields of both parsing results are equal proving that both parsers yield the same result. - Introduce ´TestProjectHelper` which helps set up test projects. - Fixes multiple bugs, e.g. calculation of the classpath. - Cleanups and refactorings
1 parent f16447e commit d9c9bdb

28 files changed

+843
-309
lines changed

.github/workflows/check-license-headers.yml

-14
This file was deleted.

sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/MavenProject.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,11 @@ public String getArtifactId() {
129129
return pomModel.getArtifactId();
130130
}
131131

132+
/**
133+
* FIXME: when the version of parent pom is null (inherited by it's parent) the version will be null.
134+
*/
132135
public String getVersion() {
133-
return pomModel.getVersion();
136+
return pomModel.getVersion() == null ? pomModel.getParent().getVersion() : pomModel.getVersion();
134137
}
135138

136139
@Override
@@ -223,4 +226,8 @@ public Path getModulePath() {
223226
public ProjectId getProjectId() {
224227
return projectId;
225228
}
229+
230+
public Object getProjectEncoding() {
231+
return getPomModel().getProperties().get("project.build.sourceEncoding");
232+
}
226233
}

sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ModuleParser.java

+72-23
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,29 @@
1818
import lombok.extern.slf4j.Slf4j;
1919
import org.jetbrains.annotations.NotNull;
2020
import org.openrewrite.ExecutionContext;
21+
import org.openrewrite.FileAttributes;
2122
import org.openrewrite.Parser;
2223
import org.openrewrite.SourceFile;
2324
import org.openrewrite.internal.lang.Nullable;
2425
import org.openrewrite.java.JavaParser;
2526
import org.openrewrite.java.internal.JavaTypeCache;
2627
import org.openrewrite.java.marker.JavaSourceSet;
28+
import org.openrewrite.java.tree.J;
29+
import org.openrewrite.java.tree.JavaType;
2730
import org.openrewrite.marker.Generated;
2831
import org.openrewrite.marker.Marker;
2932
import org.openrewrite.marker.Markers;
3033
import org.openrewrite.xml.tree.Xml;
3134
import org.springframework.core.io.Resource;
3235
import org.springframework.sbm.utils.ResourceUtil;
3336

37+
import java.io.InputStream;
38+
import java.nio.file.Files;
3439
import java.nio.file.Path;
3540
import java.nio.file.Paths;
36-
import java.util.ArrayList;
37-
import java.util.Collection;
38-
import java.util.List;
39-
import java.util.Set;
41+
import java.util.*;
4042
import java.util.function.Predicate;
43+
import java.util.function.Supplier;
4144
import java.util.function.UnaryOperator;
4245
import java.util.stream.Collectors;
4346
import java.util.stream.Stream;
@@ -73,7 +76,7 @@ public <T extends SourceFile> UnaryOperator<T> addProvenance(
7376
/**
7477
* Parse Java sources and resources under {@code src/main} of current module.
7578
*/
76-
public List<SourceFile> processMainSources(
79+
public SourceSetParsingResult processMainSources(
7780
Path baseDir,
7881
List<Resource> resources,
7982
Xml.Document moduleBuildFile,
@@ -116,20 +119,40 @@ public List<SourceFile> processMainSources(
116119
javaParserBuilder.typeCache(typeCache);
117120

118121
Iterable<Parser.Input> inputs = mainJavaSources.stream()
119-
.map(r -> new Parser.Input(ResourceUtil.getPath(r), () -> ResourceUtil.getInputStream(r)))
122+
.map(r -> {
123+
FileAttributes fileAttributes = null;
124+
Path path = ResourceUtil.getPath(r);
125+
boolean isSynthetic = Files.exists(path);
126+
Supplier<InputStream> inputStreamSupplier = () -> ResourceUtil.getInputStream(r);
127+
Parser.Input input = new Parser.Input(path, fileAttributes, inputStreamSupplier, isSynthetic);
128+
return input;
129+
})
120130
.toList();
121131

122-
Stream<? extends SourceFile> cus = Stream.of(javaParserBuilder)
123-
.map(JavaParser.Builder::build)
124-
.flatMap(parser -> parser.parseInputs(inputs, baseDir, executionContext))
125-
.peek(s -> alreadyParsed.add(baseDir.resolve(s.getSourcePath())));
132+
Set<JavaType.FullyQualified> localClassesCp = new HashSet<>();
133+
JavaSourceSet javaSourceSet = sourceSet("main", dependencies, typeCache);
134+
List<? extends SourceFile> cus = javaParserBuilder.build()
135+
.parseInputs(inputs, baseDir, executionContext)
136+
.peek(s -> {
137+
((J.CompilationUnit)s).getClasses()
138+
.stream()
139+
.map(J.ClassDeclaration::getType)
140+
.forEach(localClassesCp::add);
141+
142+
alreadyParsed.add(baseDir.resolve(s.getSourcePath()));
143+
})
144+
.toList();
126145

146+
// TODO: This is a hack:
147+
// Parsed java sources are not themselves on the classpath (here).
148+
// The actual parsing happens when the stream is terminated (toList),
149+
// therefore the toList() must be called before the parsed compilation units can be added to the classpath
127150
List<Marker> mainProjectProvenance = new ArrayList<>(provenanceMarkers);
128-
mainProjectProvenance.add(sourceSet("main", dependencies, typeCache));
151+
javaSourceSet = appendToClasspath(localClassesCp, javaSourceSet);
152+
mainProjectProvenance.add(javaSourceSet);
129153

130-
// FIXME: 945 Why target and not all main?
131154
List<Path> parsedJavaPaths = javaSourcesInTarget.stream().map(ResourceUtil::getPath).toList();
132-
Stream<SourceFile> parsedJava = cus.map(addProvenance(baseDir, mainProjectProvenance, parsedJavaPaths));
155+
Stream<SourceFile> parsedJava = cus.stream().map(addProvenance(baseDir, mainProjectProvenance, parsedJavaPaths));
133156
log.debug("[%s] Scanned %d java source files in main scope.".formatted(currentProject, mainJavaSources.size()));
134157

135158
//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<SourceFile> processMainSources(
148171
log.debug("[%s] Scanned %d resource files in main scope.".formatted(currentProject, (alreadyParsed.size() - sourcesParsedBefore)));
149172
// Any resources parsed from "main/resources" should also have the main source set added to them.
150173
sourceFiles.addAll(parsedResourceFiles);
151-
return sourceFiles;
174+
return new SourceSetParsingResult(sourceFiles, javaSourceSet.getClasspath());
175+
}
176+
177+
/**
178+
* Add entries that don't exist in the classpath of {@code javaSourceSet} from {@code appendingClasspath}.
179+
*/
180+
@NotNull
181+
private static JavaSourceSet appendToClasspath(Set<JavaType.FullyQualified> appendingClasspath, JavaSourceSet javaSourceSet) {
182+
List<JavaType.FullyQualified> curCp = javaSourceSet.getClasspath();
183+
appendingClasspath.forEach(f -> {
184+
if(!curCp.contains(f)) {
185+
curCp.add(f);
186+
}
187+
});
188+
javaSourceSet = javaSourceSet.withClasspath(new ArrayList<>(curCp));
189+
return javaSourceSet;
152190
}
153191

154192
@NotNull
@@ -160,7 +198,7 @@ private static JavaSourceSet sourceSet(String name, List<Path> dependencies, Jav
160198
/**
161199
* Parse Java sources and resource files under {@code src/test}.
162200
*/
163-
public List<SourceFile> processTestSources(
201+
public SourceSetParsingResult processTestSources(
164202
Path baseDir,
165203
Xml.Document moduleBuildFile,
166204
JavaParser.Builder<? extends JavaParser, ?> javaParserBuilder,
@@ -169,8 +207,8 @@ public List<SourceFile> processTestSources(
169207
Set<Path> alreadyParsed,
170208
ExecutionContext executionContext,
171209
MavenProject currentProject,
172-
List<Resource> resources
173-
) {
210+
List<Resource> resources,
211+
List<JavaType.FullyQualified> classpath) {
174212
log.info("Processing test sources in module '%s'".formatted(currentProject.getProjectId()));
175213

176214
List<Path> testDependencies = currentProject.getTestClasspathElements();
@@ -186,14 +224,25 @@ public List<SourceFile> processTestSources(
186224
.map(r -> new Parser.Input(ResourceUtil.getPath(r), () -> ResourceUtil.getInputStream(r)))
187225
.toList();
188226

189-
Stream<? extends SourceFile> cus = Stream.of(javaParserBuilder)
190-
.map(JavaParser.Builder::build)
191-
.flatMap(parser -> parser.parseInputs(inputs, baseDir, executionContext));
227+
final List<JavaType.FullyQualified> localClassesCp = new ArrayList<>();
228+
List<? extends SourceFile> cus = javaParserBuilder.build()
229+
.parseInputs(inputs, baseDir, executionContext)
230+
.peek(s -> {
231+
((J.CompilationUnit) s).getClasses()
232+
.stream()
233+
.map(J.ClassDeclaration::getType)
234+
.forEach(localClassesCp::add);
235+
alreadyParsed.add(baseDir.resolve(s.getSourcePath()));
236+
})
237+
.toList();
192238

193239
List<Marker> markers = new ArrayList<>(provenanceMarkers);
194-
markers.add(sourceSet("test", testDependencies, typeCache));
195240

196-
Stream<SourceFile> parsedJava = cus.map(addProvenance(baseDir, markers, null));
241+
JavaSourceSet javaSourceSet = sourceSet("test", testDependencies, typeCache);
242+
Set<JavaType.FullyQualified> curClasspath = Stream.concat(classpath.stream(), localClassesCp.stream()).collect(Collectors.toSet());
243+
javaSourceSet = appendToClasspath(curClasspath, javaSourceSet);
244+
markers.add(javaSourceSet);
245+
Stream<SourceFile> parsedJava = cus.stream().map(addProvenance(baseDir, markers, null));
197246

198247
log.debug("[%s] Scanned %d java source files in test scope.".formatted(currentProject, testJavaSources.size()));
199248
Stream<SourceFile> sourceFiles = parsedJava;
@@ -205,7 +254,7 @@ public List<SourceFile> processTestSources(
205254
log.debug("[%s] Scanned %d resource files in test scope.".formatted(currentProject, (alreadyParsed.size() - sourcesParsedBefore)));
206255
sourceFiles = Stream.concat(sourceFiles, parsedResourceFiles);
207256
List<SourceFile> result = sourceFiles.toList();
208-
return result;
257+
return new SourceSetParsingResult(result, javaSourceSet.getClasspath());
209258
}
210259

211260

sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserContext.java

+1
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,5 @@ private void addSourceFileToModel(Path baseDir, List<MavenProject> sortedProject
7979
.filter(p -> ResourceUtil.getPath(p.getPomFile()).toString().equals(baseDir.resolve(s.getSourcePath()).toString()))
8080
.forEach(p -> p.setSourceFile(s));
8181
}
82+
8283
}

sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ParserProperties.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import lombok.Setter;
2020
import org.springframework.boot.context.properties.ConfigurationProperties;
2121

22+
import java.nio.file.Path;
2223
import java.util.HashSet;
2324
import java.util.List;
2425
import java.util.Set;
@@ -47,7 +48,7 @@ public class ParserProperties {
4748
/**
4849
* Directory used by RocksdbMavenPomCache when pomCacheEnabled is true
4950
*/
50-
private String pomCacheDirectory;
51+
private String pomCacheDirectory = Path.of(System.getProperty("user.home")).resolve(".rewrite-cache").toAbsolutePath().normalize().toString();
5152

5253
/**
5354
* Comma-separated list of patterns used to create PathMatcher

sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/ProjectScanner.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ private List<Resource> filterIgnoredResources(Path baseDir, Resource[] resources
8585
List<Resource> resultingResources = Stream.of(resources)
8686
.filter(r -> isAccepted(baseDir, r, pathMatchers))
8787
.toList();
88+
89+
if(resultingResources.isEmpty()) {
90+
throw new IllegalArgumentException("No resources were scanned. Check directory and ignore patterns.");
91+
}
92+
8893
return resultingResources;
8994
}
9095

@@ -101,7 +106,8 @@ private boolean isAccepted(Path baseDir, Resource r, List<PathMatcher> pathMatch
101106
})
102107
.findFirst();
103108
if(isIgnored.isPresent() && log.isInfoEnabled()) {
104-
log.info("Ignoring scanned resource '%s'.".formatted(baseDir.relativize(ResourceUtil.getPath(r))));
109+
Set<String> ignoredPathPatterns = parserProperties.getIgnoredPathPatterns();
110+
log.info("Ignoring scanned resource '%s' given these path matchers: %s.".formatted(baseDir.relativize(ResourceUtil.getPath(r)), ignoredPathPatterns));
105111
}
106112
return isIgnored.isEmpty();
107113
}

sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteParserConfiguration.java

+1-8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import lombok.extern.slf4j.Slf4j;
1919
import org.openrewrite.ExecutionContext;
20+
import org.openrewrite.maven.MavenExecutionContextView;
2021
import org.openrewrite.maven.cache.*;
2122
import org.openrewrite.maven.utilities.MavenArtifactDownloader;
2223
import org.openrewrite.tree.ParsingEventListener;
@@ -54,14 +55,6 @@
5455
@Import({ScanScope.class, ScopeConfiguration.class})
5556
public class RewriteParserConfiguration {
5657

57-
@Autowired
58-
private ParserProperties parserProperties;
59-
60-
// @Bean
61-
// ProvenanceMarkerFactory provenanceMarkerFactory(MavenMojoProjectParserFactory projectParserFactory) {
62-
// return new ProvenanceMarkerFactory(projectParserFactory);
63-
// }
64-
6558
@Bean
6659
MavenPasswordDecrypter mavenPasswordDecrypter() {
6760
return new MavenPasswordDecrypter();

sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/RewriteProjectParser.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.openrewrite.ExecutionContext;
2222
import org.openrewrite.SourceFile;
2323
import org.openrewrite.marker.Marker;
24+
import org.openrewrite.maven.MavenExecutionContextView;
25+
import org.openrewrite.maven.MavenSettings;
2426
import org.openrewrite.style.NamedStyles;
2527
import org.openrewrite.tree.ParsingEventListener;
2628
import org.openrewrite.tree.ParsingExecutionContextView;
@@ -35,6 +37,8 @@
3537
import org.springframework.sbm.parsers.maven.ProvenanceMarkerFactory;
3638
import org.springframework.sbm.scopes.ScanScope;
3739

40+
import java.net.URI;
41+
import java.nio.charset.Charset;
3842
import java.nio.file.Path;
3943
import java.util.ArrayList;
4044
import java.util.List;
@@ -117,7 +121,6 @@ public RewriteProjectParsingResult parse(Path givenBaseDir, List<Resource> resou
117121
List<Xml.Document> parsedBuildFiles = buildFileParser.parseBuildFiles(baseDir, parserContext.getBuildFileResources(), parserContext.getActiveProfiles(), executionContext, parserProperties.isSkipMavenParsing(), provenanceMarkers);
118122
parserContext.setParsedBuildFiles(parsedBuildFiles);
119123

120-
121124
log.trace("Start to parse %d source files in %d modules".formatted(resources.size() + parsedBuildFiles.size(), parsedBuildFiles.size()));
122125
List<SourceFile> otherSourceFiles = sourceFileParser.parseOtherSourceFiles(baseDir, parserContext, resources, provenanceMarkers, styles, executionContext);
123126

Original file line numberDiff line numberDiff line change
@@ -13,22 +13,15 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.springframework.sbm.parsers.maven;
16+
package org.springframework.sbm.parsers;
1717

18-
import lombok.RequiredArgsConstructor;
19-
import lombok.extern.slf4j.Slf4j;
20-
import org.openrewrite.maven.utilities.MavenArtifactDownloader;
18+
import org.openrewrite.SourceFile;
19+
import org.openrewrite.java.tree.JavaType;
20+
21+
import java.util.List;
2122

2223
/**
2324
* @author Fabian Krüger
2425
*/
25-
@Slf4j
26-
@RequiredArgsConstructor
27-
class MavenMojoProjectParserPrivateMethods {
28-
29-
private final MavenMojoProjectParserFactory mavenMojoProjectParserFactory;
30-
private final MavenArtifactDownloader artifactDownloader;
31-
32-
33-
26+
public record SourceSetParsingResult(List<SourceFile> sourceFiles, List<JavaType.FullyQualified> classpath) {
3427
}

sbm-support-rewrite/src/main/java/org/springframework/sbm/parsers/maven/BuildFileParser.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import org.openrewrite.Parser;
2222
import org.openrewrite.SourceFile;
2323
import org.openrewrite.marker.Marker;
24+
import org.openrewrite.maven.MavenExecutionContextView;
2425
import org.openrewrite.maven.MavenParser;
26+
import org.openrewrite.maven.MavenSettings;
2527
import org.openrewrite.xml.tree.Xml;
2628
import org.springframework.core.io.Resource;
2729
import org.springframework.sbm.utils.ResourceUtil;
@@ -46,14 +48,14 @@
4648
public class BuildFileParser {
4749

4850
/**
49-
* Parse a list of Maven Pom files to a Map of {@code Path} and their parsed {@link Xml.Document}s.
50-
* The {@link Xml.Document}s are marked with {@link org.openrewrite.maven.tree.MavenResolutionResult} and the provided provenance markers.
51+
* Parse a list of Maven Pom files to a {@code List} of {@link Xml.Document}s.
52+
* The {@link Xml.Document}s get marked with {@link org.openrewrite.maven.tree.MavenResolutionResult} and the provided provenance markers.
5153
*
5254
* @param baseDir the {@link Path} to the root of the scanned project
5355
* @param buildFiles the list of resources for relevant pom files.
54-
* @param activeProfiles teh active Maven profiles
56+
* @param activeProfiles the active Maven profiles
5557
* @param executionContext the ExecutionContext to use
56-
* * @param skipMavenParsing skip parsing Maven files
58+
* @param skipMavenParsing skip parsing Maven files
5759
* @param provenanceMarkers the map of markers to be added
5860
*/
5961
public List<Xml.Document> parseBuildFiles(
@@ -138,7 +140,10 @@ private Stream<Xml.Document> parsePoms(Path baseDir, List<Resource> pomFiles, Ma
138140
}
139141

140142
private void initializeMavenSettings(ExecutionContext executionContext) {
141-
143+
// FIXME: https://github.com/spring-projects-experimental/spring-boot-migrator/issues/880
144+
String repo = "file://" + Path.of(System.getProperty("user.home")).resolve(".m2/repository") + "/";
145+
MavenSettings mavenSettings = new MavenSettings(repo, null, null, null, null);
146+
MavenExecutionContextView.view(executionContext).setMavenSettings(mavenSettings);
142147
}
143148

144149
public List<Resource> filterAndSortBuildFiles(List<Resource> resources) {

0 commit comments

Comments
 (0)