Skip to content

Commit

Permalink
Merge pull request #16 from sse-labs/feature/mock-version-range-tests
Browse files Browse the repository at this point in the history
Proper mocking for tests of version range resolution
  • Loading branch information
jachiaram authored Jan 31, 2025
2 parents df2d00e + 2027a5a commit cea390b
Show file tree
Hide file tree
Showing 7 changed files with 335 additions and 180 deletions.
113 changes: 31 additions & 82 deletions src/main/java/org/tudo/sse/resolution/PomResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.repository.metadata.Metadata;
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
import org.apache.maven.model.*;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;

Expand All @@ -18,6 +16,8 @@
import org.tudo.sse.model.pom.License;
import org.tudo.sse.model.pom.PomInformation;
import org.tudo.sse.model.pom.RawPomFeatures;
import org.tudo.sse.resolution.releases.DefaultMavenReleaseListProvider;
import org.tudo.sse.resolution.releases.IReleaseListProvider;
import org.tudo.sse.utils.MavenCentralRepository;
import scala.Tuple2;

Expand All @@ -41,81 +41,30 @@ public class PomResolver {
private static final Logger log = LogManager.getLogger(PomResolver.class);
private final Map<String, Function<PomInformation, String>> predefinedPomValues;

private final IReleaseListProvider releaseListProvider;

/**
* In the constructor for this class, a boolean is passed to determine if transitive dependencies should be resolved when the resolution is run.
*
* @param resolveTransitives determines if transitive dependencies are to be resolved
*/
public PomResolver(boolean resolveTransitives) {
output = false;
pathToDirectory = null;
this.resolveTransitives = resolveTransitives;
predefinedPomValues = new HashMap<>();
predefinedPomValues.put("project.version", pom -> pom.getIdent().getVersion());
predefinedPomValues.put("pom.version", pom -> pom.getIdent().getVersion());
predefinedPomValues.put("version", pom -> pom.getIdent().getVersion());
predefinedPomValues.put("pom.currentVersion", pom -> pom.getIdent().getVersion());
predefinedPomValues.put("project.parent.version", pom -> {
if (pom.getRawPomFeatures().getParent() != null) {
return pom.getRawPomFeatures().getParent().getVersion();
} else {
return null;
}
});
predefinedPomValues.put("pom.parent.version", pom -> {
if (pom.getRawPomFeatures().getParent() != null) {
return pom.getRawPomFeatures().getParent().getVersion();
} else {
return null;
}
});
predefinedPomValues.put("parent.version", pom -> {
if (pom.getRawPomFeatures().getParent() != null) {
return pom.getRawPomFeatures().getParent().getVersion();
} else {
return null;
}
});
predefinedPomValues.put("groupId", pom -> pom.getIdent().getGroupID());
predefinedPomValues.put("project.groupId", pom -> pom.getIdent().getGroupID());
predefinedPomValues.put("pom.groupId", pom -> pom.getIdent().getGroupID());
predefinedPomValues.put("project.parent.groupId", pom -> {
if (pom.getRawPomFeatures().getParent() != null) {
return pom.getRawPomFeatures().getParent().getGroupID();
} else {
return null;
}
});
predefinedPomValues.put("parent.groupId", pom -> {
if (pom.getRawPomFeatures().getParent() != null) {
return pom.getRawPomFeatures().getParent().getGroupID();
} else {
return null;
}
});
predefinedPomValues.put("artifactId", pom -> pom.getIdent().getArtifactID());
predefinedPomValues.put("project.artifactId", pom -> pom.getIdent().getArtifactID());
predefinedPomValues.put("pom.artifactId", pom -> pom.getIdent().getArtifactID());
predefinedPomValues.put("project.parent.artifactId", pom -> {
if (pom.getRawPomFeatures().getParent() != null) {
return pom.getRawPomFeatures().getParent().getArtifactID();
} else {
return null;
}
});
predefinedPomValues.put("parent.artifactId", pom -> {
if (pom.getRawPomFeatures().getParent() != null) {
return pom.getRawPomFeatures().getParent().getArtifactID();
} else {
return null;
}
});
this(resolveTransitives, DefaultMavenReleaseListProvider.getInstance());
}

public PomResolver(boolean resolveTransitives, IReleaseListProvider provider) {
this(false, null, resolveTransitives, provider);
}

public PomResolver(boolean output, Path pathToDirectory, boolean resolveTransitives){
this(output, pathToDirectory, resolveTransitives, DefaultMavenReleaseListProvider.getInstance());
}

public PomResolver(boolean output, Path pathToDirectory, boolean resolveTransitives) {
public PomResolver(boolean output, Path pathToDirectory, boolean resolveTransitives, IReleaseListProvider provider) {
this.output = output;
this.pathToDirectory = pathToDirectory;
this.resolveTransitives = resolveTransitives;
this.releaseListProvider = provider;
predefinedPomValues = new HashMap<>();
predefinedPomValues.put("project.version", pom -> pom.getIdent().getVersion());
predefinedPomValues.put("pom.version", pom -> pom.getIdent().getVersion());
Expand Down Expand Up @@ -730,49 +679,49 @@ public String resolveVersionRange(org.tudo.sse.model.pom.Dependency toResolve) {
List<VersionRange> ranges = new ArrayList<>();
List<String> sets = splitSets(toResolve.getIdent().getVersion());
GenericVersionScheme scheme = new GenericVersionScheme();
MetadataXpp3Reader reader = new MetadataXpp3Reader();
String highestMatching = null;

try {
for(String splitRange : sets) {
ranges.add(scheme.parseVersionRange(splitRange));
}

var xmlData = new BufferedReader(new InputStreamReader(Objects.requireNonNull(MavenRepo.openXMLFileInputStream(toResolve.getIdent()))));
Metadata meta = reader.read(xmlData);
xmlData.close();
List<Version> allVersions = new ArrayList<>();

for(String version : releaseListProvider.getReleases(toResolve.getIdent())){
allVersions.add(scheme.parseVersion(version));
}

allVersions.sort(Version::compareTo);

if(meta.getVersioning() != null) {
List<String> versioning = meta.getVersioning().getVersions();
if(!allVersions.isEmpty()) {

for (VersionRange range : ranges) {
if (range.getUpperBound() == null) {
//add comparison here to check that the lower bound is met
Version current = scheme.parseVersion(versioning.get(versioning.size() - 1));
Version current = allVersions.get(allVersions.size() - 1);

if (range.getLowerBound().isInclusive()) {
if (range.getLowerBound().getVersion().compareTo(current) <= 0) {
highestMatching = versioning.get(versioning.size() - 1);
highestMatching = allVersions.get(allVersions.size() - 1).toString();
}
} else {
if (range.getLowerBound().getVersion().compareTo(current) < 0) {
highestMatching = versioning.get(versioning.size() - 1);
highestMatching = allVersions.get(allVersions.size() - 1).toString();
}
}
return highestMatching;
}
if (!range.getUpperBound().isInclusive()) {
for (String version : versioning) {
Version current = scheme.parseVersion(version);
for (Version current : allVersions) {
if (current.compareTo(range.getUpperBound().getVersion()) < 0) {
highestMatching = version;
highestMatching = current.toString();
}
}
} else {
for (String version : versioning) {
Version current = scheme.parseVersion(version);
for (Version current : allVersions) {
if (current.compareTo(range.getUpperBound().getVersion()) <= 0) {
highestMatching = version;
highestMatching = current.toString();
}
}
}
Expand All @@ -781,7 +730,7 @@ public String resolveVersionRange(org.tudo.sse.model.pom.Dependency toResolve) {
throw new IOException();
}

} catch (IOException | XmlPullParserException | InvalidVersionSpecificationException | FileNotFoundException e) {
} catch (IOException | InvalidVersionSpecificationException e) {
log.error(e);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.tudo.sse.resolution.releases;

import org.apache.maven.artifact.repository.metadata.Metadata;
import org.apache.maven.artifact.repository.metadata.Versioning;
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.tudo.sse.model.ArtifactIdent;
import org.tudo.sse.resolution.FileNotFoundException;
import org.tudo.sse.utils.MavenCentralRepository;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
* This default implementation of IReleaseListProvider obtains a list of releases for a given GA-Tuple by
* querying the maven-metadata.xml file hosted on Maven Central. It will throw an IOException if file
* access files for any given reason.
*
* @author Johannes Düsing
*/
public class DefaultMavenReleaseListProvider implements IReleaseListProvider{

private final MetadataXpp3Reader reader = new MetadataXpp3Reader();
private final MavenCentralRepository mavenRepo = MavenCentralRepository.getInstance();

// Singleton pattern implementation
private final static DefaultMavenReleaseListProvider instance = new DefaultMavenReleaseListProvider();

/**
* Access the singleton instance of DefaultMavenReleaseListProvider
* @return The only instance of this class
*/
public static DefaultMavenReleaseListProvider getInstance() {
return instance;
}

// Singleton pattern implementation
private DefaultMavenReleaseListProvider(){}

@Override
public List<String> getReleases(ArtifactIdent identifier) throws IOException {
Objects.requireNonNull(identifier);

try(InputStream xmlInputStream = mavenRepo.openXMLFileInputStream(identifier)) {
Metadata versionListData = reader.read(new BufferedReader(new InputStreamReader(xmlInputStream)));

Versioning versioning = versionListData.getVersioning();

if(versioning != null) {
List<String> versions = new ArrayList<>();
for(Object version : versioning.getVersions()) {
versions.add((String)version);
}
return versions;
} else {
return List.of();
}

} catch (XmlPullParserException | IOException | FileNotFoundException x) {
throw new IOException("Failed to obtain version list for " + identifier, x);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.tudo.sse.resolution.releases;

import org.tudo.sse.model.ArtifactIdent;

import java.io.IOException;
import java.util.List;

public interface IReleaseListProvider {

/**
* Gets the ordered list of releases (i.e. version numbers) for the given identifier. The identifier's version is
* irrelevant, only the GA tuple is used to obtain a release list.
*
* @param identifier Identifier to obtain the release list for (GA-Tuple)
* @return List of version numbers as ordered by the underlying source
*/
List<String> getReleases(ArtifactIdent identifier) throws IOException;

}
36 changes: 34 additions & 2 deletions src/test/java/org/tudo/sse/model/LocalPomInformationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import org.tudo.sse.model.pom.LocalPomInformation;
import org.tudo.sse.model.pom.RawPomFeatures;
import org.tudo.sse.resolution.PomResolver;
import org.tudo.sse.resolution.releases.IReleaseListProvider;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

Expand All @@ -26,10 +28,40 @@ class LocalPomInformationTest {
json = gson.fromJson(targetReader, new TypeToken<Map<String, Object>>() {}.getType());
}

final IReleaseListProvider mockProvider = new IReleaseListProvider() {

private Map<String, List<String>> releaseListData = new HashMap<>();

private void buildMap(){
for(Map<String, Object> entry : (List<Map<String, Object>>)json.get("versionLists")){
String g = (String)entry.get("g");
String a = (String)entry.get("a");
List<String> versions = (List<String>)entry.get("v");
String ga = g + ":" + a;

releaseListData.put(ga, versions);
}
}

@Override
public List<String> getReleases(ArtifactIdent identifier) throws IOException {
if(releaseListData.isEmpty()){
buildMap();
}

if(releaseListData.containsKey(identifier.getGA())){
return releaseListData.get(identifier.getGA());
} else {
fail("No mock release data available for " + identifier.getGA());
return null;
}
}
};

@Test
void localPom() {
List<LocalPomInformation> tests = new ArrayList<>();
PomResolver resolver = new PomResolver(true);
PomResolver resolver = new PomResolver(true, mockProvider);

tests.add(new LocalPomInformation("src/test/resources/localPom.xml", resolver));
tests.add(new LocalPomInformation(new File("src/test/resources/localPom.xml"), resolver));
Expand All @@ -45,7 +77,7 @@ void localPom() {

@Test
void localPomException() {
PomResolver resolver = new PomResolver(true);
PomResolver resolver = new PomResolver(true, mockProvider);
assertThrows(RuntimeException.class, () -> new LocalPomInformation("src/test/resources/brokenlocalPom.xml", resolver));
assertThrows(RuntimeException.class, () -> new LocalPomInformation(new File("src/test/resources/brokenlocalPom.xml"), resolver));
assertThrows(RuntimeException.class, () -> new LocalPomInformation(new FileInputStream("src/test/resources/brokenlocalPom.xml"), resolver));
Expand Down
Loading

0 comments on commit cea390b

Please sign in to comment.