Compare commits

...

4 commits

Author SHA1 Message Date
Geert Bevin 2177fcb57c Stop counting POM retrievals before the response is released
Some checks are pending
bld-ci / build (./bld, 17, macos-latest, false) (push) Waiting to run
bld-ci / build (./bld, 21, macos-latest, false) (push) Waiting to run
bld-ci / build (./bld, 25, macos-latest, false) (push) Waiting to run
bld-ci / build (./bld, 26, macos-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 17, windows-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 21, windows-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 25, windows-latest, false) (push) Waiting to run
bld-ci / build (.\bld.bat, 26, windows-latest, false) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 17, ubuntu-latest, true) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 21, ubuntu-latest, true) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 25, ubuntu-latest, true) (push) Waiting to run
bld-ci / build (unittests, password, unittests, mariadb:10.9, mysql:8, gvenzl/oracle-free:latest, gvenzl/oracle-xe:18-slim, postgres:15, ./bld, 26, ubuntu-latest, true) (push) Waiting to run
javadocs-pages / deploy (push) Waiting to run
2026-07-25 12:55:14 -04:00
Geert Bevin 8a28b4eac8 Fall through to the next repository on transient retrieval failures 2026-07-25 12:55:14 -04:00
Geert Bevin 88547605dc Retry the live repository tests on transient network failures 2026-07-25 11:51:40 -04:00
Geert Bevin a930128f66 Retry transient download failures in the wrapper 2026-07-25 11:51:40 -04:00
8 changed files with 538 additions and 186 deletions

View file

@ -243,15 +243,24 @@ public class DependencyResolver {
*/
public RepositoryArtifact transferIntoDirectory(File directory)
throws DependencyTransferException {
DependencyTransferException transient_failure = null;
for (var artifact : getTransferArtifacts()) {
try {
if (retriever_.transferIntoDirectory(artifact, directory)) {
return artifact;
}
} catch (IOException e) {
throw new DependencyTransferException(dependency_, artifact.location(), directory, e);
// a transient issue with this repository, try the next one
// before giving up
transient_failure = rememberFailure(transient_failure, new DependencyTransferException(dependency_, artifact.location(), directory, e));
}
}
// transient failures never degrade into a null not-found result,
// an artifact that couldn't be transferred might still exist
if (transient_failure != null) {
throw transient_failure;
}
return null;
}
@ -370,46 +379,58 @@ public class DependencyResolver {
}
private MavenMetadata parseMavenMetadata(List<RepositoryArtifact> artifacts) {
RepositoryArtifact retrieved_artifact = null;
String metadata = null;
var retrieved = retrieveFirstAvailable(artifacts, "metadata locations");
var xml = new Xml2MavenMetadata();
if (!xml.processXml(retrieved.content())) {
throw new DependencyXmlParsingErrorException(dependency_, retrieved.artifact().location(), xml.getErrors());
}
return xml;
}
private record RetrievedArtifact(RepositoryArtifact artifact, String content) {
}
private RetrievedArtifact retrieveFirstAvailable(List<RepositoryArtifact> artifacts, String locationsDescription) {
ArtifactRetrievalErrorException transient_failure = null;
for (var artifact : artifacts) {
try {
var content = retriever_.readString(artifact);
if (content == null) {
throw new ArtifactNotFoundException(dependency_, artifact.location());
}
retrieved_artifact = artifact;
metadata = content;
break;
return new RetrievedArtifact(artifact, retriever_.readString(artifact));
} catch (FileUtilsErrorException e) {
if (e.getCause() instanceof FileNotFoundException) {
continue;
}
throw new ArtifactRetrievalErrorException(dependency_, artifact.location(), e);
// a transient issue with this repository, try the next one
// before giving up
transient_failure = rememberFailure(transient_failure, new ArtifactRetrievalErrorException(dependency_, artifact.location(), e));
}
}
if (metadata == null) {
var location = artifacts.stream().map(RepositoryArtifact::location).collect(Collectors.joining(", "));
if (location.isEmpty()) {
if (repositories_.isEmpty()) {
location = "[no repositories defined]";
}
else {
location = "[no metadata locations defined]";
}
// transient failures never degrade into not-found, an artifact
// that couldn't be retrieved might still exist
if (transient_failure != null) {
throw transient_failure;
}
var location = artifacts.stream().map(RepositoryArtifact::location).collect(Collectors.joining(", "));
if (location.isEmpty()) {
if (repositories_.isEmpty()) {
location = "[no repositories defined]";
}
else {
location = "[no " + locationsDescription + " defined]";
}
throw new ArtifactNotFoundException(dependency_, location);
}
throw new ArtifactNotFoundException(dependency_, location);
}
var xml = new Xml2MavenMetadata();
if (!xml.processXml(metadata)) {
throw new DependencyXmlParsingErrorException(dependency_, retrieved_artifact.location(), xml.getErrors());
private static <T extends Throwable> T rememberFailure(T remembered, T failure) {
if (remembered == null) {
return failure;
}
return xml;
remembered.addSuppressed(failure);
return remembered;
}
private List<RepositoryArtifact> getPomLocations() {
@ -426,50 +447,16 @@ public class DependencyResolver {
}
Xml2MavenPom getMavenPom(Dependency parent) {
RepositoryArtifact retrieved_artifact = null;
String pom = null;
var artifacts = getPomLocations();
for (var artifact : artifacts) {
try {
var content = retriever_.readString(artifact);
if (content == null) {
throw new ArtifactNotFoundException(dependency_, artifact.location());
}
retrieved_artifact = artifact;
pom = content;
break;
} catch (FileUtilsErrorException e) {
if (e.getCause() instanceof FileNotFoundException) {
continue;
}
throw new ArtifactRetrievalErrorException(dependency_, artifact.location(), e);
}
}
if (pom == null) {
var location = artifacts.stream().map(RepositoryArtifact::location).collect(Collectors.joining(", "));
if (location.isEmpty()) {
if (repositories_.isEmpty()) {
location = "[no repositories defined]";
}
else {
location = "[no pom locations defined]";
}
}
throw new ArtifactNotFoundException(dependency_, location);
}
var retrieved = retrieveFirstAvailable(getPomLocations(), "pom locations");
var xml = new Xml2MavenPom(parent, resolution_, retriever_, repositories_);
// first pass only extracts the properties from the pom
if (!xml.processXml(pom)) {
throw new DependencyXmlParsingErrorException(dependency_, retrieved_artifact.location(), xml.getErrors());
if (!xml.processXml(retrieved.content())) {
throw new DependencyXmlParsingErrorException(dependency_, retrieved.artifact().location(), xml.getErrors());
}
// second pass parses all the rest so that the properties are available anywhere
if (!xml.processXml(pom)) {
throw new DependencyXmlParsingErrorException(dependency_, retrieved_artifact.location(), xml.getErrors());
if (!xml.processXml(retrieved.content())) {
throw new DependencyXmlParsingErrorException(dependency_, retrieved.artifact().location(), xml.getErrors());
}
return xml;

View file

@ -629,29 +629,32 @@ public class Wrapper {
private void downloadDistribution(File file, String downloadUrl)
throws IOException {
try {
System.out.print("Downloading: " + downloadUrl + " ... ");
System.out.flush();
var url = new URL(downloadUrl);
var readableByteChannel = Channels.newChannel(url.openStream());
try (var fileOutputStream = new FileOutputStream(file)) {
var fileChannel = fileOutputStream.getChannel();
fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
retryOnTransientIoIssues(() -> {
try {
System.out.print("Downloading: " + downloadUrl + " ... ");
System.out.flush();
var url = new URL(downloadUrl);
var readableByteChannel = Channels.newChannel(url.openStream());
try (var fileOutputStream = new FileOutputStream(file)) {
var fileChannel = fileOutputStream.getChannel();
fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
System.out.print("done");
System.out.print("done");
}
} catch (FileNotFoundException e) {
System.err.println("not found");
System.err.println("Failed to download file " + file + ".");
throw e;
} catch (IOException e) {
System.err.println("error");
System.err.println("Failed to download file " + file + " due to I/O issue.");
Files.deleteIfExists(file.toPath());
throw e;
} finally {
System.out.println();
}
} catch (FileNotFoundException e) {
System.err.println("not found");
System.err.println("Failed to download file " + file + ".");
throw e;
} catch (IOException e) {
System.err.println("error");
System.err.println("Failed to download file " + file + " due to I/O issue.");
Files.deleteIfExists(file.toPath());
throw e;
} finally {
System.out.println();
}
return null;
});
}
private void resolveExtensions() {
@ -835,11 +838,48 @@ public class Wrapper {
private String readString(String version, URL url)
throws IOException {
var connection = url.openConnection();
connection.setUseCaches(false);
connection.setRequestProperty(HttpUtils.HEADER_USER_AGENT, Product.BLD.toUserAgent(version));
try (var in = connection.getInputStream()) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
return retryOnTransientIoIssues(() -> {
var connection = url.openConnection();
connection.setUseCaches(false);
connection.setRequestProperty(HttpUtils.HEADER_USER_AGENT, Product.BLD.toUserAgent(version));
try (var in = connection.getInputStream()) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
});
}
static final int DOWNLOAD_ATTEMPTS = 3;
static final long DOWNLOAD_RETRY_DELAY_MS = 2000L;
interface IoAction<T> {
T execute() throws IOException;
}
private static <T> T retryOnTransientIoIssues(IoAction<T> action)
throws IOException {
return retryOnTransientIoIssues(action, DOWNLOAD_ATTEMPTS, DOWNLOAD_RETRY_DELAY_MS);
}
static <T> T retryOnTransientIoIssues(IoAction<T> action, int attempts, long delayMs)
throws IOException {
for (var attempt = 1; ; ++attempt) {
try {
return action.execute();
} catch (FileNotFoundException e) {
// a resource that doesn't exist will not appear by retrying
throw e;
} catch (IOException e) {
if (attempt >= attempts) {
throw e;
}
System.err.println("Download issue (" + e.getMessage() + "), retrying ...");
try {
Thread.sleep(delayMs * attempt);
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
throw e;
}
}
}
}

View file

@ -6,6 +6,7 @@ package rife.bld.dependencies;
import com.sun.net.httpserver.HttpServer;
import org.junit.jupiter.api.Test;
import rife.bld.testing.RetryTest;
import rife.ioc.HierarchicalProperties;
import rife.tools.FileUtils;
import rife.tools.StringUtils;
@ -41,38 +42,38 @@ public class TestDependencyResolver {
assertEquals(new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 4, 0)), resolver.dependency());
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testNotFound() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.org.unknown", "voidthing"));
assertFalse(resolver.exists());
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testCheckExistence() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.uwyn.rife2", "rife2"));
assertTrue(resolver.exists());
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testCheckExistenceVersion() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 4, 0)));
assertTrue(resolver.exists());
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testCheckExistenceMissingVersion() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 3, 9)));
assertFalse(resolver.exists());
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testCheckVersionOverride() {
var resolver = new DependencyResolver(new VersionResolution(new HierarchicalProperties().put(PROPERTY_OVERRIDE_PREFIX, "com.uwyn.rife2:rife2:1.8.0")),
ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 3, 9)));
assertEquals(new VersionNumber(1, 8, 0), resolver.resolveVersion());
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testListVersions() {
var resolver1 = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.uwyn.rife2", "rife2"));
var versions1 = resolver1.listVersions();
@ -91,7 +92,7 @@ public class TestDependencyResolver {
assertTrue(versions2.contains(new VersionNumber(11, 0, 14)));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetLatestVersion() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(3), new Dependency("com.uwyn.rife2", "rife2"));
var version = resolver.latestVersion();
@ -99,7 +100,7 @@ public class TestDependencyResolver {
assertTrue(version.compareTo(new VersionNumber(1, 4)) >= 0);
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetReleaseVersion() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(4), new Dependency("com.uwyn.rife2", "rife2"));
var version = resolver.releaseVersion();
@ -107,7 +108,7 @@ public class TestDependencyResolver {
assertTrue(version.compareTo(new VersionNumber(1, 4)) >= 0);
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testMetadata() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(5), new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 4, 0)));
var metadata = resolver.getMavenMetadata();
@ -116,7 +117,7 @@ public class TestDependencyResolver {
assertNull(resolver.getSnapshotMavenMetadata());
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testSnapshotMetadata() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), List.of(getNextRepository(), RIFE2_SNAPSHOTS), new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 9, 1, "SNAPSHOT")));
var metadata = resolver.getSnapshotMavenMetadata();
@ -125,7 +126,7 @@ public class TestDependencyResolver {
assertEquals(4, metadata.getSnapshotBuildNumber());
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileDependenciesRIFE2() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.uwyn.rife2", "rife2"));
var dependencies = resolver.getDirectDependencies(compile);
@ -133,7 +134,7 @@ public class TestDependencyResolver {
assertEquals(0, dependencies.size());
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileDependenciesRIFE2Snapshot() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), List.of(getNextRepository(), RIFE2_SNAPSHOTS), new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 9, 1, "SNAPSHOT")));
var dependencies = resolver.getDirectDependencies(compile);
@ -141,7 +142,7 @@ public class TestDependencyResolver {
assertEquals(0, dependencies.size());
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileDependenciesGoogleApi() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.google.apis", "google-api-services-youtube", new VersionGeneric("v3-rev20240514-2.0.0")));
var dependencies = resolver.getDirectDependencies(compile);
@ -151,7 +152,7 @@ public class TestDependencyResolver {
com.google.api-client:google-api-client:2.5.0""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileDependenciesJetty() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.eclipse.jetty", "jetty-server", new VersionNumber(11, 0, 14)));
var dependencies = resolver.getDirectDependencies(compile);
@ -164,7 +165,7 @@ public class TestDependencyResolver {
org.slf4j:slf4j-api:2.0.5""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileDependenciesAssertJ() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.assertj", "assertj-joda-time", new VersionNumber(2, 2, 0)));
var dependencies = resolver.getDirectDependencies(compile);
@ -174,7 +175,7 @@ public class TestDependencyResolver {
org.assertj:assertj-core""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileDependenciesSwagger() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("io.swagger.core.v3", "swagger-core", new VersionNumber(2,2,27)));
var dependencies = resolver.getDirectDependencies(compile);
@ -194,7 +195,7 @@ public class TestDependencyResolver {
jakarta.validation:jakarta.validation-api:2.0.2""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileDependenciesJettyOverride1() {
var resolver = new DependencyResolver(new VersionResolution(new HierarchicalProperties().put(PROPERTY_OVERRIDE_PREFIX, "org.slf4j:slf4j-api:2.0.16")),
ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.eclipse.jetty", "jetty-server", new VersionNumber(11, 0, 14)));
@ -208,7 +209,7 @@ public class TestDependencyResolver {
org.slf4j:slf4j-api:2.0.16""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileDependenciesJettyOverride2() {
var resolver = new DependencyResolver(new VersionResolution(new HierarchicalProperties().put(PROPERTY_OVERRIDE_PREFIX, "org.slf4j:slf4j-api:2.0.11,org.eclipse.jetty:jetty-io:11.0.13,org.eclipse.jetty:jetty-server:11.0.15")),
ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.eclipse.jetty", "jetty-server", new VersionNumber(11, 0, 14)));
@ -222,7 +223,7 @@ public class TestDependencyResolver {
org.slf4j:slf4j-api:2.0.11""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileRuntimeDependenciesJunit() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.junit.jupiter", "junit-jupiter", new VersionNumber(5, 9, 2)));
var dependencies_compile = resolver.getDirectDependencies(compile, runtime);
@ -234,7 +235,7 @@ public class TestDependencyResolver {
org.junit.jupiter:junit-jupiter-engine:5.9.2""", StringUtils.join(dependencies_compile, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileDependenciesSpringBoot() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.springframework.boot", "spring-boot-starter", new VersionNumber(3, 0, 4)));
var dependencies = resolver.getDirectDependencies(compile);
@ -249,7 +250,7 @@ public class TestDependencyResolver {
org.yaml:snakeyaml:1.33""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileDependenciesMaven() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.apache.maven", "maven-core", new VersionNumber(3, 9, 0)));
var dependencies = resolver.getDirectDependencies(compile);
@ -284,7 +285,7 @@ public class TestDependencyResolver {
org.slf4j:slf4j-api:1.7.36""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileDependenciesPlay() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.typesafe.play", "play_2.13", new VersionNumber(2, 8, 19)));
var dependencies = resolver.getDirectDependencies(compile);
@ -318,7 +319,7 @@ public class TestDependencyResolver {
org.scala-lang.modules:scala-parser-combinators_2.13:1.1.2""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileDependenciesVaadin() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.vaadin", "vaadin", new VersionNumber(23, 3, 7)));
var dependencies = resolver.getDirectDependencies(compile);
@ -336,7 +337,7 @@ public class TestDependencyResolver {
com.vaadin:collaboration-engine:5.3.0""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileRuntimeDependenciesBitly() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("net.thauvin.erik", "bitly-shorten", new VersionNumber(2, 0, 0)));
var dependencies = resolver.getDirectDependencies(compile, runtime);
@ -351,7 +352,7 @@ public class TestDependencyResolver {
org.json:json:20250107""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileTransitiveDependenciesRIFE2() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.uwyn.rife2", "rife2"));
var dependencies = resolver.getAllDependencies(compile);
@ -361,7 +362,7 @@ public class TestDependencyResolver {
com.uwyn.rife2:rife2""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileTransitiveDependenciesRIFE2Snapshot() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), List.of(getNextRepository(), RIFE2_SNAPSHOTS), new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 9, 1, "SNAPSHOT")));
var dependencies = resolver.getAllDependencies(compile);
@ -371,7 +372,7 @@ public class TestDependencyResolver {
com.uwyn.rife2:rife2:1.9.1-SNAPSHOT""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileTransitiveDependenciesJetty() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.eclipse.jetty", "jetty-server", new VersionNumber(11, 0, 14)));
var dependencies = resolver.getAllDependencies(compile);
@ -386,7 +387,7 @@ public class TestDependencyResolver {
org.eclipse.jetty:jetty-util:11.0.14""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileTransitiveDependenciesJettyExclusion() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(),
new Dependency("org.eclipse.jetty", "jetty-server", new VersionNumber(11, 0, 14))
@ -402,7 +403,7 @@ public class TestDependencyResolver {
org.eclipse.jetty:jetty-util:11.0.14""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileTransitiveDependenciesJettyFullGroupExclusion() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(),
new Dependency("org.eclipse.jetty", "jetty-server", new VersionNumber(11, 0, 14))
@ -416,7 +417,7 @@ public class TestDependencyResolver {
org.slf4j:slf4j-api:2.0.5""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileTransitiveDependenciesJettyFullArtifactExclusion() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(),
new Dependency("org.eclipse.jetty", "jetty-server", new VersionNumber(11, 0, 14))
@ -432,7 +433,7 @@ public class TestDependencyResolver {
org.eclipse.jetty:jetty-util:11.0.14""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileTransitiveDependenciesJettyFullExclusion() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(),
new Dependency("org.eclipse.jetty", "jetty-server", new VersionNumber(11, 0, 14))
@ -444,7 +445,7 @@ public class TestDependencyResolver {
org.eclipse.jetty:jetty-server:11.0.14""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileTransitiveDependenciesJettyAndSlfj() {
var dependencies = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.eclipse.jetty", "jetty-server", new VersionNumber(11, 0, 14))).getAllDependencies(compile);
var dependencies2 = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.slf4j", "slf4j-simple", new VersionNumber(2, 0, 6))).getAllDependencies(compile, runtime);
@ -464,7 +465,7 @@ public class TestDependencyResolver {
org.slf4j:slf4j-api:2.0.6""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileRuntimeTransitiveDependenciesJunit() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.junit.jupiter", "junit-jupiter", new VersionNumber(5, 9, 2)));
var dependencies_compile = resolver.getAllDependencies(compile, runtime);
@ -487,7 +488,7 @@ public class TestDependencyResolver {
org.junit.jupiter:junit-jupiter-engine:5.9.2""", StringUtils.join(dependencies_runtime, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileTransitiveDependenciesSpringBoot() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.springframework.boot", "spring-boot-starter", new VersionNumber(3, 0, 4)));
var dependencies = resolver.getAllDependencies(compile);
@ -514,7 +515,7 @@ public class TestDependencyResolver {
org.apache.logging.log4j:log4j-api:2.19.0""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileTransitiveDependenciesMaven() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.apache.maven", "maven-core", new VersionNumber(3, 9, 0)));
var dependencies = resolver.getAllDependencies(compile);
@ -555,7 +556,7 @@ public class TestDependencyResolver {
org.codehaus.plexus:plexus-cipher:2.0""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileTransitiveDependenciesPlay() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.typesafe.play", "play_2.13", new VersionNumber(2, 8, 19)));
var dependencies = resolver.getAllDependencies(compile);
@ -612,7 +613,7 @@ public class TestDependencyResolver {
com.thoughtworks.paranamer:paranamer:2.8""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileTransitiveDependenciesVaadin() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("com.vaadin", "vaadin", new VersionNumber(23, 3, 7)));
var dependencies = resolver.getAllDependencies(compile);
@ -709,7 +710,7 @@ public class TestDependencyResolver {
com.google.code.findbugs:jsr305:3.0.2""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileRuntimeTransitiveDependenciesBitly() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("net.thauvin.erik", "bitly-shorten", new VersionNumber(2, 0, 0)));
var dependencies = resolver.getAllDependencies(compile, runtime);
@ -729,7 +730,7 @@ public class TestDependencyResolver {
com.squareup.okio:okio-jvm:3.6.0""", StringUtils.join(dependencies, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetCompileRuntimeTransitiveDependenciesMariaDb() {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.mariadb.jdbc", "mariadb-java-client", new VersionNumber(3, 1, 3)));
var dependencies_compile = resolver.getAllDependencies(compile, runtime);
@ -752,7 +753,7 @@ public class TestDependencyResolver {
org.mariadb.jdbc:mariadb-java-client:3.1.3""", StringUtils.join(dependencies_runtime, "\n"));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependency()
throws Exception {
var repos = getNextRepositories();
@ -776,7 +777,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencySources()
throws Exception {
var repos = getNextRepositories();
@ -801,7 +802,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencySourcesJavadoc()
throws Exception {
var repos = getNextRepositories();
@ -827,7 +828,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencySnapshot()
throws Exception {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), List.of(getNextRepository(), RIFE2_SNAPSHOTS), new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 9, 1, "SNAPSHOT")));
@ -849,7 +850,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencySnapshotSources()
throws Exception {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), List.of(getNextRepository(), RIFE2_SNAPSHOTS), new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 9, 1, "SNAPSHOT")));
@ -873,7 +874,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencySnapshotSourcesJavadoc()
throws Exception {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), List.of(getNextRepository(), RIFE2_SNAPSHOTS), new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 9, 1, "SNAPSHOT")));
@ -899,7 +900,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencySourcesModule()
throws Exception {
var repos = getNextRepositories();
@ -924,7 +925,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencySourcesJavadocModule()
throws Exception {
var repos = getNextRepositories();
@ -950,7 +951,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencySnapshotModule()
throws Exception {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), List.of(getNextRepository(), RIFE2_SNAPSHOTS), new Module("com.uwyn.rife2", "rife2", new VersionNumber(1, 9, 1, "SNAPSHOT")));
@ -972,7 +973,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencySnapshotSourcesModule()
throws Exception {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), List.of(getNextRepository(), RIFE2_SNAPSHOTS), new Module("com.uwyn.rife2", "rife2", new VersionNumber(1, 9, 1, "SNAPSHOT")));
@ -996,7 +997,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencySnapshotSourcesJavadocModule()
throws Exception {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), List.of(SONATYPE_SNAPSHOTS, RIFE2_SNAPSHOTS), new Module("com.uwyn.rife2", "rife2", new VersionNumber(1, 9, 1, "SNAPSHOT")));
@ -1022,7 +1023,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyJetty()
throws Exception {
var repos = getNextRepositories();
@ -1059,7 +1060,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyJettyModule()
throws Exception {
var repos = getNextRepositories();
@ -1096,7 +1097,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencySwagger()
throws Exception {
var repos = getNextRepositories();
@ -1149,7 +1150,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyGoogleApi()
throws Exception {
var repos = getNextRepositories();
@ -1222,7 +1223,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyGoogleApiModule()
throws Exception {
var repos = getNextRepositories();
@ -1295,7 +1296,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyJettyOverriddenVersions()
throws Exception {
var repos = getNextRepositories();
@ -1336,7 +1337,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyJettyOverriddenVersionsModule()
throws Exception {
var repos = getNextRepositories();
@ -1377,7 +1378,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyJettySources()
throws Exception {
var repos = getNextRepositories();
@ -1426,7 +1427,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyJettySourcesModule()
throws Exception {
var repos = getNextRepositories();
@ -1475,7 +1476,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyJettySourcesJavadoc()
throws Exception {
var repos = getNextRepositories();
@ -1536,7 +1537,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyJettySourcesJavadocModule()
throws Exception {
var repos = getNextRepositories();
@ -1597,7 +1598,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependenciesJunit()
throws Exception {
var repos = getNextRepositories();
@ -1638,7 +1639,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencySpringBoot()
throws Exception {
var repos = getNextRepositories();
@ -1699,7 +1700,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyMaven()
throws Exception {
var repos = getNextRepositories();
@ -1788,7 +1789,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyPlay()
throws Exception {
var repos = getNextRepositories();
@ -1909,7 +1910,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyPlayOverriddenVersions()
throws Exception {
var repos = getNextRepositories();
@ -2031,7 +2032,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferDependencyVaadin()
throws Exception {
var repos = getNextRepositories();
@ -2231,7 +2232,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferCheckExisting()
throws Exception {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Dependency("org.eclipse.jetty", "jetty-server", new VersionNumber(11, 0, 14)));
@ -2289,7 +2290,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testTransferCheckExistingModule()
throws Exception {
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), getNextRepositories(), new Module("org.eclipse.jetty", "jetty-server", new VersionNumber(11, 0, 14)));
@ -2347,7 +2348,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetAllDependenciesParallelPomPrefetching() throws Exception {
var max_concurrent_retrievals = new AtomicInteger();
var server = createPomServer(max_concurrent_retrievals);
@ -2373,7 +2374,7 @@ public class TestDependencyResolver {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testGetAllDependenciesSequentialWithoutCachingRetriever() throws Exception {
var max_concurrent_retrievals = new AtomicInteger();
var server = createPomServer(max_concurrent_retrievals);
@ -2405,19 +2406,23 @@ public class TestDependencyResolver {
try {
// delay the response so that parallel retrievals overlap
Thread.sleep(100);
// serve the POM of the artifact in the request path
var segments = exchange.getRequestURI().getPath().split("/");
var artifact = segments[segments.length - 3];
var body = buildPom(artifact, graph.getOrDefault(artifact, List.of())).getBytes();
exchange.sendResponseHeaders(200, body.length);
exchange.getResponseBody().write(body);
exchange.close();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
active_retrievals.decrementAndGet();
}
// serve the POM of the artifact in the request path
var segments = exchange.getRequestURI().getPath().split("/");
var artifact = segments[segments.length - 3];
var body = buildPom(artifact, graph.getOrDefault(artifact, List.of())).getBytes();
// stop counting before the response is released, a sequential
// client sends its next request the moment the response
// arrives, even while this handler is still finishing up
active_retrievals.decrementAndGet();
exchange.sendResponseHeaders(200, body.length);
exchange.getResponseBody().write(body);
exchange.close();
});
server.setExecutor(Executors.newCachedThreadPool());
return server;

View file

@ -0,0 +1,248 @@
/*
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.bld.dependencies;
import com.sun.net.httpserver.HttpServer;
import org.junit.jupiter.api.Test;
import rife.bld.dependencies.exceptions.ArtifactNotFoundException;
import rife.bld.dependencies.exceptions.ArtifactRetrievalErrorException;
import rife.bld.dependencies.exceptions.DependencyTransferException;
import rife.ioc.HierarchicalProperties;
import rife.tools.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import static org.junit.jupiter.api.Assertions.*;
import static rife.bld.dependencies.Scope.*;
/**
* A repository that fails with a transient issue is skipped in favor of
* the next one, and transient failures never degrade into not-found.
*/
public class TestRepositoryFallthrough {
private DependencyResolver resolver(List<Repository> repositories, Dependency dependency) {
return new DependencyResolver(new VersionResolution(new HierarchicalProperties()), ArtifactRetriever.instance(), repositories, dependency);
}
@Test
void testMetadataFallsThroughTransientFailure() throws Exception {
var failing = createFailingServer();
var serving = createArtifactServer(Map.of("tool:1.0.0", pom("tool", "1.0.0", "")),
Map.of("tool", metadata("tool", "1.0.0")));
failing.start();
serving.start();
try {
var resolver = resolver(List.of(serverRepository(failing), serverRepository(serving)),
new Dependency("com.example", "tool"));
assertEquals(Version.parse("1.0.0"), resolver.latestVersion());
} finally {
failing.stop(0);
serving.stop(0);
}
}
@Test
void testMetadataAllTransientFailures() throws Exception {
var failing1 = createFailingServer();
var failing2 = createFailingServer();
failing1.start();
failing2.start();
try {
var resolver = resolver(List.of(serverRepository(failing1), serverRepository(failing2)),
new Dependency("com.example", "tool"));
var exception = assertThrows(ArtifactRetrievalErrorException.class, resolver::latestVersion);
// the failure of the second repository is preserved as suppressed
assertEquals(1, exception.getSuppressed().length);
} finally {
failing1.stop(0);
failing2.stop(0);
}
}
@Test
void testMetadataNotFoundStaysNotFound() throws Exception {
var empty1 = createArtifactServer(Map.of(), Map.of());
var empty2 = createArtifactServer(Map.of(), Map.of());
empty1.start();
empty2.start();
try {
var resolver = resolver(List.of(serverRepository(empty1), serverRepository(empty2)),
new Dependency("com.example", "tool"));
assertThrows(ArtifactNotFoundException.class, resolver::latestVersion);
} finally {
empty1.stop(0);
empty2.stop(0);
}
}
@Test
void testPomFallsThroughTransientFailure() throws Exception {
var failing = createFailingServer();
var serving = createArtifactServer(Map.of(
"tool:1.0.0", pom("tool", "1.0.0", dependency("liba", "1.1.0")),
"liba:1.1.0", pom("liba", "1.1.0", "")), Map.of());
failing.start();
serving.start();
try {
var resolver = resolver(List.of(serverRepository(failing), serverRepository(serving)),
new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
var dependencies = resolver.getAllDependencies(compile);
assertEquals(2, dependencies.size());
} finally {
failing.stop(0);
serving.stop(0);
}
}
@Test
void testPomAllTransientFailures() throws Exception {
var failing = createFailingServer();
failing.start();
try {
var resolver = resolver(List.of(serverRepository(failing)),
new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
assertThrows(ArtifactRetrievalErrorException.class, () -> resolver.getAllDependencies(compile));
} finally {
failing.stop(0);
}
}
@Test
void testTransferFallsThroughTransientFailure() throws Exception {
var failing = createFailingServer();
var serving = createArtifactServer(Map.of("tool:1.0.0", pom("tool", "1.0.0", "")), Map.of());
failing.start();
serving.start();
var tmp = Files.createTempDirectory("fallthrough").toFile();
try {
var resolver = resolver(List.of(serverRepository(failing), serverRepository(serving)),
new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
var artifact = resolver.transferIntoDirectory(tmp);
assertNotNull(artifact);
assertTrue(new File(tmp, "tool-1.0.0.jar").exists());
} finally {
failing.stop(0);
serving.stop(0);
FileUtils.deleteDirectory(tmp);
}
}
@Test
void testTransferAllTransientFailures() throws Exception {
var failing = createFailingServer();
failing.start();
var tmp = Files.createTempDirectory("fallthrough").toFile();
try {
var resolver = resolver(List.of(serverRepository(failing)),
new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
// a transient failure surfaces as an exception, not as the
// null result that signals not-found
assertThrows(DependencyTransferException.class, () -> resolver.transferIntoDirectory(tmp));
} finally {
failing.stop(0);
FileUtils.deleteDirectory(tmp);
}
}
@Test
void testTransferNotFoundReturnsNull() throws Exception {
var empty = createArtifactServer(Map.of(), Map.of());
empty.start();
var tmp = Files.createTempDirectory("fallthrough").toFile();
try {
var resolver = resolver(List.of(serverRepository(empty)),
new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
assertNull(resolver.transferIntoDirectory(tmp));
} finally {
empty.stop(0);
FileUtils.deleteDirectory(tmp);
}
}
private static Repository serverRepository(HttpServer server) {
return new Repository("http://localhost:" + server.getAddress().getPort() + "/");
}
private static HttpServer createFailingServer()
throws IOException {
var server = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
server.createContext("/", exchange -> {
exchange.sendResponseHeaders(500, -1);
exchange.close();
});
server.setExecutor(Executors.newCachedThreadPool());
return server;
}
private static HttpServer createArtifactServer(Map<String, String> poms, Map<String, String> metadata)
throws IOException {
var server = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
server.createContext("/", exchange -> {
var segments = exchange.getRequestURI().getPath().split("/");
var filename = segments[segments.length - 1];
byte[] body = null;
if (filename.endsWith(".pom") && segments.length >= 3) {
var content = poms.get(segments[segments.length - 3] + ":" + segments[segments.length - 2]);
if (content != null) {
body = content.getBytes();
}
} else if (filename.endsWith(".jar") && segments.length >= 3) {
if (poms.containsKey(segments[segments.length - 3] + ":" + segments[segments.length - 2])) {
body = "jar".getBytes();
}
} else if (filename.equals("maven-metadata.xml") && segments.length >= 2) {
var content = metadata.get(segments[segments.length - 2]);
if (content != null) {
body = content.getBytes();
}
}
if (body == null) {
exchange.sendResponseHeaders(404, -1);
} else {
exchange.sendResponseHeaders(200, body.length);
exchange.getResponseBody().write(body);
}
exchange.close();
});
server.setExecutor(Executors.newCachedThreadPool());
return server;
}
private static String dependency(String artifact, String version) {
return "<dependency><groupId>com.example</groupId><artifactId>" + artifact + "</artifactId><version>" + version + "</version></dependency>";
}
private static String pom(String artifact, String version, String dependencies) {
return """
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>%s</artifactId>
<version>%s</version>
<dependencies>%s</dependencies>
</project>""".formatted(artifact, version, dependencies);
}
private static String metadata(String artifact, String version) {
return """
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>com.example</groupId>
<artifactId>%s</artifactId>
<versioning>
<latest>%s</latest>
<release>%s</release>
<versions><version>%s</version></versions>
</versioning>
</metadata>""".formatted(artifact, version, version, version);
}
}

View file

@ -5,6 +5,7 @@
package rife.bld.operations;
import org.junit.jupiter.api.Test;
import rife.bld.testing.RetryTest;
import rife.bld.BldVersion;
import rife.bld.WebProject;
import rife.bld.dependencies.*;
@ -14,6 +15,7 @@ import rife.tools.FileUtils;
import rife.tools.StringUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
@ -64,7 +66,7 @@ public class TestDependencyTreeOperation {
assertTrue(operation3.repositories().contains(repository2));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testExecution()
throws Exception {
var tmp = Files.createTempDirectory("test").toFile();
@ -137,7 +139,7 @@ public class TestDependencyTreeOperation {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testExecutionProvidedTest()
throws Exception {
var tmp = Files.createTempDirectory("test").toFile();
@ -215,7 +217,7 @@ public class TestDependencyTreeOperation {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testFromProject()
throws Exception {
var tmp = Files.createTempDirectory("test").toFile();
@ -292,7 +294,7 @@ public class TestDependencyTreeOperation {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testFromProjectProvidedTest()
throws Exception {
var tmp = Files.createTempDirectory("test").toFile();
@ -367,7 +369,7 @@ public class TestDependencyTreeOperation {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testFromProjectExtensions()
throws Exception {
var tmp = Files.createTempDirectory("test").toFile();

View file

@ -5,12 +5,14 @@
package rife.bld.operations;
import org.junit.jupiter.api.Test;
import rife.bld.testing.RetryTest;
import rife.bld.WebProject;
import rife.bld.dependencies.*;
import rife.bld.dependencies.Module;
import rife.tools.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
@ -120,7 +122,7 @@ public class TestDownloadOperation {
assertTrue(operation3.repositories().contains(repository2));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testExecution()
throws Exception {
var tmp = Files.createTempDirectory("test").toFile();
@ -208,7 +210,7 @@ public class TestDownloadOperation {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testExecutionAdditionalSourcesJavadoc()
throws Exception {
var tmp = Files.createTempDirectory("test").toFile();
@ -352,7 +354,7 @@ public class TestDownloadOperation {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testFromProject()
throws Exception {
var tmp = Files.createTempDirectory("test").toFile();
@ -460,7 +462,7 @@ public class TestDownloadOperation {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testFromProject2()
throws Exception {
var tmp = Files.createTempDirectory("test").toFile();

View file

@ -5,12 +5,14 @@
package rife.bld.operations;
import org.junit.jupiter.api.Test;
import rife.bld.testing.RetryTest;
import rife.bld.WebProject;
import rife.bld.dependencies.*;
import rife.bld.dependencies.Module;
import rife.tools.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
@ -115,7 +117,7 @@ public class TestPurgeOperation {
assertTrue(operation3.repositories().contains(repository2));
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testExecution()
throws Exception {
var tmp = Files.createTempDirectory("test").toFile();
@ -325,7 +327,7 @@ public class TestPurgeOperation {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testExecutionAdditionalSourcesJavadoc()
throws Exception {
var tmp = Files.createTempDirectory("test").toFile();
@ -798,7 +800,7 @@ public class TestPurgeOperation {
}
}
@Test
@RetryTest(value = 3, delay = 2, withExceptions = IOException.class)
void testFromProject()
throws Exception {
var tmp = Files.createTempDirectory("test").toFile();

View file

@ -0,0 +1,66 @@
/*
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
*/
package rife.bld.wrapper;
import org.junit.jupiter.api.Test;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.*;
public class TestWrapperRetry {
@Test
void testSucceedsWithoutRetry() throws Exception {
var attempts = new AtomicInteger();
var result = Wrapper.retryOnTransientIoIssues(() -> {
attempts.incrementAndGet();
return "ok";
}, 3, 1);
assertEquals("ok", result);
assertEquals(1, attempts.get());
}
@Test
void testRetriesTransientIoIssues() throws Exception {
// the first two attempts fail with a transient issue, the third
// succeeds
var attempts = new AtomicInteger();
var result = Wrapper.retryOnTransientIoIssues(() -> {
if (attempts.incrementAndGet() < 3) {
throw new IOException("Connection timed out");
}
return "ok";
}, 3, 1);
assertEquals("ok", result);
assertEquals(3, attempts.get());
}
@Test
void testThrowsAfterExhaustedAttempts() {
var attempts = new AtomicInteger();
var exception = assertThrows(IOException.class, () ->
Wrapper.retryOnTransientIoIssues(() -> {
attempts.incrementAndGet();
throw new IOException("Connection timed out");
}, 3, 1));
assertEquals("Connection timed out", exception.getMessage());
assertEquals(3, attempts.get());
}
@Test
void testDoesntRetryMissingResources() {
// a 404 is not transient, retrying it would only slow down the
// fallback paths that rely on a quick failure
var attempts = new AtomicInteger();
assertThrows(FileNotFoundException.class, () ->
Wrapper.retryOnTransientIoIssues(() -> {
attempts.incrementAndGet();
throw new FileNotFoundException("http://localhost/absent");
}, 3, 1));
assertEquals(1, attempts.get());
}
}