From 858ec54247a5538dbf8cc09b3b81014303db35f9 Mon Sep 17 00:00:00 2001
From: Kirill Artamonov
Date: Sun, 31 May 2020 14:30:21 +0200
Subject: [PATCH 01/22] :arrow_up: latest default LocalStack container
---
.../localstack/LocalStackContainer.java | 31 +++++++++++---
.../localstack/LocalstackContainerTest.java | 40 +++++++++++++------
.../LocalstackLegacyContainerTest.java | 30 ++++++++++++++
3 files changed, 83 insertions(+), 18 deletions(-)
create mode 100644 modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackLegacyContainerTest.java
diff --git a/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java
index 72eb320c729..739f751984c 100644
--- a/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java
+++ b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java
@@ -31,10 +31,13 @@
*/
public class LocalStackContainer extends GenericContainer {
- public static final String VERSION = "0.10.8";
+ public static final String VERSION = "0.11.2";
+ static final int PORT = 4566;
private static final String HOSTNAME_EXTERNAL_ENV_VAR = "HOSTNAME_EXTERNAL";
+ private static final String BEFORE_0_11 = "^0\\.([1-9]|10)\\.[0-9]+$";
private final List services = new ArrayList<>();
+ private final String version;
public LocalStackContainer() {
this(VERSION);
@@ -42,6 +45,7 @@ public LocalStackContainer() {
public LocalStackContainer(String version) {
super(TestcontainersConfiguration.getInstance().getLocalStackImage() + ":" + version);
+ this.version = version;
withFileSystemBind("//var/run/docker.sock", "/var/run/docker.sock");
waitingFor(Wait.forLogMessage(".*Ready\\.\n", 1));
@@ -68,9 +72,14 @@ protected void configure() {
}
logger().info("{} environment variable set to {} ({})", HOSTNAME_EXTERNAL_ENV_VAR, getEnvMap().get(HOSTNAME_EXTERNAL_ENV_VAR), hostnameExternalReason);
- for (Service service : services) {
- addExposedPort(service.getPort());
- }
+ exposePorts();
+ }
+
+ private void exposePorts() {
+ services.stream()
+ .map(this::getServicePort)
+ .distinct()
+ .forEach(this::addExposedPort);
}
/**
@@ -141,12 +150,16 @@ public URI getEndpointOverride(Service service) {
return new URI("http://" +
ipAddress +
":" +
- getMappedPort(service.getPort()));
+ getMappedPort(getServicePort(service)));
} catch (UnknownHostException | URISyntaxException e) {
throw new IllegalStateException("Cannot obtain endpoint URL", e);
}
}
+ private int getServicePort(Service service) {
+ return version.matches(BEFORE_0_11) ? service.port : PORT;
+ }
+
/**
* Provides a {@link AWSCredentialsProvider} that is preconfigured to communicate with a given simulated service.
* The credentials provider should be set in the AWS Java SDK when building a client, e.g.:
@@ -258,5 +271,13 @@ public enum Service {
String localStackName;
int port;
+
+ @Deprecated
+ /*
+ Since version 0.11, LocalStack exposes all services on a single (4566) port.
+ */
+ public int getPort() {
+ return port;
+ }
}
}
diff --git a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java
index d4e2f0c2864..4e2287007fc 100644
--- a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java
+++ b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java
@@ -43,6 +43,7 @@
import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
import static org.rnorth.visibleassertions.VisibleAssertions.assertThat;
import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
+import static org.testcontainers.containers.localstack.LocalStackContainer.PORT;
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.*;
/**
@@ -117,7 +118,7 @@ public void sqsTestOverBridgeNetwork() {
CreateQueueResult queueResult = sqs.createQueue("baz");
String fooQueueUrl = queueResult.getQueueUrl();
assertThat("Created queue has external hostname URL", fooQueueUrl,
- containsString("http://" + DockerClientFactory.instance().dockerHostIpAddress() + ":" + localstack.getMappedPort(SQS.getPort())));
+ containsString("http://" + DockerClientFactory.instance().dockerHostIpAddress() + ":" + localstack.getMappedPort(PORT)));
sqs.sendMessage(fooQueueUrl, "test");
final long messageCount = sqs.receiveMessage(fooQueueUrl).getMessages().stream()
@@ -129,8 +130,8 @@ public void sqsTestOverBridgeNetwork() {
@Test
public void cloudWatchLogsTestOverBridgeNetwork() {
AWSLogs logs = AWSLogsClientBuilder.standard()
- .withEndpointConfiguration(localstack.getEndpointConfiguration(CLOUDWATCHLOGS))
- .withCredentials(localstack.getDefaultCredentialsProvider()).build();
+ .withEndpointConfiguration(localstack.getEndpointConfiguration(CLOUDWATCHLOGS))
+ .withCredentials(localstack.getDefaultCredentialsProvider()).build();
logs.createLogGroup(new CreateLogGroupRequest("foo"));
@@ -153,6 +154,19 @@ public void kmsKeyCreationTest() {
assertEquals("AWS KMS Customer Managed Key should be created ", key.getKeyMetadata().getDescription(), desc);
}
+
+ @Test
+ public void samePortIsExposedForAllServices() {
+ assertTrue("A single port is exposed", localstack.getExposedPorts().size() == 1);
+ assertEquals(
+ "Endpoint overrides are different",
+ localstack.getEndpointOverride(S3).toString(),
+ localstack.getEndpointOverride(SQS).toString());
+ assertEquals(
+ "Endpoint configuration have different endpoints",
+ localstack.getEndpointConfiguration(S3).getServiceEndpoint(),
+ localstack.getEndpointConfiguration(SQS).getServiceEndpoint());
+ }
}
public static class WithNetwork {
@@ -177,33 +191,33 @@ public static class WithNetwork {
@Test
public void s3TestOverDockerNetwork() throws Exception {
- runAwsCliAgainstDockerNetworkContainer("s3api create-bucket --bucket foo", S3.getPort());
- runAwsCliAgainstDockerNetworkContainer("s3api list-buckets", S3.getPort());
- runAwsCliAgainstDockerNetworkContainer("s3 ls s3://foo", S3.getPort());
+ runAwsCliAgainstDockerNetworkContainer("s3api create-bucket --bucket foo");
+ runAwsCliAgainstDockerNetworkContainer("s3api list-buckets");
+ runAwsCliAgainstDockerNetworkContainer("s3 ls s3://foo");
}
@Test
public void sqsTestOverDockerNetwork() throws Exception {
- final String queueCreationResponse = runAwsCliAgainstDockerNetworkContainer("sqs create-queue --queue-name baz", SQS.getPort());
+ final String queueCreationResponse = runAwsCliAgainstDockerNetworkContainer("sqs create-queue --queue-name baz");
assertThat("Created queue has external hostname URL", queueCreationResponse,
- containsString("http://localstack:" + SQS.getPort()));
+ containsString("http://localstack:" + PORT));
runAwsCliAgainstDockerNetworkContainer(
- String.format("sqs send-message --endpoint http://localstack:%d --queue-url http://localstack:%d/queue/baz --message-body test", SQS.getPort(), SQS.getPort()), SQS.getPort());
+ String.format("sqs send-message --endpoint http://localstack:%d --queue-url http://localstack:%d/queue/baz --message-body test", PORT, PORT));
final String message = runAwsCliAgainstDockerNetworkContainer(
- String.format("sqs receive-message --endpoint http://localstack:%d --queue-url http://localstack:%d/queue/baz", SQS.getPort(), SQS.getPort()), SQS.getPort());
+ String.format("sqs receive-message --endpoint http://localstack:%d --queue-url http://localstack:%d/queue/baz", PORT, PORT));
assertTrue("the sent message can be received", message.contains("\"Body\": \"test\""));
}
@Test
public void cloudWatchLogsTestOverDockerNetwork() throws Exception {
- runAwsCliAgainstDockerNetworkContainer("logs create-log-group --log-group-name foo", CLOUDWATCHLOGS.getPort());
+ runAwsCliAgainstDockerNetworkContainer("logs create-log-group --log-group-name foo");
}
- private String runAwsCliAgainstDockerNetworkContainer(String command, final int port) throws Exception {
- final String[] commandParts = String.format("/usr/bin/aws --region eu-west-1 %s --endpoint-url http://localstack:%d --no-verify-ssl", command, port).split(" ");
+ private String runAwsCliAgainstDockerNetworkContainer(String command) throws Exception {
+ final String[] commandParts = String.format("/usr/bin/aws --region eu-west-1 %s --endpoint-url http://localstack:%d --no-verify-ssl", command, PORT).split(" ");
final Container.ExecResult execResult = awsCliInDockerNetwork.execInContainer(commandParts);
Assert.assertEquals(0, execResult.getExitCode());
diff --git a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackLegacyContainerTest.java b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackLegacyContainerTest.java
new file mode 100644
index 00000000000..152623d2316
--- /dev/null
+++ b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackLegacyContainerTest.java
@@ -0,0 +1,30 @@
+package org.testcontainers.containers.localstack;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import static org.rnorth.visibleassertions.VisibleAssertions.assertNotEquals;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
+import static org.testcontainers.containers.localstack.LocalStackContainer.Service.S3;
+import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS;
+
+public class LocalstackLegacyContainerTest {
+
+ @ClassRule
+ public static LocalStackContainer localstack = new LocalStackContainer("0.10.8")
+ .withServices(S3, SQS);
+
+ @Test
+ public void differentPortsAreExposed() {
+ assertTrue("Multiple ports are exposed", localstack.getExposedPorts().size() > 1);
+ assertNotEquals(
+ "Endpoint overrides are different",
+ localstack.getEndpointOverride(S3).toString(),
+ localstack.getEndpointOverride(SQS).toString());
+ assertNotEquals(
+ "Endpoint configuration have different endpoints",
+ localstack.getEndpointConfiguration(S3).getServiceEndpoint(),
+ localstack.getEndpointConfiguration(SQS).getServiceEndpoint());
+ }
+
+}
From b310e3e6e02fdc4e50e4401dd44f75f82a396811 Mon Sep 17 00:00:00 2001
From: Kirill Artamonov
Date: Sun, 7 Jun 2020 00:02:07 +0200
Subject: [PATCH 02/22] :recycle: rely on ComparableVersion for version
comparison. added LocalStackContainer(String version, boolean legacyMode)
constructor. Extended test cases.
---
.../utility/ComparableVersion.java | 4 +
.../localstack/LocalStackContainer.java | 41 ++++++-
.../containers/localstack/LegacyModeTest.java | 109 ++++++++++++++++++
.../LocalstackLegacyContainerTest.java | 30 -----
4 files changed, 148 insertions(+), 36 deletions(-)
create mode 100644 modules/localstack/src/test/java/org/testcontainers/containers/localstack/LegacyModeTest.java
delete mode 100644 modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackLegacyContainerTest.java
diff --git a/core/src/main/java/org/testcontainers/utility/ComparableVersion.java b/core/src/main/java/org/testcontainers/utility/ComparableVersion.java
index 1ffa1642568..d3e3d9eb958 100644
--- a/core/src/main/java/org/testcontainers/utility/ComparableVersion.java
+++ b/core/src/main/java/org/testcontainers/utility/ComparableVersion.java
@@ -32,6 +32,10 @@ public int compareTo(@NotNull ComparableVersion other) {
return 0;
}
+ public boolean isSemanticVersion() {
+ return parts.length > 0;
+ }
+
public boolean isLessThan(String other) {
return this.compareTo(new ComparableVersion(other)) < 0;
}
diff --git a/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java
index 739f751984c..95de64db799 100644
--- a/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java
+++ b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java
@@ -7,9 +7,11 @@
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
+import lombok.extern.slf4j.Slf4j;
import org.rnorth.ducttape.Preconditions;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.ComparableVersion;
import org.testcontainers.utility.TestcontainersConfiguration;
import java.net.InetAddress;
@@ -29,23 +31,50 @@
* {@link LocalStackContainer#getDefaultCredentialsProvider()}
* be used to obtain compatible endpoint configuration and credentials, respectively.
*/
+@Slf4j
public class LocalStackContainer extends GenericContainer {
public static final String VERSION = "0.11.2";
static final int PORT = 4566;
private static final String HOSTNAME_EXTERNAL_ENV_VAR = "HOSTNAME_EXTERNAL";
- private static final String BEFORE_0_11 = "^0\\.([1-9]|10)\\.[0-9]+$";
-
private final List services = new ArrayList<>();
- private final String version;
+ private final boolean legacyMode;
+
+ /**
+ * Uses default ({@value #VERSION}) version of LocalStack container
+ */
public LocalStackContainer() {
- this(VERSION);
+ this(VERSION, false);
}
+ /**
+ * Uses provided version of LocalStack container
+ * Enables legacy mode (each AWS service is exposed on a different port) if provided version is less that 0.11
+ *
+ * @param version tag of LocalStack container to run
+ */
public LocalStackContainer(String version) {
+ this(version, shouldRunInLegacyMode(version));
+ }
+
+ private static boolean shouldRunInLegacyMode(String version) {
+ if (version.equals("latest")) return false;
+ ComparableVersion comparableVersion = new ComparableVersion(version);
+ if (comparableVersion.isSemanticVersion())
+ return comparableVersion.isLessThan("0.11");
+ log.warn("Version {} is not a semantic version, LocalStack will run in legacy mode.", version);
+ log.warn("Consider using \"LocalStackContainer(String version, boolean legacyMode)\" constructor if you want to disable legacy mode.");
+ return true;
+ }
+
+ /**
+ * @param version tag of LocalStack container to run
+ * @param legacyMode if true, each AWS service is exposed on a different port
+ */
+ public LocalStackContainer(String version, boolean legacyMode) {
super(TestcontainersConfiguration.getInstance().getLocalStackImage() + ":" + version);
- this.version = version;
+ this.legacyMode = legacyMode;
withFileSystemBind("//var/run/docker.sock", "/var/run/docker.sock");
waitingFor(Wait.forLogMessage(".*Ready\\.\n", 1));
@@ -157,7 +186,7 @@ public URI getEndpointOverride(Service service) {
}
private int getServicePort(Service service) {
- return version.matches(BEFORE_0_11) ? service.port : PORT;
+ return legacyMode ? service.port : PORT;
}
/**
diff --git a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LegacyModeTest.java b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LegacyModeTest.java
new file mode 100644
index 00000000000..3f864fc841f
--- /dev/null
+++ b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LegacyModeTest.java
@@ -0,0 +1,109 @@
+package org.testcontainers.containers.localstack;
+
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+
+import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertNotEquals;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
+import static org.testcontainers.containers.localstack.LocalStackContainer.Service.S3;
+import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS;
+
+@RunWith(Enclosed.class)
+public class LegacyModeTest {
+
+ @RunWith(Parameterized.class)
+ @AllArgsConstructor
+ public static class Off {
+ private final String version;
+ private final LocalStackContainer localstack;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable constructors() {
+ return Arrays.asList(new Object[][]{
+ {"default constructor", new LocalStackContainer()},
+ {"latest", new LocalStackContainer("latest")},
+ {"0.11.1", new LocalStackContainer("0.11.1")},
+ {"0.7.0 with legacy = off", new LocalStackContainer("0.7.0", false)}
+ });
+ }
+
+ @Test
+ public void samePortIsExposedForAllServices() {
+ localstack.withServices(S3, SQS);
+ localstack.start();
+
+ assertTrue("A single port is exposed", localstack.getExposedPorts().size() == 1);
+ assertEquals(
+ "Endpoint overrides are different",
+ localstack.getEndpointOverride(S3).toString(),
+ localstack.getEndpointOverride(SQS).toString());
+ assertEquals(
+ "Endpoint configuration have different endpoints",
+ localstack.getEndpointConfiguration(S3).getServiceEndpoint(),
+ localstack.getEndpointConfiguration(SQS).getServiceEndpoint());
+ }
+
+ @After
+ public void cleanup() {
+ if (localstack != null) localstack.stop();
+ }
+ }
+
+ @RunWith(Parameterized.class)
+ @AllArgsConstructor
+ public static class On {
+ private final String version;
+ private final LocalStackContainer localstack;
+
+ @BeforeClass
+ public static void createCustomTag() {
+ run("docker pull localstack/localstack:latest");
+ run("docker tag localstack/localstack:latest localstack/localstack:custom");
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable constructors() {
+ return Arrays.asList(new Object[][]{
+ {"0.10.7", new LocalStackContainer("0.10.7")},
+ {"custom", new LocalStackContainer("custom")},
+ {"0.11.1 with legacy = on", new LocalStackContainer("0.11.1", true)}
+ });
+ }
+
+ @Test
+ public void differentPortsAreExposed() {
+ localstack.withServices(S3, SQS);
+ localstack.start();
+
+ assertTrue("Multiple ports are exposed", localstack.getExposedPorts().size() > 1);
+ assertNotEquals(
+ "Endpoint overrides are different",
+ localstack.getEndpointOverride(S3).toString(),
+ localstack.getEndpointOverride(SQS).toString());
+ assertNotEquals(
+ "Endpoint configuration have different endpoints",
+ localstack.getEndpointConfiguration(S3).getServiceEndpoint(),
+ localstack.getEndpointConfiguration(SQS).getServiceEndpoint());
+ }
+
+ @After
+ public void cleanup() {
+ if (localstack != null) localstack.stop();
+ }
+ }
+
+ @SneakyThrows
+ private static void run(String command) {
+ Runtime.getRuntime().exec(command);
+ }
+
+}
diff --git a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackLegacyContainerTest.java b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackLegacyContainerTest.java
deleted file mode 100644
index 152623d2316..00000000000
--- a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackLegacyContainerTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.testcontainers.containers.localstack;
-
-import org.junit.ClassRule;
-import org.junit.Test;
-
-import static org.rnorth.visibleassertions.VisibleAssertions.assertNotEquals;
-import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
-import static org.testcontainers.containers.localstack.LocalStackContainer.Service.S3;
-import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS;
-
-public class LocalstackLegacyContainerTest {
-
- @ClassRule
- public static LocalStackContainer localstack = new LocalStackContainer("0.10.8")
- .withServices(S3, SQS);
-
- @Test
- public void differentPortsAreExposed() {
- assertTrue("Multiple ports are exposed", localstack.getExposedPorts().size() > 1);
- assertNotEquals(
- "Endpoint overrides are different",
- localstack.getEndpointOverride(S3).toString(),
- localstack.getEndpointOverride(SQS).toString());
- assertNotEquals(
- "Endpoint configuration have different endpoints",
- localstack.getEndpointConfiguration(S3).getServiceEndpoint(),
- localstack.getEndpointConfiguration(SQS).getServiceEndpoint());
- }
-
-}
From df1d96e79b84a3bfc029fd92eb6f636099b7c362 Mon Sep 17 00:00:00 2001
From: Kirill Artamonov
Date: Sun, 7 Jun 2020 14:31:30 +0200
Subject: [PATCH 03/22] :white_check_mark: wait for pull and tag commands to
finish
---
.../containers/localstack/LegacyModeTest.java | 20 ++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LegacyModeTest.java b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LegacyModeTest.java
index 3f864fc841f..0e7b6c9cb35 100644
--- a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LegacyModeTest.java
+++ b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LegacyModeTest.java
@@ -9,7 +9,12 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.util.Arrays;
+import java.util.function.Consumer;
import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
import static org.rnorth.visibleassertions.VisibleAssertions.assertNotEquals;
@@ -103,7 +108,20 @@ public void cleanup() {
@SneakyThrows
private static void run(String command) {
- Runtime.getRuntime().exec(command);
+ Process process = Runtime.getRuntime().exec(command);
+ join(process.getInputStream(), System.out::println);
+ join(process.getErrorStream(), System.err::println);
+ process.waitFor();
+ if (process.exitValue() != 0)
+ throw new RuntimeException("Failed to execute " + command);
+ }
+
+ private static void join(InputStream stream, Consumer logger) throws IOException {
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream));
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ logger.accept(line);
+ }
}
}
From 68ee3bcc5e0a5b417139159487687c8bdadd1b9e Mon Sep 17 00:00:00 2001
From: Richard North
Date: Thu, 25 Jun 2020 14:04:35 +0100
Subject: [PATCH 04/22] WIP
---
.../testcontainers/DockerClientFactory.java | 2 +-
.../containers/DockerComposeContainer.java | 4 +-
.../FixedHostPortGenericContainer.java | 1 +
.../containers/GenericContainer.java | 13 ++++
.../containers/PortForwardingContainer.java | 4 +-
.../containers/SocatContainer.java | 10 ++-
.../images/RemoteDockerImage.java | 6 ++
.../utility/DockerImageName.java | 71 ++++++++++++------
.../utility/DockerMachineClient.java | 1 -
.../utility/ResourceReaper.java | 4 +-
.../utility/TestcontainersConfiguration.java | 45 +++++++++++
.../java/org/testcontainers/DaemonTest.java | 4 +-
.../DockerClientFactoryTest.java | 2 +-
.../org/testcontainers/TestingImages.java | 15 ++++
.../containers/ExposedHostTest.java | 28 ++++---
.../FailureDetectingExternalResourceTest.java | 1 +
.../containers/ReusabilityUnitTests.java | 20 +++--
.../containers/output/ContainerLogsTest.java | 5 +-
.../dockerclient/EventStreamTest.java | 4 +-
.../dockerclient/ImagePullTest.java | 3 +-
.../images/ImagePullPolicyTest.java | 22 +++---
.../images/RemoteDockerImageTest.java | 11 +--
.../images/builder/DockerignoreTest.java | 30 ++++----
.../junit/CopyFileToContainerTest.java | 8 +-
.../junit/DependenciesTest.java | 8 +-
.../junit/DockerNetworkModeTest.java | 6 +-
.../junit/FileOperationsTest.java | 11 +--
.../junit/FixedHostPortContainerTest.java | 9 ++-
.../junit/GenericContainerRuleTest.java | 71 ++++++++++++------
.../junit/NonExistentImagePullTest.java | 3 +-
.../junit/OutputStreamTest.java | 7 +-
.../junit/OutputStreamWithTTYTest.java | 7 +-
.../junit/WorkingDirectoryTest.java | 3 +-
.../strategy/AbstractWaitStrategyTest.java | 3 +-
.../strategy/HostPortWaitStrategyTest.java | 3 +-
.../utility/AuthenticatedImagePullTest.java | 4 +-
.../test/java/generic/CmdModifierTest.java | 5 +-
.../src/test/java/generic/CommandsTest.java | 3 +-
.../java/generic/ContainerCreationTest.java | 11 +--
.../test/java/generic/ContainerLabelTest.java | 5 +-
.../src/test/java/generic/ExecTest.java | 3 +-
.../generic/MultiplePortsExposedTest.java | 3 +-
.../test/java/generic/WaitStrategiesTest.java | 7 +-
.../StartupCheckStrategyTest.java | 7 +-
.../quickstart/RedisBackedCacheIntTest.java | 3 +-
.../quickstart/RedisBackedCacheIntTest.java | 3 +-
.../test/java/DisqueBackedMailSenderTest.java | 13 +++-
.../test/java/SingleDisqueInstanceTest.java | 8 +-
examples/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58910 bytes
.../gradle/wrapper/gradle-wrapper.properties | 5 ++
examples/gradlew | 33 ++++----
examples/gradlew.bat | 6 +-
.../src/test/java/RedisBackedCacheTest.java | 6 +-
.../src/test/java/RedisBackedCacheTest.java | 7 +-
.../com/example/AbstractIntegrationTest.java | 3 +-
.../com/example/AbstractIntegrationTest.java | 3 +-
.../containers/CassandraContainer.java | 17 ++++-
.../containers/CassandraContainerTest.java | 29 ++++---
.../containers/ClickHouseContainer.java | 8 ++
.../containers/ClickHouseProvider.java | 4 +-
.../containers/CockroachContainer.java | 7 ++
.../CockroachContainerProvider.java | 4 +-
.../couchbase/CouchbaseContainer.java | 13 +++-
.../couchbase/CouchbaseContainerTest.java | 3 +-
.../containers/Db2Container.java | 11 ++-
.../containers/Db2ContainerProvider.java | 4 +-
.../dynamodb/DynaliteContainer.java | 12 ++-
.../dynamodb/DynaliteContainerTest.java | 11 ++-
.../elasticsearch/ElasticsearchContainer.java | 7 ++
.../ElasticsearchContainerTest.java | 30 ++++----
.../containers/InfluxDBContainer.java | 9 ++-
.../containers/JdbcDatabaseContainer.java | 8 +-
.../jdbc/MissingJdbcDriverTest.java | 3 +-
...ntainersNestedRestartedContainerTests.java | 6 +-
...tcontainersNestedSharedContainerTests.java | 3 +-
.../TestcontainersRestartBetweenTests.java | 3 +-
.../TestcontainersSharedContainerTests.java | 3 +-
.../containers/KafkaContainer.java | 9 ++-
.../containers/KafkaContainerTest.java | 5 +-
.../localstack/LocalStackContainer.java | 9 ++-
.../localstack/LocalstackContainerTest.java | 3 +-
.../containers/MariaDBContainer.java | 8 ++
.../containers/MariaDBContainerProvider.java | 5 +-
.../MariaDBR2DBCDatabaseContainerTest.java | 3 +-
.../containers/MongoDBContainer.java | 7 ++
.../containers/MSSQLServerContainer.java | 7 ++
.../MSSQLServerContainerProvider.java | 4 +-
.../MSSQLR2DBCDatabaseContainerTest.java | 3 +-
.../CustomPasswordMSSQLServerTest.java | 3 +-
.../CustomizableMSSQLServerTest.java | 3 +-
.../containers/MySQLContainer.java | 10 ++-
.../containers/MySQLContainerProvider.java | 5 +-
.../MySQLR2DBCDatabaseContainerTest.java | 3 +-
.../junit/mysql/CustomizableMysqlTest.java | 3 +-
.../containers/Neo4jContainer.java | 21 ++++--
.../containers/NginxContainer.java | 9 ++-
.../containers/OracleContainer.java | 7 ++
.../containers/OrientDBContainer.java | 7 ++
.../containers/PostgisContainerProvider.java | 5 +-
.../containers/PostgreSQLContainer.java | 7 ++
.../PostgreSQLContainerProvider.java | 5 +-
.../PostgreSQLR2DBCDatabaseContainerTest.java | 3 +-
.../CustomizablePostgreSQLTest.java | 3 +-
.../containers/PrestoContainer.java | 7 ++
.../containers/PrestoContainerProvider.java | 5 +-
.../containers/PulsarContainer.java | 14 +++-
.../containers/RabbitMQContainer.java | 13 +++-
.../containers/BrowserWebDriverContainer.java | 13 ++--
...ChromeRecordingWebDriverContainerTest.java | 1 +
.../containers/SolrContainer.java | 19 +++--
.../containers/ToxiproxyContainer.java | 11 ++-
.../containers/ToxiproxyTest.java | 11 ++-
.../testcontainers/vault/VaultContainer.java | 7 ++
113 files changed, 735 insertions(+), 301 deletions(-)
create mode 100644 core/src/test/java/org/testcontainers/TestingImages.java
create mode 100644 examples/gradle/wrapper/gradle-wrapper.jar
create mode 100644 examples/gradle/wrapper/gradle-wrapper.properties
diff --git a/core/src/main/java/org/testcontainers/DockerClientFactory.java b/core/src/main/java/org/testcontainers/DockerClientFactory.java
index 50c0f3d021c..0b2af3dc408 100644
--- a/core/src/main/java/org/testcontainers/DockerClientFactory.java
+++ b/core/src/main/java/org/testcontainers/DockerClientFactory.java
@@ -55,7 +55,7 @@ public class DockerClientFactory {
TESTCONTAINERS_SESSION_ID_LABEL, SESSION_ID
);
- private static final String TINY_IMAGE = TestcontainersConfiguration.getInstance().getTinyImage();
+ private static final String TINY_IMAGE = TestcontainersConfiguration.getInstance().getTinyDockerImageName().toString();
private static DockerClientFactory instance;
// Cached client configuration
diff --git a/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java b/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java
index c3fe9ab17f9..6a83e5f6a1d 100644
--- a/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java
+++ b/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java
@@ -87,7 +87,7 @@ public class DockerComposeContainer> e
private final Map> ambassadorPortMappings = new ConcurrentHashMap<>();
private final Map serviceInstanceMap = new ConcurrentHashMap<>();
private final Map waitStrategyMap = new ConcurrentHashMap<>();
- private final SocatContainer ambassadorContainer = new SocatContainer();
+ private final SocatContainer ambassadorContainer = new SocatContainer(TestcontainersConfiguration.getInstance().getSocatDockerImageName());
private final Map>> logConsumers = new ConcurrentHashMap<>();
private static final Object MUTEX = new Object();
@@ -585,7 +585,7 @@ class ContainerisedDockerCompose extends GenericContainer composeFiles, String identifier) {
- super(TestcontainersConfiguration.getInstance().getDockerComposeContainerImage());
+ super(TestcontainersConfiguration.getInstance().getDockerComposeDockerImageName());
addEnv(ENV_PROJECT_NAME, identifier);
// Map the docker compose file into the container
diff --git a/core/src/main/java/org/testcontainers/containers/FixedHostPortGenericContainer.java b/core/src/main/java/org/testcontainers/containers/FixedHostPortGenericContainer.java
index 33c1c8e89bd..d679023f342 100644
--- a/core/src/main/java/org/testcontainers/containers/FixedHostPortGenericContainer.java
+++ b/core/src/main/java/org/testcontainers/containers/FixedHostPortGenericContainer.java
@@ -12,6 +12,7 @@
* not available - which could manifest as flaky or unstable tests.
*/
public class FixedHostPortGenericContainer> extends GenericContainer {
+ @Deprecated
public FixedHostPortGenericContainer(@NotNull String dockerImageName) {
super(dockerImageName);
}
diff --git a/core/src/main/java/org/testcontainers/containers/GenericContainer.java b/core/src/main/java/org/testcontainers/containers/GenericContainer.java
index 072129de60c..8d31542d0eb 100644
--- a/core/src/main/java/org/testcontainers/containers/GenericContainer.java
+++ b/core/src/main/java/org/testcontainers/containers/GenericContainer.java
@@ -54,6 +54,7 @@
import org.testcontainers.lifecycle.TestDescription;
import org.testcontainers.lifecycle.TestLifecycleAware;
import org.testcontainers.utility.Base58;
+import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.DockerLoggerFactory;
import org.testcontainers.utility.DockerMachineClient;
import org.testcontainers.utility.MountableFile;
@@ -224,10 +225,22 @@ public class GenericContainer>
@Setter(AccessLevel.NONE)
private boolean shouldBeReused = false;
+
+ public GenericContainer(@NonNull final DockerImageName dockerImageName) {
+ this.image = new RemoteDockerImage(dockerImageName);
+ }
+
+ public GenericContainer(@NonNull final RemoteDockerImage image) {
+ this.image = image;
+ }
+
+
+ @Deprecated
public GenericContainer() {
this(TestcontainersConfiguration.getInstance().getTinyImage());
}
+ @Deprecated
public GenericContainer(@NonNull final String dockerImageName) {
this.setDockerImageName(dockerImageName);
}
diff --git a/core/src/main/java/org/testcontainers/containers/PortForwardingContainer.java b/core/src/main/java/org/testcontainers/containers/PortForwardingContainer.java
index 09f1d98bd2d..e42f2681675 100644
--- a/core/src/main/java/org/testcontainers/containers/PortForwardingContainer.java
+++ b/core/src/main/java/org/testcontainers/containers/PortForwardingContainer.java
@@ -19,7 +19,7 @@
public enum PortForwardingContainer {
INSTANCE;
- private GenericContainer container;
+ private GenericContainer> container;
private final Set> exposedPorts = Collections.newSetFromMap(new ConcurrentHashMap<>());
@@ -29,7 +29,7 @@ public enum PortForwardingContainer {
@SneakyThrows
private Connection createSSHSession() {
String password = UUID.randomUUID().toString();
- container = new GenericContainer<>(TestcontainersConfiguration.getInstance().getSSHdImage())
+ container = new GenericContainer<>(TestcontainersConfiguration.getInstance().getSSHdDockerImageName())
.withExposedPorts(22)
.withEnv("PASSWORD", password)
.withCommand(
diff --git a/core/src/main/java/org/testcontainers/containers/SocatContainer.java b/core/src/main/java/org/testcontainers/containers/SocatContainer.java
index 5949a14490d..bcf2b1670fd 100644
--- a/core/src/main/java/org/testcontainers/containers/SocatContainer.java
+++ b/core/src/main/java/org/testcontainers/containers/SocatContainer.java
@@ -1,6 +1,7 @@
package org.testcontainers.containers;
import org.testcontainers.utility.Base58;
+import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.TestcontainersConfiguration;
import java.util.HashMap;
@@ -15,8 +16,13 @@ public class SocatContainer extends GenericContainer {
private final Map targets = new HashMap<>();
+ @Deprecated
public SocatContainer() {
- super(TestcontainersConfiguration.getInstance().getSocatContainerImage());
+ this(new DockerImageName(TestcontainersConfiguration.getInstance().getSocatContainerImage()));
+ }
+
+ public SocatContainer(final DockerImageName dockerImageName) {
+ super(dockerImageName);
withCreateContainerCmdModifier(it -> it.withEntrypoint("/bin/sh"));
withCreateContainerCmdModifier(it -> it.withName("testcontainers-socat-" + Base58.randomString(8)));
}
@@ -39,4 +45,4 @@ protected void configure() {
.collect(Collectors.joining(" & "))
);
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/testcontainers/images/RemoteDockerImage.java b/core/src/main/java/org/testcontainers/images/RemoteDockerImage.java
index d0e1313edf6..7e1faa8752f 100644
--- a/core/src/main/java/org/testcontainers/images/RemoteDockerImage.java
+++ b/core/src/main/java/org/testcontainers/images/RemoteDockerImage.java
@@ -38,10 +38,16 @@ public class RemoteDockerImage extends LazyFuture {
@ToString.Exclude
private DockerClient dockerClient = DockerClientFactory.lazyClient();
+ public RemoteDockerImage(DockerImageName dockerImageName) {
+ this.imageNameFuture = CompletableFuture.completedFuture(dockerImageName);
+ }
+
+ @Deprecated
public RemoteDockerImage(String dockerImageName) {
this.imageNameFuture = CompletableFuture.completedFuture(new DockerImageName(dockerImageName));
}
+ @Deprecated
public RemoteDockerImage(@NonNull String repository, @NonNull String tag) {
this.imageNameFuture = CompletableFuture.completedFuture(new DockerImageName(repository, tag));
}
diff --git a/core/src/main/java/org/testcontainers/utility/DockerImageName.java b/core/src/main/java/org/testcontainers/utility/DockerImageName.java
index d57f1044e80..970237b66db 100644
--- a/core/src/main/java/org/testcontainers/utility/DockerImageName.java
+++ b/core/src/main/java/org/testcontainers/utility/DockerImageName.java
@@ -2,12 +2,15 @@
import com.google.common.net.HostAndPort;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.regex.Pattern;
@EqualsAndHashCode(exclude = "rawName")
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
public final class DockerImageName {
/* Regex patterns used for validation */
@@ -21,20 +24,27 @@ public final class DockerImageName {
private final String repo;
private final Versioning versioning;
- public DockerImageName(String name) {
- this.rawName = name;
- final int slashIndex = name.indexOf('/');
+ /**
+ * Parses a docker image name from a provided string.
+ *
+ * @param fullImageName in standard Docker format, e.g. name:tag,
+ * some.registry/path/name:tag,
+ * some.registry/path/name@sha256:abcdef..., etc.
+ */
+ public DockerImageName(String fullImageName) {
+ this.rawName = fullImageName;
+ final int slashIndex = fullImageName.indexOf('/');
String remoteName;
if (slashIndex == -1 ||
- (!name.substring(0, slashIndex).contains(".") &&
- !name.substring(0, slashIndex).contains(":") &&
- !name.substring(0, slashIndex).equals("localhost"))) {
+ (!fullImageName.substring(0, slashIndex).contains(".") &&
+ !fullImageName.substring(0, slashIndex).contains(":") &&
+ !fullImageName.substring(0, slashIndex).equals("localhost"))) {
registry = "";
- remoteName = name;
+ remoteName = fullImageName;
} else {
- registry = name.substring(0, slashIndex);
- remoteName = name.substring(slashIndex + 1);
+ registry = fullImageName.substring(0, slashIndex);
+ remoteName = fullImageName.substring(slashIndex + 1);
}
if (remoteName.contains("@sha256:")) {
@@ -49,28 +59,38 @@ public DockerImageName(String name) {
}
}
- public DockerImageName(String name, String tag) {
- this.rawName = name;
- final int slashIndex = name.indexOf('/');
+ /**
+ * Parses a docker image name from a provided string, and uses a separate provided version.
+ *
+ * @param nameWithoutTag in standard Docker format, e.g. name,
+ * some.registry/path/name,
+ * some.registry/path/name, etc.
+ * @param version a docker image version identifier, either as a tag or sha256 checksum, e.g.
+ * tag,
+ * sha256:abcdef....
+ */
+ public DockerImageName(String nameWithoutTag, String version) {
+ this.rawName = nameWithoutTag;
+ final int slashIndex = nameWithoutTag.indexOf('/');
String remoteName;
if (slashIndex == -1 ||
- (!name.substring(0, slashIndex).contains(".") &&
- !name.substring(0, slashIndex).contains(":") &&
- !name.substring(0, slashIndex).equals("localhost"))) {
+ (!nameWithoutTag.substring(0, slashIndex).contains(".") &&
+ !nameWithoutTag.substring(0, slashIndex).contains(":") &&
+ !nameWithoutTag.substring(0, slashIndex).equals("localhost"))) {
registry = "";
- remoteName = name;
+ remoteName = nameWithoutTag;
} else {
- registry = name.substring(0, slashIndex);
- remoteName = name.substring(slashIndex + 1);
+ registry = nameWithoutTag.substring(0, slashIndex);
+ remoteName = nameWithoutTag.substring(slashIndex + 1);
}
- if (tag.startsWith("sha256:")) {
+ if (version.startsWith("sha256:")) {
repo = remoteName;
- versioning = new Sha256Versioning(tag.replace("sha256:", ""));
+ versioning = new Sha256Versioning(version.replace("sha256:", ""));
} else {
repo = remoteName;
- versioning = new TagVersioning(tag);
+ versioning = new TagVersioning(version);
}
}
@@ -113,7 +133,7 @@ public void assertValid() {
}
if (versioning == null) {
throw new IllegalArgumentException("No image tag was specified in docker image name " +
- "(" + rawName + "). Please provide a tag; this may be 'latest' or a specific version");
+ "(" + rawName + "). Please provide a tag; this may be 'latest' or a specific version");
}
if (!versioning.isValid()) {
throw new IllegalArgumentException(versioning + " is not a valid image versioning identifier (in " + rawName + ")");
@@ -124,8 +144,13 @@ public String getRegistry() {
return registry;
}
+ public DockerImageName withTag(final String newTag) {
+ return new DockerImageName(rawName, registry, repo, new TagVersioning(newTag));
+ }
+
private interface Versioning {
boolean isValid();
+
String getSeparator();
}
@@ -155,7 +180,7 @@ public String toString() {
}
@Data
- private class Sha256Versioning implements Versioning {
+ private static class Sha256Versioning implements Versioning {
public static final String HASH_REGEX = "[0-9a-fA-F]{32,}";
private final String hash;
diff --git a/core/src/main/java/org/testcontainers/utility/DockerMachineClient.java b/core/src/main/java/org/testcontainers/utility/DockerMachineClient.java
index 240a6d5afb4..fe12e9f541d 100644
--- a/core/src/main/java/org/testcontainers/utility/DockerMachineClient.java
+++ b/core/src/main/java/org/testcontainers/utility/DockerMachineClient.java
@@ -1,7 +1,6 @@
package org.testcontainers.utility;
import lombok.NonNull;
-
import org.apache.commons.lang.SystemUtils;
import org.slf4j.Logger;
diff --git a/core/src/main/java/org/testcontainers/utility/ResourceReaper.java b/core/src/main/java/org/testcontainers/utility/ResourceReaper.java
index 5d89ab886e7..27d70bf6926 100644
--- a/core/src/main/java/org/testcontainers/utility/ResourceReaper.java
+++ b/core/src/main/java/org/testcontainers/utility/ResourceReaper.java
@@ -14,7 +14,6 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.collect.Sets;
-
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.rnorth.ducttape.ratelimits.RateLimiter;
@@ -22,6 +21,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.DockerClientFactory;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -72,7 +72,7 @@ private ResourceReaper() {
@SneakyThrows(InterruptedException.class)
public static String start(String hostIpAddress, DockerClient client) {
- String ryukImage = TestcontainersConfiguration.getInstance().getRyukImage();
+ String ryukImage = TestcontainersConfiguration.getInstance().getRyukDockerImageName().toString();
DockerClientFactory.instance().checkAndPullImage(client, ryukImage);
List binds = new ArrayList<>();
diff --git a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java
index aa829573447..0409199c649 100644
--- a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java
+++ b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java
@@ -63,50 +63,95 @@ public String getAmbassadorContainerImage() {
return (String) properties.getOrDefault("ambassador.container.image", "richnorth/ambassador:latest");
}
+ @Deprecated
public String getSocatContainerImage() {
return (String) properties.getOrDefault("socat.container.image", "alpine/socat:latest");
}
+ public DockerImageName getSocatDockerImageName() {
+ return new DockerImageName(getSocatContainerImage());
+ }
+
+ @Deprecated
public String getVncRecordedContainerImage() {
return (String) properties.getOrDefault("vncrecorder.container.image", "testcontainers/vnc-recorder:1.1.0");
}
+ public DockerImageName getVncDockerImageName() {
+ return new DockerImageName(getVncRecordedContainerImage());
+ }
+
+ @Deprecated
public String getDockerComposeContainerImage() {
return (String) properties.getOrDefault("compose.container.image", "docker/compose:1.24.1");
}
+ public DockerImageName getDockerComposeDockerImageName() {
+ return new DockerImageName(getDockerComposeContainerImage());
+ }
+
+ @Deprecated
public String getTinyImage() {
return (String) properties.getOrDefault("tinyimage.container.image", "alpine:3.5");
}
+ public DockerImageName getTinyDockerImageName() {
+ return new DockerImageName(getTinyImage());
+ }
+
public boolean isRyukPrivileged() {
return Boolean.parseBoolean((String) properties.getOrDefault("ryuk.container.privileged", "false"));
}
+ @Deprecated
public String getRyukImage() {
return (String) properties.getOrDefault("ryuk.container.image", "testcontainers/ryuk:0.3.0");
}
+ public DockerImageName getRyukDockerImageName() {
+ return new DockerImageName(getRyukImage());
+ }
+
+ @Deprecated
public String getSSHdImage() {
return (String) properties.getOrDefault("sshd.container.image", "testcontainers/sshd:1.0.0");
}
+ public DockerImageName getSSHdDockerImageName() {
+ return new DockerImageName(getSSHdImage());
+ }
+
public Integer getRyukTimeout() {
return Integer.parseInt((String) properties.getOrDefault("ryuk.container.timeout", "30"));
}
+ @Deprecated
public String getKafkaImage() {
return (String) properties.getOrDefault("kafka.container.image", "confluentinc/cp-kafka");
}
+ public DockerImageName getKafkaDockerImageName() {
+ return new DockerImageName(getKafkaImage());
+ }
+
+ @Deprecated
public String getPulsarImage() {
return (String) properties.getOrDefault("pulsar.container.image", "apachepulsar/pulsar");
}
+ public DockerImageName getPulsarDockerImageName() {
+ return new DockerImageName(getPulsarImage());
+ }
+
+ @Deprecated
public String getLocalStackImage() {
return (String) properties.getOrDefault("localstack.container.image", "localstack/localstack");
}
+ public DockerImageName getLocalstackDockerImageName() {
+ return new DockerImageName(getLocalStackImage());
+ }
+
public boolean isDisableChecks() {
return Boolean.parseBoolean((String) environmentProperties.getOrDefault("checks.disable", "false"));
}
diff --git a/core/src/test/java/org/testcontainers/DaemonTest.java b/core/src/test/java/org/testcontainers/DaemonTest.java
index e339f30acc9..b4ccf4da374 100644
--- a/core/src/test/java/org/testcontainers/DaemonTest.java
+++ b/core/src/test/java/org/testcontainers/DaemonTest.java
@@ -19,10 +19,10 @@ public class DaemonTest {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
- GenericContainer genericContainer = null;
+ GenericContainer> genericContainer = null;
try {
- genericContainer = new GenericContainer().withCommand("top");
+ genericContainer = new GenericContainer<>(TINY_IMAGE).withCommand("top");
genericContainer.start();
Set threads = new HashSet<>(Thread.getAllStackTraces().keySet());
diff --git a/core/src/test/java/org/testcontainers/DockerClientFactoryTest.java b/core/src/test/java/org/testcontainers/DockerClientFactoryTest.java
index 60fd69e9fd3..aa90209b88c 100644
--- a/core/src/test/java/org/testcontainers/DockerClientFactoryTest.java
+++ b/core/src/test/java/org/testcontainers/DockerClientFactoryTest.java
@@ -30,7 +30,7 @@ public void runCommandInsideDockerShouldNotFailIfImageDoesNotExistsLocally() {
try {
//remove tiny image, so it will be pulled during next command run
dockFactory.client()
- .removeImageCmd(TestcontainersConfiguration.getInstance().getTinyImage())
+ .removeImageCmd(TestcontainersConfiguration.getInstance().getTinyDockerImageName().toString())
.withForce(true).exec();
} catch (NotFoundException ignored) {
// Do not fail if it's not pulled yet
diff --git a/core/src/test/java/org/testcontainers/TestingImages.java b/core/src/test/java/org/testcontainers/TestingImages.java
new file mode 100644
index 00000000000..8a18ec6e173
--- /dev/null
+++ b/core/src/test/java/org/testcontainers/TestingImages.java
@@ -0,0 +1,15 @@
+package org.testcontainers;
+
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.TestcontainersConfiguration;
+
+/**
+ * TODO: Javadocs
+ */
+public interface TestingImages {
+ public static final DockerImageName REDIS_IMAGE = new DockerImageName("redis:3.0.2");
+ public static final DockerImageName RABBITMQ_IMAGE = new DockerImageName("rabbitmq:3.5.3");
+ public static final DockerImageName MONGODB_IMAGE = new DockerImageName("mongo:3.1.5");
+ public static final DockerImageName ALPINE_IMAGE = new DockerImageName("alpine:3.2");
+ public static final DockerImageName TINY_IMAGE = TestcontainersConfiguration.getInstance().getTinyDockerImageName();
+}
diff --git a/core/src/test/java/org/testcontainers/containers/ExposedHostTest.java b/core/src/test/java/org/testcontainers/containers/ExposedHostTest.java
index 8c98b0730b0..008faf381ba 100644
--- a/core/src/test/java/org/testcontainers/containers/ExposedHostTest.java
+++ b/core/src/test/java/org/testcontainers/containers/ExposedHostTest.java
@@ -12,6 +12,7 @@
import java.net.InetSocketAddress;
import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
+import static org.testcontainers.TestingImages.TINY_IMAGE;
public class ExposedHostTest {
@@ -31,9 +32,9 @@ public static void setUpClass() throws Exception {
server.start();
Testcontainers.exposeHostPorts(server.getAddress().getPort());
-
+
Testcontainers.exposeHostPorts(ImmutableMap.of(server.getAddress().getPort(), 80));
- Testcontainers.exposeHostPorts(ImmutableMap.of(server.getAddress().getPort(), 81));
+ Testcontainers.exposeHostPorts(ImmutableMap.of(server.getAddress().getPort(), 81));
}
@AfterClass
@@ -43,24 +44,33 @@ public static void tearDownClass() throws Exception {
@Test
public void testExposedHost() throws Exception {
- assertResponse(new GenericContainer().withCommand("top"), server.getAddress().getPort());
+ assertResponse(new GenericContainer<>(TINY_IMAGE)
+ .withCommand("top"),
+ server.getAddress().getPort());
}
@Test
public void testExposedHostWithNetwork() throws Exception {
try (Network network = Network.newNetwork()) {
- assertResponse(new GenericContainer().withNetwork(network).withCommand("top"), server.getAddress().getPort());
+ assertResponse(new GenericContainer<>(TINY_IMAGE)
+ .withNetwork(network)
+ .withCommand("top"),
+ server.getAddress().getPort());
}
}
-
+
@Test
public void testExposedHostPortOnFixedInternalPorts() throws Exception {
- assertResponse(new GenericContainer().withCommand("top"), 80);
- assertResponse(new GenericContainer().withCommand("top"), 81);
- }
+ assertResponse(new GenericContainer<>(TINY_IMAGE)
+ .withCommand("top"),
+ 80);
+ assertResponse(new GenericContainer<>(TINY_IMAGE)
+ .withCommand("top"),
+ 81);
+ }
@SneakyThrows
- protected void assertResponse(GenericContainer container, int port) {
+ protected void assertResponse(GenericContainer> container, int port) {
try {
container.start();
diff --git a/core/src/test/java/org/testcontainers/containers/FailureDetectingExternalResourceTest.java b/core/src/test/java/org/testcontainers/containers/FailureDetectingExternalResourceTest.java
index 15a9eba7d17..63993bc24ff 100644
--- a/core/src/test/java/org/testcontainers/containers/FailureDetectingExternalResourceTest.java
+++ b/core/src/test/java/org/testcontainers/containers/FailureDetectingExternalResourceTest.java
@@ -3,6 +3,7 @@
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
diff --git a/core/src/test/java/org/testcontainers/containers/ReusabilityUnitTests.java b/core/src/test/java/org/testcontainers/containers/ReusabilityUnitTests.java
index b2d4d327ba2..c2caa2b42e2 100644
--- a/core/src/test/java/org/testcontainers/containers/ReusabilityUnitTests.java
+++ b/core/src/test/java/org/testcontainers/containers/ReusabilityUnitTests.java
@@ -3,7 +3,12 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.dockerjava.api.DockerClient;
-import com.github.dockerjava.api.command.*;
+import com.github.dockerjava.api.command.CreateContainerCmd;
+import com.github.dockerjava.api.command.CreateContainerResponse;
+import com.github.dockerjava.api.command.InspectContainerCmd;
+import com.github.dockerjava.api.command.InspectContainerResponse;
+import com.github.dockerjava.api.command.ListContainersCmd;
+import com.github.dockerjava.api.command.StartContainerCmd;
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.core.command.CreateContainerCmdImpl;
import com.github.dockerjava.core.command.InspectContainerCmdImpl;
@@ -23,6 +28,7 @@
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.startupcheck.StartupCheckStrategy;
import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;
+import org.testcontainers.images.RemoteDockerImage;
import org.testcontainers.utility.MockTestcontainersConfigurationRule;
import org.testcontainers.utility.MountableFile;
import org.testcontainers.utility.TestcontainersConfiguration;
@@ -61,8 +67,8 @@ public static class CanBeReusedTest {
@Parameterized.Parameters(name = "{0}")
public static Object[][] data() {
return new Object[][] {
- { "generic", new GenericContainer(IMAGE_FUTURE), true },
- { "anonymous generic", new GenericContainer(IMAGE_FUTURE) {}, true },
+ { "generic", new GenericContainer<>(new RemoteDockerImage(IMAGE_FUTURE)), true },
+ { "anonymous generic", new GenericContainer(new RemoteDockerImage(IMAGE_FUTURE)) {}, true },
{ "custom", new CustomContainer(), true },
{ "anonymous custom", new CustomContainer() {}, true },
{ "custom with containerIsCreated", new CustomContainerWithContainerIsCreated(), false },
@@ -84,13 +90,13 @@ public void shouldBeReusable() {
}
}
- static class CustomContainer extends GenericContainer {
+ static class CustomContainer extends GenericContainer {
CustomContainer() {
super(IMAGE_FUTURE);
}
}
- static class CustomContainerWithContainerIsCreated extends GenericContainer {
+ static class CustomContainerWithContainerIsCreated extends GenericContainer {
CustomContainerWithContainerIsCreated() {
super(IMAGE_FUTURE);
@@ -109,7 +115,7 @@ public static class HooksTest extends AbstractReusabilityTest {
List script = new ArrayList<>();
- GenericContainer> container = makeReusable(new GenericContainer(IMAGE_FUTURE) {
+ GenericContainer> container = makeReusable(new GenericContainer(new RemoteDockerImage(IMAGE_FUTURE)) {
@Override
protected boolean canBeReused() {
@@ -296,7 +302,7 @@ public void shouldHashCopiedFiles() {
@RunWith(BlockJUnit4ClassRunner.class)
@FieldDefaults(makeFinal = true)
public static class CopyFilesHashTest {
- GenericContainer> container = new GenericContainer(IMAGE_FUTURE);
+ GenericContainer> container = new GenericContainer<>(new RemoteDockerImage(IMAGE_FUTURE));
@Test
public void empty() {
diff --git a/core/src/test/java/org/testcontainers/containers/output/ContainerLogsTest.java b/core/src/test/java/org/testcontainers/containers/output/ContainerLogsTest.java
index ad0e4c76ae2..4e8d0314add 100644
--- a/core/src/test/java/org/testcontainers/containers/output/ContainerLogsTest.java
+++ b/core/src/test/java/org/testcontainers/containers/output/ContainerLogsTest.java
@@ -4,6 +4,7 @@
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
+import org.testcontainers.utility.DockerImageName;
import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
@@ -73,13 +74,13 @@ public void getLogsForLongRunningContainer() throws InterruptedException {
}
private static GenericContainer shortLivedContainer() {
- return new GenericContainer("alpine:3.3")
+ return new GenericContainer(new DockerImageName("alpine:3.3"))
.withCommand("/bin/sh", "-c", "echo -n 'stdout' && echo -n 'stderr' 1>&2")
.withStartupCheckStrategy(new OneShotStartupCheckStrategy());
}
private static GenericContainer longRunningContainer() {
- return new GenericContainer("alpine:3.3")
+ return new GenericContainer(new DockerImageName("alpine:3.3"))
.withCommand("ping -c 100 127.0.0.1");
}
}
diff --git a/core/src/test/java/org/testcontainers/dockerclient/EventStreamTest.java b/core/src/test/java/org/testcontainers/dockerclient/EventStreamTest.java
index 4d7a4e21133..f3a03a26677 100644
--- a/core/src/test/java/org/testcontainers/dockerclient/EventStreamTest.java
+++ b/core/src/test/java/org/testcontainers/dockerclient/EventStreamTest.java
@@ -16,6 +16,8 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import static org.testcontainers.TestingImages.TINY_IMAGE;
+
/**
* Test that event streaming from the {@link DockerClient} works correctly
*/
@@ -32,7 +34,7 @@ public void test() throws IOException, InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
try (
- GenericContainer container = new GenericContainer<>()
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
.withCommand("true")
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
) {
diff --git a/core/src/test/java/org/testcontainers/dockerclient/ImagePullTest.java b/core/src/test/java/org/testcontainers/dockerclient/ImagePullTest.java
index 6e319838888..9ddb4825c8f 100644
--- a/core/src/test/java/org/testcontainers/dockerclient/ImagePullTest.java
+++ b/core/src/test/java/org/testcontainers/dockerclient/ImagePullTest.java
@@ -5,6 +5,7 @@
import org.junit.runners.Parameterized;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
+import org.testcontainers.utility.DockerImageName;
@RunWith(Parameterized.class)
public class ImagePullTest {
@@ -34,7 +35,7 @@ public ImagePullTest(String image) {
@Test
public void test() {
- try (final GenericContainer container = new GenericContainer<>(image)
+ try (final GenericContainer container = new GenericContainer(new DockerImageName(image))
.withCommand("/bin/sh", "-c", "sleep 0")
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())) {
container.start();
diff --git a/core/src/test/java/org/testcontainers/images/ImagePullPolicyTest.java b/core/src/test/java/org/testcontainers/images/ImagePullPolicyTest.java
index 69e68f7f225..3145959ec2c 100644
--- a/core/src/test/java/org/testcontainers/images/ImagePullPolicyTest.java
+++ b/core/src/test/java/org/testcontainers/images/ImagePullPolicyTest.java
@@ -1,8 +1,5 @@
package org.testcontainers.images;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.exception.NotFoundException;
import com.github.dockerjava.core.command.PullImageResultCallback;
@@ -23,10 +20,13 @@
import java.util.UUID;
import java.util.concurrent.TimeUnit;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+
public class ImagePullPolicyTest {
@ClassRule
- public static GenericContainer> registry = new GenericContainer<>("registry:2")
+ public static GenericContainer> registry = new GenericContainer(new DockerImageName("registry:2"))
.withExposedPorts(5000);
private static String imageName;
@@ -74,7 +74,7 @@ public void setUp() {
@Test
public void pullsByDefault() {
try (
- GenericContainer> container = new GenericContainer<>(imageName)
+ GenericContainer> container = new GenericContainer(new DockerImageName(imageName))
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
) {
container.start();
@@ -84,7 +84,7 @@ public void pullsByDefault() {
@Test
public void shouldAlwaysPull() {
try (
- GenericContainer> container = new GenericContainer<>(imageName)
+ GenericContainer> container = new GenericContainer(new DockerImageName(imageName))
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
) {
container.start();
@@ -93,7 +93,7 @@ public void shouldAlwaysPull() {
DockerClientFactory.instance().client().removeImageCmd(imageName).withForce(true).exec();
try (
- GenericContainer> container = new GenericContainer<>(imageName)
+ GenericContainer> container = new GenericContainer(new DockerImageName(imageName))
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
) {
expectToFailWithNotFoundException(container);
@@ -101,7 +101,7 @@ public void shouldAlwaysPull() {
try (
// built_in_image_pull_policy {
- GenericContainer> container = new GenericContainer<>(imageName)
+ GenericContainer> container = new GenericContainer(new DockerImageName(imageName))
.withImagePullPolicy(PullPolicy.alwaysPull())
// }
) {
@@ -114,7 +114,7 @@ public void shouldAlwaysPull() {
public void shouldSupportCustomPolicies() {
try (
// custom_image_pull_policy {
- GenericContainer> container = new GenericContainer<>(imageName)
+ GenericContainer> container = new GenericContainer(new DockerImageName(imageName))
.withImagePullPolicy(new AbstractImagePullPolicy() {
@Override
protected boolean shouldPullCached(DockerImageName imageName, ImageData localImageData) {
@@ -137,7 +137,7 @@ protected boolean shouldPullCached(DockerImageName imageName, ImageData localIma
}
});
try (
- GenericContainer> container = new GenericContainer<>(imageName)
+ GenericContainer> container = new GenericContainer(new DockerImageName(imageName))
.withImagePullPolicy(policy)
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
) {
@@ -150,7 +150,7 @@ protected boolean shouldPullCached(DockerImageName imageName, ImageData localIma
@Test
public void shouldNotForcePulling() {
try (
- GenericContainer> container = new GenericContainer<>(imageName)
+ GenericContainer> container = new GenericContainer(new DockerImageName(imageName))
.withImagePullPolicy(__ -> false)
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
) {
diff --git a/core/src/test/java/org/testcontainers/images/RemoteDockerImageTest.java b/core/src/test/java/org/testcontainers/images/RemoteDockerImageTest.java
index c62f15653b3..a465c41fd92 100644
--- a/core/src/test/java/org/testcontainers/images/RemoteDockerImageTest.java
+++ b/core/src/test/java/org/testcontainers/images/RemoteDockerImageTest.java
@@ -1,20 +1,21 @@
package org.testcontainers.images;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-
import org.junit.Test;
import org.testcontainers.utility.Base58;
+import org.testcontainers.utility.DockerImageName;
import java.util.concurrent.CompletableFuture;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+
public class RemoteDockerImageTest {
@Test
public void toStringContainsOnlyImageName() {
String imageName = Base58.randomString(8).toLowerCase();
- RemoteDockerImage remoteDockerImage = new RemoteDockerImage(imageName);
+ RemoteDockerImage remoteDockerImage = new RemoteDockerImage(new DockerImageName(imageName));
assertThat(remoteDockerImage.toString(), containsString("imageName=" + imageName));
}
diff --git a/core/src/test/java/org/testcontainers/images/builder/DockerignoreTest.java b/core/src/test/java/org/testcontainers/images/builder/DockerignoreTest.java
index faf5fb950b8..810100fea51 100644
--- a/core/src/test/java/org/testcontainers/images/builder/DockerignoreTest.java
+++ b/core/src/test/java/org/testcontainers/images/builder/DockerignoreTest.java
@@ -1,21 +1,21 @@
package org.testcontainers.images.builder;
-import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
-import static org.rnorth.visibleassertions.VisibleAssertions.fail;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
+import com.github.dockerjava.api.exception.DockerClientException;
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
+import org.testcontainers.utility.DockerImageName;
-import com.github.dockerjava.api.exception.DockerClientException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
+import static org.rnorth.visibleassertions.VisibleAssertions.fail;
public class DockerignoreTest {
-
+
private static final Path INVALID_DOCKERIGNORE_PATH = Paths.get("src/test/resources/dockerfile-build-invalid");
-
+
@Test
public void testInvalidDockerignore() throws Exception {
try {
@@ -24,13 +24,13 @@ public void testInvalidDockerignore() throws Exception {
.withDockerfile(INVALID_DOCKERIGNORE_PATH.resolve("Dockerfile"))
.get();
fail("Should not be able to build an image with an invalid .dockerignore file");
- }
+ }
catch (DockerClientException e) {
if (!e.getMessage().contains("Invalid pattern"))
throw e;
}
}
-
+
@SuppressWarnings("resource")
@Test
public void testValidDockerignore() throws Exception {
@@ -38,7 +38,7 @@ public void testValidDockerignore() throws Exception {
.withFileFromPath(".", DockerfileBuildTest.RESOURCE_PATH)
.withDockerfile(DockerfileBuildTest.RESOURCE_PATH.resolve("Dockerfile-currentdir"));
try(
- final GenericContainer> container = new GenericContainer<>(img.get())
+ final GenericContainer> container = new GenericContainer(new DockerImageName(img.get()))
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
.withCommand("ls", "/")
) {
@@ -46,11 +46,11 @@ public void testValidDockerignore() throws Exception {
container.start();
final String logs = container.getLogs();
- assertTrue("Files in the container indicated the .dockerignore was not applied. Output was: " + logs,
+ assertTrue("Files in the container indicated the .dockerignore was not applied. Output was: " + logs,
logs.contains("should_not_be_ignored.txt"));
- assertTrue("Files in the container indicated the .dockerignore was not applied. Output was: " + logs,
+ assertTrue("Files in the container indicated the .dockerignore was not applied. Output was: " + logs,
!logs.contains("should_be_ignored.txt"));
}
}
-
+
}
diff --git a/core/src/test/java/org/testcontainers/junit/CopyFileToContainerTest.java b/core/src/test/java/org/testcontainers/junit/CopyFileToContainerTest.java
index ed6ee561505..4a10f9b1ea6 100644
--- a/core/src/test/java/org/testcontainers/junit/CopyFileToContainerTest.java
+++ b/core/src/test/java/org/testcontainers/junit/CopyFileToContainerTest.java
@@ -19,7 +19,7 @@ public class CopyFileToContainerTest {
@Test
public void checkFileCopied() throws IOException, InterruptedException {
try (
- GenericContainer container = new GenericContainer()
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
.withCommand("sleep", "3000")
.withCopyFileToContainer(MountableFile.forClasspathResource("/mappable-resource/"), containerPath)
) {
@@ -32,7 +32,7 @@ public void checkFileCopied() throws IOException, InterruptedException {
@Test
public void shouldUseCopyForReadOnlyClasspathResources() throws Exception {
try (
- GenericContainer container = new GenericContainer()
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
.withCommand("sleep", "3000")
.withClasspathResourceMapping("/mappable-resource/", containerPath, BindMode.READ_ONLY)
) {
@@ -45,7 +45,7 @@ public void shouldUseCopyForReadOnlyClasspathResources() throws Exception {
@Test
public void shouldUseCopyOnlyWithReadOnlyClasspathResources() {
String resource = "/test_copy_to_container.txt";
- GenericContainer> container = new GenericContainer<>()
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
.withClasspathResourceMapping(resource, "/readOnly", BindMode.READ_ONLY)
.withClasspathResourceMapping(resource, "/readOnlyNoSelinux", BindMode.READ_ONLY)
@@ -64,7 +64,7 @@ public void shouldUseCopyOnlyWithReadOnlyClasspathResources() {
public void shouldCreateFoldersStructureWithCopy() throws Exception {
String resource = "/test_copy_to_container.txt";
try (
- GenericContainer container = new GenericContainer<>()
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
.withCommand("sleep", "3000")
.withClasspathResourceMapping(resource, "/a/b/c/file", BindMode.READ_ONLY)
) {
diff --git a/core/src/test/java/org/testcontainers/junit/DependenciesTest.java b/core/src/test/java/org/testcontainers/junit/DependenciesTest.java
index 4f2ee01bc6d..4c98e0728f8 100644
--- a/core/src/test/java/org/testcontainers/junit/DependenciesTest.java
+++ b/core/src/test/java/org/testcontainers/junit/DependenciesTest.java
@@ -23,7 +23,7 @@ public void shouldWorkWithSimpleDependency() {
InvocationCountingStartable startable = new InvocationCountingStartable();
try (
- GenericContainer container = new GenericContainer()
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
.dependsOn(startable)
) {
@@ -40,7 +40,7 @@ public void shouldWorkWithMutlipleDependencies() {
InvocationCountingStartable startable2 = new InvocationCountingStartable();
try (
- GenericContainer container = new GenericContainer()
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
.dependsOn(startable1, startable2)
) {
@@ -56,7 +56,7 @@ public void shouldStartEveryTime() {
InvocationCountingStartable startable = new InvocationCountingStartable();
try (
- GenericContainer container = new GenericContainer()
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
.dependsOn(startable)
) {
@@ -83,7 +83,7 @@ public void shouldStartTransitiveDependencies() {
startable.getDependencies().add(transitiveStartable);
try (
- GenericContainer container = new GenericContainer()
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
.dependsOn(startable)
) {
diff --git a/core/src/test/java/org/testcontainers/junit/DockerNetworkModeTest.java b/core/src/test/java/org/testcontainers/junit/DockerNetworkModeTest.java
index 873160bdbc9..74c3bbd576e 100644
--- a/core/src/test/java/org/testcontainers/junit/DockerNetworkModeTest.java
+++ b/core/src/test/java/org/testcontainers/junit/DockerNetworkModeTest.java
@@ -2,7 +2,7 @@
import com.github.dockerjava.api.model.NetworkSettings;
import lombok.extern.slf4j.Slf4j;
-import org.junit.*;
+import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
@@ -19,7 +19,7 @@ public class DockerNetworkModeTest {
@Test
public void testNoNetworkContainer() {
try (
- GenericContainer container = new GenericContainer()
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
.withCommand("true")
.withNetworkMode("none")
@@ -35,7 +35,7 @@ public void testNoNetworkContainer() {
@Test
public void testHostNetworkContainer() {
try (
- GenericContainer container = new GenericContainer()
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
.withCommand("true")
.withNetworkMode("host")
diff --git a/core/src/test/java/org/testcontainers/junit/FileOperationsTest.java b/core/src/test/java/org/testcontainers/junit/FileOperationsTest.java
index 95de9ef1b85..4434bf7d20f 100644
--- a/core/src/test/java/org/testcontainers/junit/FileOperationsTest.java
+++ b/core/src/test/java/org/testcontainers/junit/FileOperationsTest.java
@@ -6,6 +6,7 @@
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.MountableFile;
import java.io.File;
@@ -21,7 +22,7 @@ public class FileOperationsTest {
@Test
public void copyFileToContainerFileTest() throws Exception {
try (
- GenericContainer alpineCopyToContainer = new GenericContainer("alpine:3.2")
+ GenericContainer alpineCopyToContainer = new GenericContainer(new DockerImageName("alpine:3.2"))
.withCommand("top")
) {
alpineCopyToContainer.start();
@@ -39,7 +40,7 @@ public void copyFileToContainerFileTest() throws Exception {
@Test
public void copyFileToContainerFolderTest() throws Exception {
try (
- GenericContainer alpineCopyToContainer = new GenericContainer("alpine:3.2")
+ GenericContainer alpineCopyToContainer = new GenericContainer(new DockerImageName("alpine:3.2"))
.withCommand("top")
) {
alpineCopyToContainer.start();
@@ -57,7 +58,7 @@ public void copyFileToContainerFolderTest() throws Exception {
@Test
public void copyFolderToContainerFolderTest() throws Exception {
try (
- GenericContainer alpineCopyToContainer = new GenericContainer("alpine:3.2")
+ GenericContainer alpineCopyToContainer = new GenericContainer(new DockerImageName("alpine:3.2"))
.withCommand("top")
) {
@@ -76,7 +77,7 @@ public void copyFolderToContainerFolderTest() throws Exception {
@Test(expected = NotFoundException.class)
public void copyFromContainerShouldFailBecauseNoFileTest() throws NotFoundException {
try (
- GenericContainer alpineCopyToContainer = new GenericContainer("alpine:3.2")
+ GenericContainer alpineCopyToContainer = new GenericContainer(new DockerImageName("alpine:3.2"))
.withCommand("top")
) {
alpineCopyToContainer.start();
@@ -87,7 +88,7 @@ public void copyFromContainerShouldFailBecauseNoFileTest() throws NotFoundExcept
@Test
public void shouldCopyFileFromContainerTest() throws IOException {
try (
- GenericContainer alpineCopyToContainer = new GenericContainer("alpine:3.2")
+ GenericContainer alpineCopyToContainer = new GenericContainer(new DockerImageName("alpine:3.2"))
.withCommand("top")
) {
diff --git a/core/src/test/java/org/testcontainers/junit/FixedHostPortContainerTest.java b/core/src/test/java/org/testcontainers/junit/FixedHostPortContainerTest.java
index 5fe49756ba1..c2e76310dfa 100644
--- a/core/src/test/java/org/testcontainers/junit/FixedHostPortContainerTest.java
+++ b/core/src/test/java/org/testcontainers/junit/FixedHostPortContainerTest.java
@@ -1,8 +1,5 @@
package org.testcontainers.junit;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
-
import com.google.common.util.concurrent.Uninterruptibles;
import org.junit.Test;
import org.rnorth.ducttape.unreliables.Unreliables;
@@ -15,6 +12,10 @@
import java.net.Socket;
import java.util.concurrent.TimeUnit;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.testcontainers.TestingImages.TINY_IMAGE;
+
/**
* Test of {@link FixedHostPortGenericContainer}. Note that this is not an example of typical use (usually, a container
* should be a field on the test class annotated with @Rule or @TestRule). Instead, here, the lifecycle of the container
@@ -46,7 +47,7 @@ public void testFixedHostPortMapping() throws IOException {
// first find a free port on the docker host that will work for testing
final Integer unusedHostPort;
try (
- final GenericContainer echoServer = new GenericContainer<>(TEST_IMAGE)
+ final GenericContainer echoServer = new GenericContainer(TINY_IMAGE)
.withExposedPorts(TEST_PORT)
.withCommand("/bin/sh", "-c", HTTP_ECHO_CMD)
) {
diff --git a/core/src/test/java/org/testcontainers/junit/GenericContainerRuleTest.java b/core/src/test/java/org/testcontainers/junit/GenericContainerRuleTest.java
index b450244a84d..fee1b3ddc88 100644
--- a/core/src/test/java/org/testcontainers/junit/GenericContainerRuleTest.java
+++ b/core/src/test/java/org/testcontainers/junit/GenericContainerRuleTest.java
@@ -6,10 +6,19 @@
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
-import com.rabbitmq.client.*;
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.DefaultConsumer;
+import com.rabbitmq.client.Envelope;
import org.apache.commons.io.FileUtils;
import org.bson.Document;
-import org.junit.*;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
import org.rnorth.ducttape.RetryCountExceededException;
import org.rnorth.ducttape.unreliables.Unreliables;
import org.testcontainers.containers.Container;
@@ -18,7 +27,13 @@
import org.testcontainers.utility.Base58;
import org.testcontainers.utility.TestEnvironment;
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
import java.net.Socket;
import java.time.Duration;
import java.util.Arrays;
@@ -30,7 +45,16 @@
import static java.util.Collections.singletonMap;
import static org.hamcrest.CoreMatchers.equalTo;
-import static org.rnorth.visibleassertions.VisibleAssertions.*;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertFalse;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertThat;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertThrows;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
+import static org.testcontainers.TestingImages.ALPINE_IMAGE;
+import static org.testcontainers.TestingImages.MONGODB_IMAGE;
+import static org.testcontainers.TestingImages.RABBITMQ_IMAGE;
+import static org.testcontainers.TestingImages.REDIS_IMAGE;
+import static org.testcontainers.TestingImages.TINY_IMAGE;
import static org.testcontainers.containers.BindMode.READ_ONLY;
import static org.testcontainers.containers.BindMode.READ_WRITE;
import static org.testcontainers.containers.SelinuxContext.SHARED;
@@ -47,6 +71,7 @@ public class GenericContainerRuleTest {
private static final int RABBITMQ_PORT = 5672;
private static final int MONGO_PORT = 27017;
+
/*
* Test data setup
*/
@@ -61,29 +86,27 @@ public static void setupContent() throws FileNotFoundException {
* Redis
*/
@ClassRule
- public static GenericContainer redis = new GenericContainer("redis:3.0.2")
+ public static GenericContainer> redis = new GenericContainer<>(REDIS_IMAGE)
.withExposedPorts(REDIS_PORT);
/**
* RabbitMQ
*/
@ClassRule
- public static GenericContainer rabbitMq = new GenericContainer("rabbitmq:3.5.3")
+ public static GenericContainer> rabbitMq = new GenericContainer<>(RABBITMQ_IMAGE)
.withExposedPorts(RABBITMQ_PORT);
-
/**
* MongoDB
*/
@ClassRule
- public static GenericContainer mongo = new GenericContainer("mongo:3.1.5")
+ public static GenericContainer> mongo = new GenericContainer<>(MONGODB_IMAGE)
.withExposedPorts(MONGO_PORT);
-
/**
* Pass an environment variable to the container, then run a shell script that exposes the variable in a quick and
* dirty way for testing.
*/
@ClassRule
- public static GenericContainer alpineEnvVar = new GenericContainer<>("alpine:3.2")
+ public static GenericContainer> alpineEnvVar = new GenericContainer<>(ALPINE_IMAGE)
.withExposedPorts(80)
.withEnv("MAGIC_NUMBER", "4")
.withEnv("MAGIC_NUMBER", oldValue -> oldValue.orElse("") + "2")
@@ -94,7 +117,7 @@ public static void setupContent() throws FileNotFoundException {
* dirty way for testing.
*/
@ClassRule
- public static GenericContainer alpineEnvVarFromMap = new GenericContainer("alpine:3.2")
+ public static GenericContainer> alpineEnvVarFromMap = new GenericContainer<>(ALPINE_IMAGE)
.withExposedPorts(80)
.withEnv(ImmutableMap.of(
"FIRST", "42",
@@ -106,7 +129,7 @@ public static void setupContent() throws FileNotFoundException {
* Map a file on the classpath to a file in the container, and then expose the content for testing.
*/
@ClassRule
- public static GenericContainer alpineClasspathResource = new GenericContainer("alpine:3.2")
+ public static GenericContainer> alpineClasspathResource = new GenericContainer<>(ALPINE_IMAGE)
.withExposedPorts(80)
.withClasspathResourceMapping("mappable-resource/test-resource.txt", "/content.txt", READ_ONLY)
.withCommand("/bin/sh", "-c", "while true; do cat /content.txt | nc -l -p 80; done");
@@ -115,7 +138,7 @@ public static void setupContent() throws FileNotFoundException {
* Map a file on the classpath to a file in the container, and then expose the content for testing.
*/
@ClassRule
- public static GenericContainer alpineClasspathResourceSelinux = new GenericContainer("alpine:3.2")
+ public static GenericContainer> alpineClasspathResourceSelinux = new GenericContainer<>(ALPINE_IMAGE)
.withExposedPorts(80)
.withClasspathResourceMapping("mappable-resource/test-resource.txt", "/content.txt", READ_WRITE, SHARED)
.withCommand("/bin/sh", "-c", "while true; do cat /content.txt | nc -l -p 80; done");
@@ -124,14 +147,16 @@ public static void setupContent() throws FileNotFoundException {
* Create a container with an extra host entry and expose the content of /etc/hosts for testing.
*/
@ClassRule
- public static GenericContainer alpineExtrahost = new GenericContainer("alpine:3.2")
+ public static GenericContainer> alpineExtrahost = new GenericContainer<>(ALPINE_IMAGE)
.withExposedPorts(80)
.withExtraHost("somehost", "192.168.1.10")
.withCommand("/bin/sh", "-c", "while true; do cat /etc/hosts | nc -l -p 80; done");
@Test
public void testIsRunning() {
- try (GenericContainer container = new GenericContainer().withCommand("top")) {
+ try (
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
+ .withCommand("top")) {
assertFalse("Container is not started and not running", container.isRunning());
container.start();
assertTrue("Container is started and running", container.isRunning());
@@ -141,7 +166,7 @@ public void testIsRunning() {
@Test
public void withTmpFsTest() throws Exception {
try (
- GenericContainer container = new GenericContainer()
+ GenericContainer> container = new GenericContainer<>(TINY_IMAGE)
.withCommand("top")
.withTmpFs(singletonMap("/testtmpfs", "rw"))
) {
@@ -222,7 +247,7 @@ public void environmentFromMapTest() throws IOException {
@Test
public void customLabelTest() {
- try (final GenericContainer alpineCustomLabel = new GenericContainer("alpine:3.2")
+ try (final GenericContainer alpineCustomLabel = new GenericContainer<>(ALPINE_IMAGE)
.withLabel("our.custom", "label")
.withCommand("top")) {
@@ -240,7 +265,7 @@ public void exceptionThrownWhenTryingToOverrideTestcontainersLabels() {
assertThrows("When trying to overwrite an 'org.testcontainers' label, withLabel() throws an exception",
IllegalArgumentException.class,
() -> {
- new GenericContainer("alpine:3.2")
+ new GenericContainer<>(ALPINE_IMAGE)
.withLabel("org.testcontainers.foo", "false");
}
);
@@ -262,7 +287,7 @@ public void customClasspathResourceMappingWithSelinuxTest() throws IOException {
}
@Test
- public void exceptionThrownWhenMappedPortNotFound() throws IOException {
+ public void exceptionThrownWhenMappedPortNotFound() {
assertThrows("When the requested port is not mapped, getMappedPort() throws an exception",
IllegalArgumentException.class,
() -> {
@@ -279,10 +304,10 @@ protected static void writeStringToFile(File contentFolder, String filename, Str
}
@Test @Ignore //TODO investigate intermittent failures
- public void failFastWhenContainerHaltsImmediately() throws Exception {
+ public void failFastWhenContainerHaltsImmediately() {
long startingTimeMs = System.currentTimeMillis();
- final GenericContainer failsImmediately = new GenericContainer("alpine:3.2")
+ final GenericContainer failsImmediately = new GenericContainer<>(ALPINE_IMAGE)
.withCommand("/bin/sh", "-c", "return false")
.withMinimumRunningDuration(Duration.ofMillis(100));
@@ -346,7 +371,7 @@ public void createContainerCmdHookTest() {
// Use random name to avoid the conflicts between the tests
String randomName = Base58.randomString(5);
try(
- GenericContainer container = new GenericContainer<>("redis:3.0.2")
+ GenericContainer> container = new GenericContainer<>(REDIS_IMAGE)
.withCommand("redis-server", "--help")
.withCreateContainerCmdModifier(cmd -> cmd.withName("overrideMe"))
// Preserves the order
@@ -390,7 +415,7 @@ public void addingExposedPortTwiceShouldNotFail() {
@Test
public void sharedMemorySetTest() {
- try (GenericContainer containerWithSharedMemory = new GenericContainer()
+ try (GenericContainer containerWithSharedMemory = new GenericContainer<>(TINY_IMAGE)
.withSharedMemorySize(42L * FileUtils.ONE_MB)
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())) {
diff --git a/core/src/test/java/org/testcontainers/junit/NonExistentImagePullTest.java b/core/src/test/java/org/testcontainers/junit/NonExistentImagePullTest.java
index d842829b4b6..ddc4ea9be15 100644
--- a/core/src/test/java/org/testcontainers/junit/NonExistentImagePullTest.java
+++ b/core/src/test/java/org/testcontainers/junit/NonExistentImagePullTest.java
@@ -3,6 +3,7 @@
import org.junit.Test;
import org.testcontainers.containers.ContainerFetchException;
import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
import static org.rnorth.visibleassertions.VisibleAssertions.assertThrows;
@@ -16,7 +17,7 @@ public void pullingNonExistentImageFailsGracefully() {
assertThrows("Pulling a nonexistent container will cause an exception to be thrown",
ContainerFetchException.class, () -> {
- new GenericContainer("richnorth/nonexistent:latest").getDockerImageName();
+ new GenericContainer(new DockerImageName("richnorth/nonexistent:latest")).getDockerImageName();
});
}
}
diff --git a/core/src/test/java/org/testcontainers/junit/OutputStreamTest.java b/core/src/test/java/org/testcontainers/junit/OutputStreamTest.java
index 83ba05be86e..c9e61b6e3f4 100644
--- a/core/src/test/java/org/testcontainers/junit/OutputStreamTest.java
+++ b/core/src/test/java/org/testcontainers/junit/OutputStreamTest.java
@@ -9,12 +9,15 @@
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.output.ToStringConsumer;
import org.testcontainers.containers.output.WaitingConsumer;
+import org.testcontainers.utility.DockerImageName;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
-import static org.rnorth.visibleassertions.VisibleAssertions.*;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertFalse;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertThrows;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
import static org.testcontainers.containers.output.OutputFrame.OutputType.STDOUT;
/**
@@ -23,7 +26,7 @@
public class OutputStreamTest {
@Rule
- public GenericContainer container = new GenericContainer("alpine:3.2")
+ public GenericContainer container = new GenericContainer(new DockerImageName("alpine:3.2"))
.withCommand("ping -c 5 127.0.0.1");
private static final Logger LOGGER = LoggerFactory.getLogger(OutputStreamTest.class);
diff --git a/core/src/test/java/org/testcontainers/junit/OutputStreamWithTTYTest.java b/core/src/test/java/org/testcontainers/junit/OutputStreamWithTTYTest.java
index 0e7b66534b7..183b2be8aaa 100644
--- a/core/src/test/java/org/testcontainers/junit/OutputStreamWithTTYTest.java
+++ b/core/src/test/java/org/testcontainers/junit/OutputStreamWithTTYTest.java
@@ -10,19 +10,22 @@
import org.testcontainers.containers.output.ToStringConsumer;
import org.testcontainers.containers.output.WaitingConsumer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
+import org.testcontainers.utility.DockerImageName;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
-import static org.rnorth.visibleassertions.VisibleAssertions.*;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertFalse;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertThrows;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
import static org.testcontainers.containers.output.OutputFrame.OutputType.STDOUT;
@Slf4j
public class OutputStreamWithTTYTest {
@Rule
- public GenericContainer container = new GenericContainer<>("alpine:3.2")
+ public GenericContainer container = new GenericContainer(new DockerImageName("alpine:3.2"))
.withCommand("ls -1")
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
.withCreateContainerCmdModifier(command -> command.withTty(true));
diff --git a/core/src/test/java/org/testcontainers/junit/WorkingDirectoryTest.java b/core/src/test/java/org/testcontainers/junit/WorkingDirectoryTest.java
index 30230cc12ff..f9fa2fc293d 100644
--- a/core/src/test/java/org/testcontainers/junit/WorkingDirectoryTest.java
+++ b/core/src/test/java/org/testcontainers/junit/WorkingDirectoryTest.java
@@ -4,6 +4,7 @@
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
+import org.testcontainers.utility.DockerImageName;
import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
@@ -13,7 +14,7 @@
public class WorkingDirectoryTest {
@ClassRule
- public static GenericContainer container = new GenericContainer("alpine:3.2")
+ public static GenericContainer container = new GenericContainer(new DockerImageName("alpine:3.2"))
.withWorkingDirectory("/etc")
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
.withCommand("ls", "-al");
diff --git a/core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java
index 3d3c2f0606d..c5b6bcb7095 100644
--- a/core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java
+++ b/core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java
@@ -7,6 +7,7 @@
import org.testcontainers.containers.ContainerLaunchException;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
+import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -67,7 +68,7 @@ protected GenericContainer startContainerWithCommand(String shellCommand, WaitSt
protected GenericContainer startContainerWithCommand(String shellCommand, WaitStrategy waitStrategy, Integer... ports) {
// apply WaitStrategy to container
- return new GenericContainer(IMAGE_NAME)
+ return new GenericContainer(new DockerImageName(IMAGE_NAME))
.withExposedPorts(ports)
.withCommand("sh", "-c", shellCommand)
.waitingFor(waitStrategy.withStartupTimeout(Duration.ofMillis(WAIT_TIMEOUT_MILLIS)));
diff --git a/core/src/test/java/org/testcontainers/junit/wait/strategy/HostPortWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/strategy/HostPortWaitStrategyTest.java
index d715b25aaf6..48d574f57fc 100644
--- a/core/src/test/java/org/testcontainers/junit/wait/strategy/HostPortWaitStrategyTest.java
+++ b/core/src/test/java/org/testcontainers/junit/wait/strategy/HostPortWaitStrategyTest.java
@@ -4,6 +4,7 @@
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
@@ -18,7 +19,7 @@ public class HostPortWaitStrategyTest {
private static final String IMAGE_NAME = "alpine:3.7";
@ClassRule
- public static GenericContainer container = new GenericContainer(IMAGE_NAME).withExposedPorts()
+ public static GenericContainer container = new GenericContainer(new DockerImageName(IMAGE_NAME)).withExposedPorts()
.withCommand("sh", "-c", "while true; do nc -lp 8080; done")
.withExposedPorts(8080)
.waitingFor(Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(10)));
diff --git a/core/src/test/java/org/testcontainers/utility/AuthenticatedImagePullTest.java b/core/src/test/java/org/testcontainers/utility/AuthenticatedImagePullTest.java
index 3986255f248..ad1fbc50732 100644
--- a/core/src/test/java/org/testcontainers/utility/AuthenticatedImagePullTest.java
+++ b/core/src/test/java/org/testcontainers/utility/AuthenticatedImagePullTest.java
@@ -99,7 +99,7 @@ public static void tearDown() {
@Test
public void testThatAuthLocatorIsUsedForContainerCreation() {
// actually start a container, which will require an authenticated pull
- try (final GenericContainer> container = new GenericContainer<>(testImageNameWithTag)
+ try (final GenericContainer> container = new GenericContainer(new DockerImageName(testImageNameWithTag))
.withCommand("/bin/sh", "-c", "sleep 10")) {
container.start();
@@ -165,7 +165,7 @@ private Path getLocalTempFile(String s) throws IOException {
private static void putImageInRegistry() throws InterruptedException {
// It doesn't matter which image we use for this test, but use one that's likely to have been pulled already
- final String dummySourceImage = TestcontainersConfiguration.getInstance().getRyukImage();
+ final String dummySourceImage = TestcontainersConfiguration.getInstance().getRyukDockerImageName().toString();
client.pullImageCmd(dummySourceImage)
.exec(new PullImageResultCallback())
diff --git a/docs/examples/junit4/generic/src/test/java/generic/CmdModifierTest.java b/docs/examples/junit4/generic/src/test/java/generic/CmdModifierTest.java
index 6d075c0bccc..9140f560de3 100644
--- a/docs/examples/junit4/generic/src/test/java/generic/CmdModifierTest.java
+++ b/docs/examples/junit4/generic/src/test/java/generic/CmdModifierTest.java
@@ -4,6 +4,7 @@
import org.junit.Test;
import org.testcontainers.containers.Container;
import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
import java.io.IOException;
@@ -13,13 +14,13 @@ public class CmdModifierTest {
// hostname {
@Rule
- public GenericContainer theCache = new GenericContainer<>("redis:3.0.2")
+ public GenericContainer theCache = new GenericContainer<>(new DockerImageName("redis:3.0.2"))
.withCreateContainerCmdModifier(cmd -> cmd.withHostName("the-cache"));
// }
// memory {
@Rule
- public GenericContainer memoryLimitedRedis = new GenericContainer<>("redis:3.0.2")
+ public GenericContainer memoryLimitedRedis = new GenericContainer<>(new DockerImageName("redis:3.0.2"))
.withCreateContainerCmdModifier(cmd -> cmd.withMemory((long) 8 * 1024 * 1024))
.withCreateContainerCmdModifier(cmd -> cmd.withMemorySwap((long) 12 * 1024 * 1024));
// }
diff --git a/docs/examples/junit4/generic/src/test/java/generic/CommandsTest.java b/docs/examples/junit4/generic/src/test/java/generic/CommandsTest.java
index 486464c7dda..5b56c81f06f 100644
--- a/docs/examples/junit4/generic/src/test/java/generic/CommandsTest.java
+++ b/docs/examples/junit4/generic/src/test/java/generic/CommandsTest.java
@@ -3,13 +3,14 @@
import org.junit.Rule;
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
import static org.junit.Assert.assertTrue;
public class CommandsTest {
@Rule
// startupCommand {
- public GenericContainer redisWithCustomPort = new GenericContainer("redis:5.0")
+ public GenericContainer redisWithCustomPort = new GenericContainer(new DockerImageName("redis:5.0"))
.withCommand("redis-server --port 7777")
// }
.withExposedPorts(7777);
diff --git a/docs/examples/junit4/generic/src/test/java/generic/ContainerCreationTest.java b/docs/examples/junit4/generic/src/test/java/generic/ContainerCreationTest.java
index 19a40a7c43a..658d28e025f 100644
--- a/docs/examples/junit4/generic/src/test/java/generic/ContainerCreationTest.java
+++ b/docs/examples/junit4/generic/src/test/java/generic/ContainerCreationTest.java
@@ -3,6 +3,7 @@
import org.junit.ClassRule;
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
import static org.junit.Assert.assertTrue;
@@ -11,20 +12,20 @@ public class ContainerCreationTest {
// simple {
@ClassRule
public static GenericContainer redis =
- new GenericContainer("redis:3.0.2")
+ new GenericContainer(new DockerImageName("redis:3.0.2"))
.withExposedPorts(6379);
// }
// withOptions {
- // Set up a plain OS container and customize environment,
- // command and exposed ports. This just listens on port 80
+ // Set up a plain OS container and customize environment,
+ // command and exposed ports. This just listens on port 80
// and always returns '42'
@ClassRule
public static GenericContainer alpine =
- new GenericContainer("alpine:3.2")
+ new GenericContainer(new DockerImageName("alpine:3.2"))
.withExposedPorts(80)
.withEnv("MAGIC_NUMBER", "42")
- .withCommand("/bin/sh", "-c",
+ .withCommand("/bin/sh", "-c",
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done");
// }
diff --git a/docs/examples/junit4/generic/src/test/java/generic/ContainerLabelTest.java b/docs/examples/junit4/generic/src/test/java/generic/ContainerLabelTest.java
index f8f26eaa5fe..0bb178767d2 100644
--- a/docs/examples/junit4/generic/src/test/java/generic/ContainerLabelTest.java
+++ b/docs/examples/junit4/generic/src/test/java/generic/ContainerLabelTest.java
@@ -1,13 +1,14 @@
package generic;
import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
import java.util.HashMap;
import java.util.Map;
public class ContainerLabelTest {
// single_label {
- public GenericContainer containerWithLabel = new GenericContainer("alpine:3.6")
+ public GenericContainer containerWithLabel = new GenericContainer(new DockerImageName("alpine:3.6"))
.withLabel("key", "value");
// }
@@ -16,7 +17,7 @@ public class ContainerLabelTest {
private Map mapOfLabels = new HashMap<>();
// populate map, e.g. mapOfLabels.put("key1", "value1");
- public GenericContainer containerWithMultipleLabels = new GenericContainer("alpine:3.6")
+ public GenericContainer containerWithMultipleLabels = new GenericContainer(new DockerImageName("alpine:3.6"))
.withLabels(mapOfLabels);
// }
}
diff --git a/docs/examples/junit4/generic/src/test/java/generic/ExecTest.java b/docs/examples/junit4/generic/src/test/java/generic/ExecTest.java
index e160390c293..a40e96f31e1 100644
--- a/docs/examples/junit4/generic/src/test/java/generic/ExecTest.java
+++ b/docs/examples/junit4/generic/src/test/java/generic/ExecTest.java
@@ -4,6 +4,7 @@
import org.junit.Test;
import org.testcontainers.containers.Container;
import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
import java.io.IOException;
@@ -12,7 +13,7 @@
public class ExecTest {
@Rule
- public GenericContainer container = new GenericContainer("alpine:3.6")
+ public GenericContainer> container = new GenericContainer<>(new DockerImageName("alpine:3.6"))
.withCommand("top");
@Test
diff --git a/docs/examples/junit4/generic/src/test/java/generic/MultiplePortsExposedTest.java b/docs/examples/junit4/generic/src/test/java/generic/MultiplePortsExposedTest.java
index f5a92423b6c..ac717685456 100644
--- a/docs/examples/junit4/generic/src/test/java/generic/MultiplePortsExposedTest.java
+++ b/docs/examples/junit4/generic/src/test/java/generic/MultiplePortsExposedTest.java
@@ -5,6 +5,7 @@
import org.slf4j.Logger;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
+import org.testcontainers.utility.DockerImageName;
import static org.slf4j.LoggerFactory.getLogger;
@@ -14,7 +15,7 @@ public class MultiplePortsExposedTest {
@Rule
// rule {
- public GenericContainer container = new GenericContainer("orientdb:3.0.13")
+ public GenericContainer container = new GenericContainer(new DockerImageName("orientdb:3.0.13"))
.withExposedPorts(2424, 2480)
.withLogConsumer(new Slf4jLogConsumer(log));
// }
diff --git a/docs/examples/junit4/generic/src/test/java/generic/WaitStrategiesTest.java b/docs/examples/junit4/generic/src/test/java/generic/WaitStrategiesTest.java
index 0bb5eb40bc1..b102826d6be 100644
--- a/docs/examples/junit4/generic/src/test/java/generic/WaitStrategiesTest.java
+++ b/docs/examples/junit4/generic/src/test/java/generic/WaitStrategiesTest.java
@@ -6,6 +6,7 @@
import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
+import org.testcontainers.utility.DockerImageName;
import static org.junit.Assert.assertTrue;
@@ -13,20 +14,20 @@ public class WaitStrategiesTest {
@Rule
// waitForNetworkListening {
- public GenericContainer nginx = new GenericContainer("nginx:1.9.4")
+ public GenericContainer nginx = new GenericContainer(new DockerImageName("nginx:1.9.4"))
.withExposedPorts(80);
// }
@Rule
// waitForSimpleHttp {
- public GenericContainer nginxWithHttpWait = new GenericContainer("nginx:1.9.4")
+ public GenericContainer nginxWithHttpWait = new GenericContainer(new DockerImageName("nginx:1.9.4"))
.withExposedPorts(80)
.waitingFor(Wait.forHttp("/"));
// }
@Rule
// logMessageWait {
- public GenericContainer containerWithLogWait = new GenericContainer("redis:5.0.3")
+ public GenericContainer containerWithLogWait = new GenericContainer(new DockerImageName("redis:5.0.3"))
.withExposedPorts(6379)
.waitingFor(
Wait.forLogMessage(".*Ready to accept connections.*\\n", 1)
diff --git a/docs/examples/junit4/generic/src/test/java/org/testcontainers/containers/startupcheck/StartupCheckStrategyTest.java b/docs/examples/junit4/generic/src/test/java/org/testcontainers/containers/startupcheck/StartupCheckStrategyTest.java
index deef64bc59b..c57b013362f 100644
--- a/docs/examples/junit4/generic/src/test/java/org/testcontainers/containers/startupcheck/StartupCheckStrategyTest.java
+++ b/docs/examples/junit4/generic/src/test/java/org/testcontainers/containers/startupcheck/StartupCheckStrategyTest.java
@@ -7,6 +7,7 @@
import org.junit.runners.Suite;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.WaitingConsumer;
+import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
@@ -35,7 +36,7 @@ private static void waitForHello(GenericContainer container) throws TimeoutExcep
public static class OneShotStrategyTest {
@Rule
// withOneShotStrategy {
- public GenericContainer bboxWithOneShot = new GenericContainer("busybox:1.31.1")
+ public GenericContainer> bboxWithOneShot = new GenericContainer<>(new DockerImageName("busybox:1.31.1"))
.withCommand(String.format("echo %s", HELLO_TESTCONTAINERS))
.withStartupCheckStrategy(
new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(3))
@@ -54,7 +55,7 @@ public void testCommandIsExecuted() {
public static class IndefiniteOneShotStrategyTest {
@Rule
// withIndefiniteOneShotStrategy {
- public GenericContainer bboxWithIndefiniteOneShot = new GenericContainer("busybox:1.31.1")
+ public GenericContainer> bboxWithIndefiniteOneShot = new GenericContainer<>(new DockerImageName("busybox:1.31.1"))
.withCommand("sh", "-c", String.format("sleep 5 && echo \"%s\"", HELLO_TESTCONTAINERS))
.withStartupCheckStrategy(
new IndefiniteWaitOneShotStartupCheckStrategy()
@@ -73,7 +74,7 @@ public void testCommandIsExecuted() {
public static class MinimumDurationStrategyTest {
@Rule
// withMinimumDurationStrategy {
- public GenericContainer bboxWithMinimumDuration = new GenericContainer("busybox:1.31.1")
+ public GenericContainer> bboxWithMinimumDuration = new GenericContainer<>(new DockerImageName("busybox:1.31.1"))
.withCommand("sh", "-c", String.format("sleep 5 && echo \"%s\"", HELLO_TESTCONTAINERS))
.withStartupCheckStrategy(
new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(1))
diff --git a/docs/examples/junit4/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java b/docs/examples/junit4/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java
index f2ef1ab0220..fe364ab4a81 100644
--- a/docs/examples/junit4/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java
+++ b/docs/examples/junit4/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java
@@ -5,6 +5,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
import static org.junit.Assert.assertEquals;
@@ -14,7 +15,7 @@ public class RedisBackedCacheIntTest {
// rule {
@Rule
- public GenericContainer redis = new GenericContainer<>("redis:5.0.3-alpine")
+ public GenericContainer redis = new GenericContainer(new DockerImageName("redis:5.0.3-alpine"))
.withExposedPorts(6379);
// }
diff --git a/docs/examples/junit5/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java b/docs/examples/junit5/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java
index 0f4f98fb669..da0a55cb536 100644
--- a/docs/examples/junit5/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java
+++ b/docs/examples/junit5/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java
@@ -6,6 +6,7 @@
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -17,7 +18,7 @@ public class RedisBackedCacheIntTest {
// container {
@Container
- public GenericContainer redis = new GenericContainer<>("redis:5.0.3-alpine")
+ public GenericContainer redis = new GenericContainer(new DockerImageName("redis:5.0.3-alpine"))
.withExposedPorts(6379);
// }
diff --git a/examples/disque-job-queue/src/test/java/DisqueBackedMailSenderTest.java b/examples/disque-job-queue/src/test/java/DisqueBackedMailSenderTest.java
index 5c78b3fdbd0..f6eed324304 100644
--- a/examples/disque-job-queue/src/test/java/DisqueBackedMailSenderTest.java
+++ b/examples/disque-job-queue/src/test/java/DisqueBackedMailSenderTest.java
@@ -4,10 +4,17 @@
import org.junit.Rule;
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
-import static org.rnorth.visibleassertions.VisibleAssertions.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
+import static org.rnorth.visibleassertions.VisibleAssertions.context;
+import static org.rnorth.visibleassertions.VisibleAssertions.info;
/**
* Created by rnorth on 03/01/2016.
@@ -15,7 +22,7 @@
public class DisqueBackedMailSenderTest {
@Rule
- public GenericContainer container = new GenericContainer("richnorth/disque:1.0-rc1")
+ public GenericContainer> container = new GenericContainer<>(new DockerImageName("richnorth/disque:1.0-rc1"))
.withExposedPorts(7711);
private DisqueClient disqueClient;
diff --git a/examples/disque-job-queue/src/test/java/SingleDisqueInstanceTest.java b/examples/disque-job-queue/src/test/java/SingleDisqueInstanceTest.java
index 8137f8fae90..30e3aa84066 100644
--- a/examples/disque-job-queue/src/test/java/SingleDisqueInstanceTest.java
+++ b/examples/disque-job-queue/src/test/java/SingleDisqueInstanceTest.java
@@ -7,10 +7,14 @@
import org.junit.Rule;
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
import java.util.concurrent.TimeUnit;
-import static org.rnorth.visibleassertions.VisibleAssertions.*;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
+import static org.rnorth.visibleassertions.VisibleAssertions.assertNotNull;
+import static org.rnorth.visibleassertions.VisibleAssertions.context;
+import static org.rnorth.visibleassertions.VisibleAssertions.info;
/**
* Created by rnorth on 03/01/2016.
@@ -18,7 +22,7 @@
public class SingleDisqueInstanceTest {
@Rule
- public GenericContainer container = new GenericContainer("richnorth/disque:1.0-rc1")
+ public GenericContainer> container = new GenericContainer<>(new DockerImageName("richnorth/disque:1.0-rc1"))
.withExposedPorts(7711);
private DisqueCommands connection;
private AddJobArgs retryAfter1Second;
diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..62d4c053550b91381bbd28b1afc82d634bf73a8a
GIT binary patch
literal 58910
zcma&ObC74zk}X`WF59+k+qTVL*+!RbS9RI8Z5v&-ZFK4Nn|tqzcjwK__x+Iv5xL`>
zj94dg?X`0sMHx^qXds{;KY)OMg#H>35XgTVfq6#vc9ww|9)
z@UMfwUqk)B9p!}NrNqTlRO#i!ALOPcWo78-=iy}NsAr~T8T0X0%G{DhX~u-yEwc29WQ4D
zuv2j{a&j?qB4wgCu`zOXj!~YpTNFg)TWoV>DhYlR^Gp^rkOEluvxkGLB?!{fD!T@(
z%3cy>OkhbIKz*R%uoKqrg1%A?)uTZD&~ssOCUBlvZhx7XHQ4b7@`&sPdT475?*zWy
z>xq*iK=5G&N6!HiZaD{NSNhWL;+>Quw_#ZqZbyglna!Fqn3N!$L`=;TFPrhodD-Q`
z1l*=DP2gKJP@)cwI@-M}?M$$$%u~=vkeC%>cwR$~?y6cXx-M{=wdT4|3X(@)a|KkZ
z`w$6CNS@5gWS7s7P86L<=vg$Mxv$?)vMj3`o*7W4U~*Nden}wz=y+QtuMmZ{(Ir1D
zGp)ZsNiy{mS}Au5;(fYf93rs^xvi(H;|H8ECYdC`CiC&G`zw?@)#DjMc7j~daL_A$
z7e3nF2$TKlTi=mOftyFBt8*Xju-OY@2k@f3YBM)-v8+5_o}M?7pxlNn)C0Mcd@87?+AA4{Ti2ptnYYKGp`^FhcJLlT%RwP4k$ad!ho}-^vW;s{6hnjD0*c39k
zrm@PkI8_p}mnT&5I@=O1^m?g}PN^8O8rB`;t`6H+?Su0IR?;8txBqwK1Au8O3BZAX
zNdJB{bpQWR@J|e=Z>XSXV1DB{uhr3pGf_tb)(cAkp)fS7*Qv))&Vkbb+cvG!j}ukd
zxt*C8&RN}5ck{jkw0=Q7ldUp0FQ&Pb_$M7a@^nf`8F%$ftu^jEz36d#^M8Ia{VaTy
z5(h$I)*l3i!VpPMW+XGgzL~fcN?{~1QWu9!Gu0jOWWE
zNW%&&by0DbXL&^)r-A*7R@;T$P}@3eOj#gqJ!uvTqBL5bupU91UK#d|IdxBUZAeh1
z>rAI#*Y4jv>uhOh7`S@mnsl0g@1C;k$Z%!d*n8#_$)l}-1&z2kr@M+xWoKR
z!KySy-7h&Bf}02%JeXmQGjO3ntu={K$jy$rFwfSV8!zqAL_*&e2|CJ06`4&0+ceI026REfNT>JzAdwmIlKLEr2?
zaZ#d*XFUN*gpzOxq)cysrzNdDDPH%
zd8_>3B}uA7;bP4fKVdd~Og@}dW#74ceETOE-
zlZgQqQfEc?-5ly(Z5`L_CCM!&Uxk5#wgo=OLs-kFHFG*cTZ)$VE?c_gQUW&*!2@W2
z7Lq&_Kf88OCo?BHCtwe*&fu&8PQ(R5&lnYo8%+U73U)Ec2&|A)Y~m7(^bh299REPe
zn#gyaJ4%o4>diN3z%P5&_aFUmlKytY$t21WGwx;3?UC}vlxi-vdEQgsKQ;=#sJ#ll
zZeytjOad$kyON4XxC}frS|Ybh`Yq!<(IrlOXP3*q86ImyV*mJyBn$m~?#xp;EplcM
z+6sez%+K}Xj3$YN6{}VL;BZ7Fi|iJj-ywlR+AP8lq~mnt5p_%VmN{Sq$L^z!otu_u
znVCl@FgcVXo510e@5(wnko%Pv+^r^)GRh;>#Z(|#cLnu_Y$#_xG&nvuT+~gzJsoSi
zBvX`|IS~xaold!`P!h(v|=>!5gk)Q+!0R1Ge7!WpRP{*Ajz$oGG$_?Ajvz6F0X?809o`L8prsJ*+LjlGfSziO;+
zv>fyRBVx#oC0jGK8$%$>Z;0+dfn8x;kHFQ?Rpi7(Rc{Uq{63Kgs{IwLV>pDK7yX-2
zls;?`h!I9YQVVbAj7Ok1%Y+F?CJa-Jl>1x#UVL(lpzBBH4(6v0^4
z3Tf`INjml5`F_kZc5M#^J|f%7Hgxg3#o}Zwx%4l9yYG!WaYUA>+dqpRE3nw#YXIX%=
ziH3iYO~jr0nP5xp*VIa#-aa;H&%>{mfAPPlh5Fc!N7^{!z$;p-p38aW{gGx
z)dFS62;V;%%fKp&i@+5x=Cn7Q>H`NofJGXmNeh{sOL+Nk>bQJJBw3K*H_$}%*xJM=Kh;s#$@RBR
z|75|g85da@#qT=pD777m$wI!Q8SC4Yw3(PVU53bzzGq$IdGQoFb-c_(iA_~qD|eAy
z@J+2!tc{|!8fF;%6rY9`Q!Kr>MFwEH%TY0y>Q(D}xGVJM{J{aGN0drG&|1xO!Ttdw
z-1^gQ&y~KS5SeslMmoA$Wv$ly={f}f9<{Gm!8ycp*D9m*5Ef{ymIq!MU01*)#J1_!
zM_i4{LYButqlQ>Q#o{~W!E_#(S=hR}kIrea_67Z5{W>8PD>g$f;dTvlD=X@T$8D0;BWkle@{VTd&D5^)U>(>g(jFt4lRV6A2(Te->ooI{nk-bZ(gwgh
zaH4GT^wXPBq^Gcu%xW#S#p_&x)pNla5%S5;*OG_T^PhIIw1gXP&u5c;{^S(AC*+$>
z)GuVq(FT@zq9;i{*9lEsNJZ)??BbSc5vF+Kdh-kL@`(`l5tB4P!9Okin2!-T?}(w%
zEpbEU67|lU#@>DppToestmu8Ce=gz=e#V+o)v)#e=N`{$MI5P0O)_fHt1@aIC_QCv=FO`Qf=Ga%^_NhqGI)xtN*^1n{
z&vgl|TrKZ3Vam@wE0p{c3xCCAl+RqFEse@r*a<3}wmJl-hoJoN<|O2zcvMRl<#BtZ
z#}-bPCv&OTw`GMp&n4tutf|er`@#d~7X+);##YFSJ)BitGALu}-N*DJdCzs(cQ?I-
z6u(WAKH^NUCcOtpt5QTsQRJ$}jN28ZsYx+4CrJUQ%egH
zo#tMoywhR*oeIkS%}%WUAIbM`D)R6Ya&@sZvvUEM7`fR0Ga03*=qaEGq4G7-+30Ck
zRkje{6A{`ebq?2BTFFYnMM$xcQbz0nEGe!s%}O)m={`075R0N9KTZ>vbv2^eml>@}722%!r#6Wto}?vNst?
zs`IasBtcROZG9+%rYaZe^=5y3chDzBf>;|5sP0!sP(t^=
z^~go8msT@|rp8LJ8km?4l?Hb%o10h7(ixqV65~5Y>n_zG3AMqM3UxUNj6K-FUgMT7
z*Dy2Y8Ws+%`Z*~m9P
zCWQ8L^kA2$rf-S@qHow$J86t)hoU#XZ2YK~9GXVR|*`f6`0&8j|ss_Ai-x=_;Df^*&=bW$1nc{Gplm
zF}VF`w)`5A;W@KM`@<9Bw_7~?_@b{Z`n_A6c1AG#h#>Z$K>gX6reEZ*bZRjCup|0#
zQ{XAb`n^}2cIwLTN%5Ix`PB*H^(|5S{j?BwItu+MS`1)VW=TnUtt6{3J!WR`4b`LW
z?AD#ZmoyYpL=903q3LSM=&5eNP^dwTDRD~iP=}FXgZ@2WqfdyPYl$9do?wX{RU*$S
zgQ{OqXK-Yuf4+}x6P#A*la&^G2c2TC;aNNZEYuB(f25|5eYi|rd$;i0qk7^3Ri8of
ziP~PVT_|4$n!~F-B1_Et<0OJZ*e+MN;5FFH`iec(lHR+O%O%_RQhvbk-NBQ+$)w{D+dlA0jxI;z|P
zEKW`!X)${xzi}Ww5G&@g0akBb_F`ziv$u^hs0W&FXuz=Ap>SUMw9=M?X$`lgPRq11
zqq+n44qL;pgGO+*DEc+Euv*j(#%;>p)yqdl`dT+Og
zZH?FXXt`<0XL2@PWYp|7DWzFqxLK)yDXae&3P*#+f+E{I&h=$UPj;ey9b`H?qe*Oj
zV|-qgI~v%&oh7rzICXfZmg$8$B|zkjliQ=e4jFgYCLR%yi!9gc7>N
z&5G#KG&Hr+UEfB;M(M>$Eh}P$)<_IqC_WKOhO4(cY@Gn4XF(#aENkp&D{sMQgrhDT
zXClOHrr9|POHqlmm+*L6CK=OENXbZ+kb}t>oRHE2xVW<;VKR@ykYq04LM9L-b;eo&
zl!QQo!Sw{_$-qosixZJWhciN>Gbe8|vEVV2l)`#5vKyrXc6E`zmH(76nGRdL)pqLb@j<&&b!qJRLf>d`rdz}^ZSm7E;+XUJ
ziy;xY&>LM?MA^v0Fu8{7hvh_ynOls6CI;kQkS2g^OZr70A}PU;i^~b_hUYN1*j-DD
zn$lHQG9(lh&sDii)ip*{;Sb_-Anluh`=l~qhqbI+;=ZzpFrRp&T+UICO!OoqX@Xr_
z32iJ`xSpx=lDDB_IG}k+GTYG@K8{rhTS)aoN8D~Xfe?ul&;jv^E;w$nhu-ICs&Q)%
zZ=~kPNZP0-A$pB8)!`TEqE`tY3Mx^`%O`?EDiWsZpoP`e-iQ#E>fIyUx8XN0L
z@S-NQwc;0HjSZKWDL}Au_Zkbh!juuB&mGL0=nO5)tUd_4scpPy&O7SNS^aRxUy0^<
zX}j*jPrLP4Pa0|PL+nrbd4G;YCxCK-=G7TG?dby~``AIHwxqFu^OJhyIUJkO0O<>_
zcpvg5Fk$Wpj}YE3;GxRK67P_Z@1V#+pu>pRj0!mFf(m_WR3w3*oQy$s39~U7Cb}p(N&8SEwt+)@%o-kW9Ck=^?tvC2$b9%
ze9(Jn+H`;uAJE|;$Flha?!*lJ0@lKfZM>B|c)3lIAHb;5OEOT(2453m!LgH2AX=jK
zQ93An1-#l@I@mwB#pLc;M7=u6V5IgLl>E%gvE|}Hvd4-bE1>gs(P^C}gTv*&t>W#+
zASLRX$y^DD3Jrht
zwyt`yuA1j(TcP*0p*Xkv>gh+YTLrcN_HuaRMso~0AJg`^nL#52dGBzY+_7i)Ud#X)
zVwg;6$WV20U2uyKt8<)jN#^1>PLg`I`@Mmut*Zy!c!zshSA!e^tWVoKJD%jN&ml#{
z@}B$j=U5J_#rc%T7(DGKF+WwIblEZ;Vq;CsG~OKxhWYGJx#g7fxb-_ya*D0=_Ys#f
zhXktl=Vnw#Z_neW>Xe#EXT(4sT^3p6srKby4Ma5LLfh6XrHGFGgM;5Z}jv-T!f~=jT&n>Rk
z4U0RT-#2fsYCQhwtW&wNp6T(im4dq>363H^ivz#>Sj;TEKY<)dOQU=g=XsLZhnR>e
zd}@p1B;hMsL~QH2Wq>9Zb;
zK`0`09fzuYg9MLJe~cdMS6oxoAD{kW3sFAqDxvFM#{GpP^NU@9$d5;w^WgLYknCTN
z0)N425mjsJTI@#2kG-kB!({*+S(WZ-{SckG5^OiyP%(6DpRsx60$H8M$V65a_>oME
z^T~>oG7r!ew>Y)&^MOBrgc-3PezgTZ2xIhXv%ExMFgSf5dQbD=Kj*!J4k^Xx!Z>AW
ziZfvqJvtm|EXYsD%A|;>m1Md}j5f2>kt*gngL=enh<>#5iud0dS1P%u2o+>VQ{U%(nQ_WTySY(s#~~>
zrTsvp{lTSup_7*Xq@qgjY@1#bisPCRMMHnOL48qi*jQ0xg~TSW%KMG9zN1(tjXix()2$N}}K$AJ@GUth+AyIhH6Aeh7qDgt#t*`iF5#A&g4+
zWr0$h9Zx6&Uo2!Ztcok($F>4NA<`dS&Js%L+67FT@WmI)z#fF~S75TUut%V($oUHw
z$IJsL0X$KfGPZYjB9jaj-LaoDD$OMY4QxuQ&vOGo?-*9@O!Nj>QBSA6n$Lx|^
zky)4+sy{#6)FRqRt6nM9j2Lzba!U;aL%ZcG&ki1=3gFx6(&A3J-oo|S2_`*w9zT)W
z4MBOVCp}?4nY)1))SOX#6Zu0fQQ7V{RJq{H)S#;sElY)S)lXTVyUXTepu4N)n85Xo
zIpWPT&rgnw$D2Fsut#Xf-hO&6uA0n~a;a3!=_!Tq^TdGE&<*c?1b|PovU}3tfiIUu
z){4W|@PY}zJOXkGviCw^x27%K_Fm9GuKVpd{P2>NJlnk^I|h2XW0IO~LTMj>2<;S*
zZh2uRNSdJM$U$@=`zz}%;ucRx{aKVxxF7?0hdKh6&GxO6f`l2kFncS3xu0Ly{ew0&
zeEP*#lk-8-B$LD(5yj>YFJ{yf5zb41PlW7S{D9zC4Aa4nVdkDNH{UsFJp)q-`9OYt
zbOKkigbmm5hF?tttn;S4g^142AF^`kiLUC?e7=*JH%Qe>uW=dB24NQa`;lm5yL>Dyh@HbHy-f%6Vz^
zh&MgwYsh(z#_fhhqY$3*f>Ha}*^cU-r4uTHaT?)~LUj5``FcS46oyoI5F3ZRizVD%
zPFY(_S&5GN8$Nl2=+YO6j4d|M6O7CmUyS&}m4LSn6}J`$M0ZzT&Ome)ZbJDFvM&}A
zZdhDn(*viM-JHf84$!I(8eakl#zRjJH4qfw8=60
z11Ely^FyXjVvtv48-Fae7p=adlt9_F^j5#ZDf7)n!#j?{W?@j$Pi=k`>Ii>XxrJ?$
z^bhh|X6qC8d{NS4rX5P!%jXy=>(P+r9?W(2)|(=a^s^l~x*^$Enw$~u%WRuRHHFan{X|S;FD(Mr
z@r@h^@Bs#C3G;~IJMrERd+D!o?HmFXi|~q(7QR3f8QDip?ms6|GV_$86aDb|5pc?_-jo6vmWqYi{P#?{m_AesA4xX
zi&ki&lh0yvf*Yw~@jt|r-=zpj!bw<6zI3Aa^Wq{|*WEC}I=O!Re!l~&8|Vu<$yZ1p
zs-SlwJD8K!$(WWyhZ+sOqa8cciwvyh%zd`r$u;;fsHn!hub0VU)bUv^QH?x30#;tH
zTc_VbZj|prj7)d%ORU;Vs{#ERb>K8>GOLSImnF7JhR|g$7FQTU{(a7RHQ*ii-{U3X
z^7+vM0R$8b3k1aSU&kxvVPfOz3~)0O2iTYinV9_5{pF18j4b{o`=@AZIOAwwedB2@
ztXI1F04mg{<>a-gdFoRjq$6#FaevDn$^06L)k%wYq03&ysdXE+LL1#w$rRS1Y;BoS
zH1x}{ms>LHWmdtP(ydD!aRdAa(d@csEo
z0EF9L>%tppp`CZ2)jVb8AuoYyu;d^wfje6^n6`A?6$&%$p>HcE_De-Zh)%3o5)LDa
zskQ}%o7?bg$xUj|n8gN9YB)z!N&-K&!_hVQ?#SFj+MpQA4@4oq!UQ$Vm3B`W_Pq3J
z=ngFP4h_y=`Iar<`EESF9){%YZVyJqLPGq07TP7&fSDmnYs2NZQKiR%>){imTBJth
zPHr@p>8b+N@~%43rSeNuOz;rgEm?14hNtI|KC6Xz1d?|2J`QS#`OW7gTF_;TPPxu@
z)9J9>3Lx*bc>Ielg|F3cou$O0+<b34_*ZJhpS&$8DP>s%47a)4ZLw`|>s=P_J4u
z?I_%AvR_z8of@UYWJV?~c4Yb|A!9n!LEUE6{sn@9+D=0w_-`szJ_T++x3MN$v-)0d
zy`?1QG}C^KiNlnJBRZBLr4G~15V3$QqC%1G5b#CEB0VTr#z?Ug%Jyv@a`QqAYUV~^
zw)d|%0g&kl{j#FMdf$cn(~L@8s~6eQ)6{`ik(RI(o9s0g30Li{4YoxcVoYd+LpeLz
zai?~r)UcbYr@lv*Z>E%BsvTNd`Sc?}*}>mzJ|cr0Y(6rA7H_6&t>F{{mJ^xovc2a@
zFGGDUcGgI-z6H#o@Gj29C=Uy{wv
zQHY2`HZu8+sBQK*_~I-_>fOTKEAQ8_Q~YE$c?cSCxI;vs-JGO`RS464Ft06rpjn+a
zqRS0Y3oN(9HCP@{J4mOWqIyD8PirA!pgU^Ne{LHBG;S*bZpx3|JyQDGO&(;Im8!ed
zNdpE&?3U?E@O~>`@B;oY>#?gXEDl3pE@J30R1;?QNNxZ?YePc)3=NS>!STCrXu*lM
z69WkLB_RBwb1^-zEm*tkcHz3H;?v
z;q+x0Jg$|?5;e1-kbJnuT+^$bWnYc~1qnyVTKh*cvM+8yJT-HBs1X@cD;L$su65;i
z2c1MxyL~NuZ9+)hF=^-#;dS#lFy^Idcb>AEDXu1!G4Kd8YPy~0lZz$2gbv?su}Zn}
zGtIbeYz3X8OA9{sT(aleold_?UEV{hWRl(@)NH6GFH@$<8hUt=dNte%e#Jc>7u9xi
zuqv!CRE@!fmZZ}3&@$D>p0z=*dfQ_=IE4bG0hLmT@OP>x$e`qaqf_=#baJ8XPtOpWi%$ep1Y)o2(sR=v)M
zt(z*pGS$Z#j_xq_lnCr+x9fwiT?h{NEn#iK(o)G&Xw-#DK?=Ms6T;%&EE${Gq_%99
z6(;P~jPKq9llc+cmI(MKQ6*7PcL)BmoI}MYFO)b3-{j>9FhNdXLR<^mnMP`I7z0v`
zj3wxcXAqi4Z0kpeSf>?V_+D}NULgU$DBvZ^=0G8Bypd7P2>;u`yW9`%4~&tzNJpgp
zqB+iLIM~IkB;ts!)exn643mAJ8-WlgFE%Rpq!UMYtB?$5QAMm)%PT0$$2{>Yu7&U@
zh}gD^Qdgu){y3ANdB5{75P;lRxSJPSpQPMJOiwmpMdT|?=q;&$aTt|dl~kvS
z+*i;6cEQJ1V`R4Fd>-Uzsc=DPQ7A7#VPCIf!R!KK%LM&G%MoZ0{-8&99H!|UW$Ejv
zhDLX3ESS6CgWTm#1ZeS2HJb`=UM^gsQ84dQpX(ESWSkjn>O
zVxg%`@mh(X9&&wN$lDIc*@>rf?C0AD_mge3f2KkT6kGySOhXqZjtA?5z`vKl_{(5g
z&%Y~9p?_DL{+q@siT~*3Q*$nWXQfNN;%s_eHP_A;O`N`SaoB
z6xYR;z_;HQ2xAa9xKgx~2f2xEKiEDpGPH1d@||v#f#_Ty6_gY>^oZ#xac?pc-F`@
z*}8sPV@xiz?efDMcmmezYVw~qw=vT;G1xh+xRVBkmN66!u(mRG3G6P#v|;w@anEh7
zCf94arw%YB*=&3=RTqX?z4mID$W*^+&d6qI*LA-yGme;F9+wTsNXNaX~zl2+qIK&D-aeN4lr0+yP;W>|Dh?ms_ogT{DT+
ztXFy*R7j4IX;w@@R9Oct5k2M%&j=c_rWvoul+`
z<18FH5D@i$P38W9VU2(EnEvlJ(SHCqTNBa)brkIjGP|jCnK&Qi%97tikU}Y#3L?s!
z2ujL%YiHO-#!|g5066V01hgT#>fzls7P>+%D~ogOT&!Whb4iF=CnCto82Yb#b`YoVsj
zS2q^W0Rj!RrM@=_GuPQy5*_X@Zmu`TKSbqEOP@;Ga&Rrr>#H@L41@ZX)LAkbo{G8+
z;!5EH6vv-ip0`tLB)xUuOX(*YEDSWf?PIxXe`+_B8=KH#HFCfthu}QJylPMTNmoV;
zC63g%?57(&osaH^sxCyI-+gwVB|Xs2TOf=mgUAq?V~N_5!4A=b{AXbDae+yABuuu3B_XSa4~c
z1s-OW>!cIkjwJf4ZhvT|*IKaRTU)WAK=G|H#B5#NB9<{*kt?7`+G*-^<)7$Iup@Um
z7u*ABkG3F*Foj)W9-I&@BrN8(#$7Hdi`BU#SR1Uz4rh&=Ey!b76Qo?RqBJ!U+rh(1
znw@xw5$)4D8OWtB_^pJO*d~2Mb-f~>I!U#*=Eh*xa6$LX?4Evp4%;ENQR!mF4`f7F
zpG!NX=qnCwE8@NAbQV`*?!v0;NJ(|
zBip8}VgFVsXFqslXUV>_Z>1gmD(7p#=WACXaB|Y`=Kxa=p@_ALsL&yAJ`*QW^`2@%
zW7~Yp(Q@ihmkf{vMF?kqkY%SwG^t&CtfRWZ{syK@W$#DzegcQ1>~r7foTw3^V1)f2Tq_5f$igmfch;8
zT-<)?RKcCdQh6x^mMEOS;4IpQ@F2q-4IC4%*dU@jfHR4UdG>Usw4;7ESpORL|2^#jd+@zxz{(|RV*1WKrw-)ln*8LnxVkKDfGDHA%7`HaiuvhMu%*mY9*Ya{Ti#{DW?i0
zXXsp+Bb(_~wv(3t70QU3a$*<$1&zm1t++x#wDLCRI4K)kU?Vm9n2c0m@TyUV&&l9%}fulj!Z9)&@yIcQ3gX}l0b1LbIh4S
z5C*IDrYxR%qm4LVzSk{0;*npO_SocYWbkAjA6(^IAwUnoAzw_Uo}xYFo?Y<-4Zqec
z&k7HtVlFGyt_pA&kX%P8PaRD8y!Wsnv}NMLNLy-CHZf(ObmzV|t-iC#@Z9*d-zUsx
zxcYWw{H)nYXVdnJu5o-U+fn~W
z-$h1ax>h{NlWLA7;;6TcQHA>UJB$KNk74T1xNWh9)kwK~wX0m|Jo_Z;g;>^E4-k4R
zRj#pQb-Hg&dAh}*=2;JY*aiNZzT=IU&v|lQY%Q|=^V5pvTR7^t9+@+ST&sr!J1Y9a
z514dYZn5rg6@4Cy6P`-?!3Y&
z?B*5zw!mTiD2)>f@3XYrW^9V-@%YFkE_;PCyCJ7*?_3cR%tHng9%ZpIU}LJM=a+0s
z(SDDLvcVa~b9O!cVL8)Q{d^R^(bbG=Ia$)dVN_tGMee3PMssZ7Z;c^Vg_1CjZYTnq
z)wnF8?=-MmqVOMX!iE?YDvHCN?%TQtKJMFHp$~kX4}jZ;EDqP$?jqJZjoa2PM@$uZ
zF4}iab1b5ep)L;jdegC3{K4VnCH#OV;pRcSa(&Nm50ze-yZ8*cGv;@+N+A?ncc^2z9~|(xFhwOHmPW@
zR5&)E^YKQj@`g=;zJ_+CLamsPuvppUr$G1#9urUj+p-mPW_QSSHkPMS!52t>Hqy|g
z_@Yu3z%|wE=uYq8G>4`Q!4zivS}+}{m5Zjr7kMRGn_p&hNf|pc&f9iQ`^%78rl#~8
z;os@rpMA{ZioY~(Rm!Wf#Wx##A0PthOI341QiJ=G*#}pDAkDm+{0kz&*NB?rC0-)glB{0_Tq*^o
zVS1>3REsv*Qb;qg!G^9;VoK)P*?f<*H&4Su1=}bP^Y<2PwFpoqw#up4IgX3L
z`w~8jsFCI3k~Y9g(Y9Km`y$0FS5vHb)kb)Jb6q-9MbO{Hbb
zxg?IWQ1ZIGgE}wKm{axO6CCh~4DyoFU+i1xn#oyfe+<{>=^B5tm!!*1M?AW8c=6g+%2Ft97_Hq&ZmOGvqGQ!Bn<_Vw`0DRuDoB6q8ME<;oL4kocr8E$NGoLI
zXWmI7Af-DR|KJw!vKp2SI4W*x%A%5BgDu%8%Iato+pWo5`vH@!XqC!yK}KLzvfS(q
z{!y(S-PKbk!qHsgVyxKsQWk_8HUSSmslUA9nWOjkKn0%cwn%yxnkfxn?Y2rysXKS=t-TeI%DN$sQ{lcD!(s>(4y#CSxZ4R}
zFDI^HPC_l?uh_)-^ppeYRkPTPu~V^0Mt}#jrTL1Q(M;qVt4zb(L|J~sxx7Lva9`mh
zz!#A9tA*6?q)xThc7(gB2Ryam$YG4qlh00c}r&$y6u
zIN#Qxn{7RKJ+_r|1G1KEv!&uKfXpOVZ8tK{M775ws%nDyoZ?bi3NufNbZs)zqXiqc
zqOsK@^OnlFMAT&mO3`@3nZP$3lLF;ds|;Z{W(Q-STa2>;)tjhR17OD|G>Q#zJHb*>
zMO<{WIgB%_4MG0SQi2;%f0J8l_FH)Lfaa>*GLobD#AeMttYh4Yfg22@q4|Itq};NB
z8;o*+@APqy@fPgrc&PTbGEwdEK=(x5K!If@R$NiO^7{#j9{~w=RBG)ZkbOw@$7Nhl
zyp{*&QoVBd5lo{iwl2gfyip@}IirZK;ia(&ozNl!-EEYc=QpYH_=
zJkv7gA{!n4up6$CrzDJIBAdC7D5D<_VLH*;OYN>_Dx3AT`K4Wyx8Tm{I+xplKP6k7
z2sb!i7)~%R#J0$|hK?~=u~rnH7HCUpsQJujDDE*GD`qrWWog+C+E~GGy|Hp_t4--}
zrxtrgnPh}r=9o}P6jpAQuDN}I*GI`8&%Lp-C0IOJt#op)}XSr!ova@w{jG2V=?GXl3zEJJFXg)U3N>BQP
z*Lb@%Mx|Tu;|u>$-K(q^-HG!EQ3o93%w(A7@ngGU)HRWoO&&^}U$5x+Tzri>6ct
zXOB#EF-;z3j311K`jrYyv6pOPF=*`SOz!ack=DuEi({UnAkL5H)@R?YbRKAeP|06U
z?-Ns0ZxD0h9D8)P66Sq$w-yF+1hEVTaul%&=kKDrQtF<$RnQPZ)ezm1`aHIjAY=!S
z`%vboP`?7mItgEo4w50C*}Ycqp9_3ZEr^F1;cEhkb`BNhbc6PvnXu@wi=AoezF4~K
zkxx%ps<8zb=wJ+9I8o#do)&{(=yAlNdduaDn!=xGSiuo~fLw~Edw$6;l-qaq#Z7?#
zGrdU(Cf-V@$x>O%yRc6!C1Vf`b19ly;=mEu8u9|zitcG^O`lbNh}k=$%a)UHhDwTEKis2yc4rBGR>l*(B$AC7ung&ssaZGkY-h(fpwcPyJSx*9EIJMRKbMP9}$nVrh6$g-Q^5Cw)BeWqb-qi#37ZXKL!GR;ql)~
z@PP*-oP?T|ThqlGKR84zi^CN
z4TZ1A)7vL>ivoL2EU_~xl-P{p+sE}9CRwGJDKy{>0KP+gj`H9C+4fUMPnIB1_D`A-
z$1`G}g0lQmqMN{Y&8R*$xYUB*V}dQPxGVZQ+rH!DVohIoTbh%#z#Tru%Px@C<=|og
zGDDwGq7yz`%^?r~6t&>x*^We^tZ4!E4dhwsht#Pb1kCY{q#Kv;z%Dp#Dq;$vH$-(9
z8S5tutZ}&JM2Iw&Y-7KY4h5BBvS=Ove0#+H2qPdR)WyI
zYcj)vB=MA{7T|3Ij_PN@FM@w(C9ANBq&|NoW30ccr~i#)EcH)T^3St~rJ0HKKd4wr
z@_+132;Bj+>UC@h)Ap*8B4r5A1lZ!Dh%H7&&hBnlFj@eayk=VD*i5AQc
z$uN8YG#PL;cuQa)Hyt-}R?&NAE1QT>svJDKt*)AQOZAJ@
zyxJoBebiobHeFlcLwu_iI&NEZuipnOR;Tn;PbT1Mt-#5v5b*8ULo7m)L-eti=UcGf
zRZXidmxeFgY!y80-*PH-*=(-W+fK%KyUKpg$X@tuv``tXj^*4qq@UkW$ZrAo%+hay
zU@a?z&2_@y)o@D!_g>NVxFBO!EyB&6Z!nd4=KyDP^hl!*(k{dEF6@NkXztO7gIh
zQ&PC+p-8WBv;N(rpfKdF^@Z~|E6pa)M1NBUrCZvLRW$%N%xIbv^uv?=C!=dDVq3%*
zgvbEBnG*JB*@vXx8>)7XL*!{1Jh=#2UrByF7U?Rj_}VYw88BwqefT_cCTv8aTrRVjnn
z1HNCF=44?*&gs2`vCGJVHX@kO
z240eo#z+FhI0=yy6NHQwZs}a+J~4U-6X`@
zZ7j+tb##m`x%J66$a9qXDHG&^kp|GkFFMmjD(Y-k_ClY~N$H|n@NkSDz=gg?*2ga5
z)+f)MEY>2Lp15;~o`t`qj;S>BaE;%dv@Ux11yq}I(k|o&`5UZFUHn}1kE^gIK@qV&
z!S2IhyU;->VfA4Qb}m7YnkIa9%z{l~iPWo2YPk-`hy2-Eg=6E$21plQA5W2qMZDFU
z-a-@Dndf%#on6chT`dOKnU9}BJo|kJwgGC<^nfo34zOKH96LbWY7@Wc%EoFF=}`VU
zksP@wd%@W;-p!e^&-)N7#oR331Q)@9cx=mOoU?_Kih2!Le*8fhsZ8Qvo6t2vt+UOZ
zw|mCB*t2%z21YqL>whu!j?s~}-L`OS+jdg1(XnmYw$rg~r(?5Y+qTg`$F}q3J?GtL
z@BN&8#`u2RqkdG4yGGTus@7U_%{6C{XAhFE!2SelH?KtMtX@B1GBhEIDL-Bj#~{4!
zd}p7!#XE9Lt;sy@p5#Wj*jf8zGv6tTotCR2X$EVOOup;GnRPRVU5A6N@Lh8?eA7k?
zn~hz&gY;B0ybSpF?qwQ|sv_yO=8}zeg2$0n3A8KpE@q26)?707pPw?H76lCpjp=5r
z6jjp|auXJDnW}uLb6d7rsxekbET9(=zdTqC8(F5@NNqII2+~yB;X5iJNQSiv`#ozm
zf&p!;>8xAlwoxUC3DQ#!31ylK%VrcwS<$WeCY4V63V!|221oj+5#r}fGFQ}|uwC0)
zNl8(CF}PD`&Sj+p{d!B&&JtC+VuH
z#>US`)YQrhb6lIAYb08H22y(?)&L8MIQsA{26X`R5Km{YU)s!x(&gIsjDvq63@X`{
z=7{SiH*_ZsPME#t2m|bS76Uz*z{cpp1m|s}HIX}Ntx#v7Eo!1%G9__4dGSGl`p+xi
zZ!VK#Qe;Re=9bqXuW+0DSP{uZ5-QXrNn-7qW19K0qU}OhVru7}3vqsG?#D67
zb}crN;QwsH*vymw(maZr_o|w&@sQki(X+D)gc5Bt&@iXisFG;eH@5d43~Wxq|HO(@
zV-rip4n#PEkHCWCa5d?@cQp^B;I-PzOfag|t-cuvTapQ@MWLmh*41NH`<+A+JGyKX
zyYL6Ba7qqa5j@3lOk~`OMO7f0!@FaOeZxkbG@vXP(t3#U*fq8=GAPqUAS>vW2uxMk{a(<0=IxB;#
zMW;M+owrHaZBp`3{e@7gJCHP!I(EeyGFF;pdFPdeP+KphrulPSVidmg#!@W`GpD&d
z9p6R`dpjaR2E1Eg)Ws{BVCBU9-aCgN57N~uLvQZH`@T+2eOBD%73rr&sV~m#2~IZx
zY_8f8O;XLu2~E3JDXnGhFvsyb^>*!D>5EtlKPe%kOLv6*@=Jpci`8h0z?+fbBUg_7
zu6DjqO=$SjAv{|Om5)nz41ZkS4E_|fk%NDY509VV5yNeo%O|sb>7C#wj8mL9cEOFh
z>nDz%?vb!h*!0dHdnxDA>97~EoT~!N40>+)G2CeYdOvJr5^VnkGz)et&T9hrD(VAgCAJjQ7V$O?csICB*HFd^k@$M5*v$PZJD-OVL?Ze(U=XGqZPVG8JQ
z<~ukO%&%nNXYaaRibq#B1KfW4+XMliC*Tng2G(T1VvP;2K~;b$EAqthc${gjn_P!b
zs62UT(->A>!ot}cJXMZHuy)^qfqW~xO-In2);e>Ta{LD6VG2u&UT&a@>r-;4<)cJ9
zjpQThb4^CY)Ev0KR7TBuT#-v}W?Xzj{c7$S5_zJA57Qf=$4^npEjl9clH0=jWO8sX
z3Fuu0@S!WY>0XX7arjH`?)I<%2|8HfL!~#c+&!ZVmhbh`wbzy0Ux|Jpy9A{_7GGB0
zadZ48dW0oUwUAHl%|E-Q{gA{z6TXsvU#Hj09<7i)d}wa+Iya)S$CVwG{4LqtB>w%S
zKZx(QbV7J9pYt`W4+0~f{hoo5ZG<0O&&5L57oF%hc0xGJ@Zrg_D&lNO=-I^0y#3mxCSZFxN2-tN_mU@7<@PnWG?L5OSqkm8TR!`|
zRcTeWH~0z1JY^%!N<(TtxSP5^G9*Vw1wub`tC-F`=U)&sJVfvmh#Pi`*44kSdG};1
zJbHOmy4Ot|%_?@$N?RA9fF?|CywR8Sf(SCN_luM8>(u0NSEbKUy7C(Sk&OuWffj)f
za`+mo+kM_8OLuCUiA*CNE|?jra$M=$F3t+h-)?pXz&r^F!ck;r##`)i)t?AWq-9A9
zSY{m~TC1w>HdEaiR*%j)L);H{IULw)uxDO>#+WcBUe^HU)~L|9#0D<*Ld459xTyew
zbh5vCg$a>`RCVk)#~ByCv@Ce!nm<#EW|9j><#jQ8JfTmK#~jJ&o0Fs9jz0Ux{svdM4__<1
zrb>H(qBO;v(pXPf5_?XDq!*3KW^4>(XTo=6O2MJdM^N4IIcYn1sZZpnmMAEdt}4SU
zPO54j2d|(xJtQ9EX-YrlXU1}6*h{zjn`in-N!Ls}IJsG@X&lfycsoCemt_Ym(PXhv
zc*QTnkNIV=Ia%tg%pwJtT^+`v8ng>;2~ps~wdqZSNI7+}-3r+#r6p`8*G;~bVFzg=
z!S3&y)#iNSUF6z;%o)%h!ORhE?CUs%g(k2a-d576uOP2@QwG-6LT*G!I$JQLpd`cz
z-2=Brr_+z96a0*aIhY2%0(Sz=|D`_v_7h%Yqbw2)8@1DwH4s*A82krEk{
zoa`LbCdS)R?egRWNeHV8KJG0Ypy!#}kslun?67}^+J&02!D??lN~t@;h?GS8#WX`)6yC**~5YNhN_Hj}YG<%2ao^bpD8RpgV|V|GQwlL27B
zEuah|)%m1s8C6>FLY0DFe9Ob66fo&b8%iUN=y_Qj;t3WGlNqP9^d#75ftCPA*R4E8
z)SWKBKkEzTr4JqRMEs`)0;x8C35yRAV++n(Cm5++?WB@ya=l8pFL`N0ag`lWhrYo3
zJJ$<
zQ*_YAqIGR*;`VzAEx1Pd4b3_oWtdcs7LU2#1#Ls>Ynvd8k^M{Ef?8`RxA3!Th-?ui{_WJvhzY4FiPxA?E4+NFmaC-Uh*a
zeLKkkECqy>Qx&1xxEhh8SzMML=8VP}?b*sgT9ypBLF)Zh#w&JzP>ymrM?nnvt!@$2
zh>N$Q>mbPAC2kNd&ab;FkBJ}39s*TYY0=@e?N7GX>wqaM>P=Y12lciUmve_jMF0lY
zBfI3U2{33vWo(DiSOc}!5##TDr|dgX1Uojq9!vW3$m#zM_83EGsP6&O`@v-PDdO3P
z>#!BEbqpOXd5s?QNnN!p+92SHy{sdpePXHL{d@c6UilT<#~I!tH$S(~o}c#(j<2%!
zQvm}MvAj-95Ekx3D4+|e%!?lO(F+DFw9bxb-}rsWQl)b44###eUg4N?N-P(sFH2hF
z`{zu?LmAxn2=2wCE8?;%ZDi#Y;Fzp+RnY8fWlzVz_*PDO6?Je&aEmuS>=uCXgdP6r
zoc_JB^TA~rU5*geh{G*gl%_HnISMS~^@{@KVC;(aL^ZA-De+1zwUSXgT>OY)W?d6~
z72znET0m`53q%AVUcGraYxIcAB?OZA8AT!uK8jU+=t;WneL~|IeQ>$*dWa#x%rB(+
z5?xEkZ&b{HsZ4Ju9TQ|)c_SIp`7r2qMJgaglfSBHhl)QO1aNtkGr0LUn{@mvAt=}nd7#>7ru}&I)FNsa*x?Oe3-4G`HcaR
zJ}c%iKlwh`x)yX1vBB;-Nr=7>$~(u=AuPX2#&Eh~IeFw%afU+U)td0KC!pHd
zyn+X$L|(H3uNit-bpn7%G%{&LsAaEfEsD?yM<;U2}WtD4KuVKuX=ec9X
zIe*ibp1?$gPL7<0uj*vmj2lWKe`U(f9E{KVbr&q*RsO;O>K{i-7W)8KG5~~uS++56
zm@XGrX@x+lGEjDQJp~XCkEyJG5Y57omJhGN{^2z5lj-()PVR&wWnDk2M?n_TYR(gM
zw4kQ|+i}3z6YZq8gVUN}KiYre^sL{ynS}o{z$s&I
z{(rWaLXxcQ=MB(Cz7W$??Tn*$1y(7XX)tv;I-{7F$fPB%6YC7>-Dk#=Y8o1=&|>t5
zV_VVts>Eb@)&4%m}!K*WfLoLl|3FW)V~E1Z!yu`Sn+bAP5sRDyu7NEbLt?khAyz-ZyL-}MYb&nQ
zU16f@q7E1rh!)d%f^tTHE3cVoa%Xs%rKFc|temN1sa)aSlT*)*4k?Z>b3NP(IRXfq
zlB^#G6BDA1%t9^Nw1BD>lBV(0XW5c?l%vyB3)q*;Z5V~SU;HkN;1kA3Nx!$!9wti=
zB8>n`gt;VlBt%5xmDxjfl0>`K$fTU-C6_Z;!A_liu0@Os5reMLNk;jrlVF^FbLETI
zW+Z_5m|ozNBn7AaQ<&7zk}(jmEdCsPgmo%^GXo>YYt82n&7I-uQ%A;k{nS~VYGDTn
zlr3}HbWQG6xu8+bFu^9%%^PYCbkLf=*J|hr>Sw+#l(Y#ZGKDufa#f-f0k-{-XOb4i
zwVG1Oa0L2+&(u$S7TvedS<1m45*>a~5tuOZ;3x%!f``{=2QQlJk|b4>NpD4&L+xI+
z+}S(m3}|8|Vv(KYAGyZK5x*sgwOOJklN0jsq|BomM>OuRDVFf_?cMq%B*iQ*&|vS9
zVH7Kh)SjrCBv+FYAE=$0V&NIW=xP>d-s7@wM*sdfjVx6-Y@=~>rz%2L*rKp|*WXIz
z*vR^4tV&7MQpS9%{9b*>E9d_ls|toL7J|;srnW{l-}1gP_Qr-bBHt=}PL@WlE|&KH
zCUmDLZb%J$ZzNii-5VeygOM?K8e$EcK=z-hIk63o4y63^_*RdaitO^THC{boKstphXZ2Z+&3ToeLQUG(0Frs?b
zCxB+65h7R$+LsbmL51Kc)pz_`YpGEzFEclzb=?FJ=>rJwgcp0QH-UuKRS1*yCHsO)
z-8t?Zw|6t($Eh&4K+u$I7HqVJBOOFCRcmMMH};RX_b?;rnk`rz@vxT_&|6V@q0~Uk
z9ax|!pA@Lwn8h7syrEtDluZ6G!;@=GL>
zse#PRQrdDs=qa_v@{Wv(3YjYD0|qocDC;-F~&{oaTP?@pi$n
z1L6SlmFU2~%)M^$@C(^cD!y)-2SeHo3t?u3JiN7UBa7E2
z;<+_A$V084@>&u)*C<4h7jw9joHuSpVsy8GZVT;(>lZ(RAr!;)bwM~o__Gm~exd`K
zKEgh2)w?ReH&syI`~;Uo4`x4$&X+dYKI{e`dS~bQuS|p
zA`P_{QLV3r$*~lb=9vR^H0AxK9_+dmHX}Y}
zIV*#65%jRWem5Z($ji{!6ug$En4O*=^CiG=K
zp4S?+xE|6!cn$A%XutqNEgUqYY3fw&N(Z6=@W6*bxdp~i_yz5VcgSj=lf-6X1Nz75
z^DabwZ4*70$$8NsEy@U^W67tcy7^lNbu;|kOLcJ40A%J#pZe0d#n
zC{)}+p+?8*ftUlxJE*!%$`h~|KZSaCb=jpK3byAcuHk7wk@?YxkT1!|r({P*KY^`u
z!hw#`5$JJZGt@nkBK_nwWA31_Q9UGvv9r-{NU<&7HHMQsq=sn@O?e~fwl20tnSBG*
zO%4?Ew6`aX=I5lqmy&OkmtU}bH-+zvJ_CFy
z_nw#!8Rap5Wcex#5}Ldtqhr_Z$}@jPuYljTosS1+WG+TxZ>dGeT)?ZP3#3>sf#KOG
z0)s%{cEHBkS)019}-1A2kd*it>y65-C
zh7J9zogM74?PU)0c0YavY7g~%j%yiWEGDb+;Ew5g5Gq@MpVFFBNOpu0x)>Yn>G6uo
zKE%z1EhkG_N5$a8f6SRm(25iH#FMeaJ1^TBcBy<04ID47(1(D)q}g=_6#^V@yI?Y&@HUf
z`;ojGDdsvRCoTmasXndENqfWkOw=#cV-9*QClpI03)FWcx(m5(P1DW+2-{Hr-`5M{v##Zu-i-9Cvt;V|n)1pR^y
ztp3IXzHjYWqabuPqnCY9^^;adc!a%Z35VN~TzwAxq{NU&Kp35m?fw_^D{wzB}4FVXX5Zk@#={6jRh%wx|!eu@Xp;%x+{2;}!&J4X*_SvtkqE#KDIPPn@
z5BE$3uRlb>N<2A$g_cuRQM1T#5ra9u2x9pQuqF1l2#N{Q!jVJ<>HlLeVW|fN|#vqSnRr<0
zTVs=)7d`=EsJXkZLJgv~9JB&ay16xDG6v(J2eZy;U%a@EbAB-=C?PpA9@}?_Yfb&)
zBpsih5m1U9Px<+2$TBJ@7s9HW>W){i&XKLZ_{1Wzh-o!l5_S+f$j^RNYo85}uVhN#
zq}_mN-d=n{>fZD2Lx$Twd2)}X2ceasu91}n&BS+4U9=Y{aZCgV5#
z?z_Hq-knIbgIpnkGzJz-NW*=p?3l(}y3(aPCW=A({g9CpjJfYuZ%#Tz81Y)al?!S~
z9AS5#&nzm*NF?2tCR#|D-EjBWifFR=da6hW^PHTl&km-WI9*F4o>5J{LBSieVk`KO
z2(^9R(zC$@g|i3}`mK-qFZ33PD34jd_qOAFj29687wCUy>;(Hwo%Me&c=~)V$ua)V
zsaM(aThQ3{TiM~;gTckp)LFvN?%TlO-;$y+YX4i`SU0hbm<})t0zZ!t1=wY&j#N>q
zONEHIB^RW6D5N*cq6^+?T}$3m|L{Fe+L!rxJ=KRjlJS~|z-&CC{#CU8`}2|lo~)<|
zk?Wi1;Cr;`?02-C_3^gD{|Ryhw!8i?yx5i0v5?p)9wZxSkwn
z3C;pz25KR&7{|rc4H)V~y8%+6lX&KN&=^$Wqu+}}n{Y~K4XpI-#O?L=(2qncYNePX
zTsB6_3`7q&e0K67=Kg7G=j#?r!j0S^w7;0?CJbB3_C4_8X*Q%F1%cmB{g%XE&|IA7
z(#?AeG{l)s_orNJp!$Q~qGrj*YnuKlV`nVdg4vkTNS~w$4d^Oc3(dxi(W5jq0e>x}
z(GN1?u2%Sy;GA|B%Sk)ukr#v*UJU%(BE9X54!&KL9A^&rR%v
zIdYt0&D59ggM}CKWyxGS@
z>T#})2Bk8sZMGJYFJtc>D#k0+Rrrs)2DG;(u(DB_v-sVg=GFMlSCx<&RL;BH}d6AG3VqP!JpC0Gv6f8d|+7YRC@g|=N=C2
zo>^0CE0*RW?W))S(N)}NKA)aSwsR{1*rs$(cZIs?nF9)G*bSr%%SZo^YQ|TSz={jX
z4Z+(~v_>RH0(|IZ-_D_h@~p_i%k^XEi+CJVC~B
zsPir
zA0Jm2yIdo4`&I`hd%$Bv=Rq#-#bh{Mxb_{PN%trcf(#J3S1UKDfC1QjH2E;>wUf5=
ze8tY9QSYx0J;$JUR-0ar6fuiQTCQP#P|WEq;Ez|*@d?JHu-(?*tTpGHC+=Q%H>&I>
z*jC7%nJIy+HeoURWN%3X47UUusY2h7nckRxh8-)J61Zvn@j-uPA@99|y48pO)0XcW
zX^d&kW^p7xsvdX?2QZ8cEUbMZ7`&n{%Bo*xgFr4&fd#tHOEboQos~xm8q&W;fqrj}
z%KYnnE%R`=`+?lu-O+J9r@+$%YnqYq!SVs>xp;%Q8p^$wA~oynhnvIFp^)Z2CvcyC
zIN-_3EUHW}1^VQ0;Oj>q?mkPx$Wj-i7QoXgQ!HyRh6Gj8p~gH22k&nmEqUR^)9qni{%uNeV{&0-H60C
zibHZtbV=8=aX!xFvkO}T@lJ_4&ki$d+0ns3FXb+iP-VAVN`B7f-hO)jyh#4#_$XG%Txk6M<+q6D~
zi*UcgRBOoP$7P6RmaPZ2%MG}CMfs=>*~(b97V4+2qdwvwA@>U3QQAA$hiN9zi%Mq{
z*#fH57zUmi)GEefh7@`Uy7?@@=BL7cXbd{O9)*lJh*v!@
z-6}p9u0AreiGauxn7JBEa-2w&d=!*TLJ49`U@D7%2ppIh)ynMaAE2Q4dl@47cNu{9
z&3vT#pG$#%hrXzXsj=&Ss*0;W`Jo^mcy4*L8b^sSi;H{*`zW9xX2HAtQ*sO|x$c6UbRA(7*9=;D~(%wfo(Z6#s$S
zuFk`dr%DfVX5KC|Af8@AIr8@OAVj=6iX!~8D_P>p7>s!Hj+X0_t}Y*T4L5V->A@Zx
zcm1wN;TNq=h`5W&>z5cNA99U1lY6+!!u$ib|41VMcJk8`+kP{PEOUvc@2@fW(bh5pp6>C3T55@XlpsAd#vn~__3H;Dz2w=t9v&{v*)1m4)vX;4
zX4YAjM66?Z7kD@XX{e`f1t_ZvYyi*puSNhVPq%jeyBteaOHo7vOr8!qqp7wV;)%jtD5>}-a?xavZ;i|2P3~7c)vP2O#Fb`Y&Kce
zQNr7%fr4#S)OOV-1piOf7NgQvR{lcvZ*SNbLMq(olrdDC6su;ubp5un!&oT=jVTC3uTw7|r;@&y*s)a<{J
zkzG(PApmMCpMmuh6GkM_`AsBE@t~)EDcq1AJ~N@7bqyW_i!mtHGnVgBA`Dxi^P93i
z5R;}AQ60wy=Q2GUnSwz+W6C^}qn`S-lY7=J(3#BlOK%pCl=|RVWhC|IDj1E#+|M{TV0vE;vMZLy7KpD1$Yk
zi0!9%qy8>CyrcRK`juQ)I};r)5|_<<9x)32b3DT1M`>v^ld!yabX6@ihf`3ZVTgME
zfy(l-ocFuZ(L&OM4=1N#Mrrm_<>1DZpoWTO70U8+x4r3BpqH6z@(4~sqv!A9_L}@7
z7o~;|?~s-b?ud&Wx6==9{4uTcS|0-p@dKi0y#tPm2`A!^o3fZ8Uidxq|uz2vxf;wr
zM^%#9)h^R&T;}cxVI(XX7kKPEVb);AQO?cFT-ub=%lZPwxefymBk+!H!W(o(>I{jW
z$h;xuNUr#^0ivvSB-YEbUqe$GLSGrU$B3q28&oA55l)ChKOrwiTyI~e*uN;^V@g-Dm4d|MK!ol8hoaSB%iOQ#i_@`EYK_9ZEjFZ8Ho7P^er
z^2U6ZNQ{*hcEm?R-lK)pD_r(e=Jfe?5VkJ$2~Oq^7YjE^5(6a6Il--j@6dBHx2Ulq
z!%hz{d-S~i9Eo~WvQYDt7O7*G9CP#nrKE#DtIEbe_uxptcCSmYZMqT2F}7Kw0AWWC
zPjwo0IYZ6klc(h9uL|NY$;{SGm4R8Bt^^q{e#foMxfCSY^-c&IVPl|A_ru!ebwR#7
z3<4+nZL(mEsU}O9e`^XB4^*m)73hd04HH%6ok^!;4|JAENnEr~%s6W~8KWD)3MD*+
zRc46yo<}8|!|yW-+KulE86aB_T4pDgL$XyiRW(OOcnP4|2;v!m2fB7Hw-IkY#wYfF
zP4w;k-RInWr4fbz=X$J;z2E8pvAuy9kLJUSl8_USi;rW`kZGF?*Ur%%(t$^{Rg!=v
zg;h3@!Q$eTa7S0#APEDHLvK%RCn^o0u!xC1Y0Jg!Baht*a4mmKHy~88md{YmN#x)
zBOAp_i-z2h#V~*oO-9k(BizR^l#Vm%uSa^~3337d;f=AhVp?heJ)nlZGm`}D(U^2w
z#vC}o1g1h?RAV^90N|Jd@M00PoNUPyA?@HeX0P7`TKSA=*4s@R;Ulo4Ih{W^CD{c8
ze(ipN{CAXP(KHJ7UvpOc@9SUAS^wKo3h-}BDZu}-qjdNlVtp^Z{|CxKOEo?tB}-4;
zEXyDzGbXttJ3V$lLo-D?HYwZm7vvwdRo}P#KVF>F|M&eJ44n*ZO~0)#0e0Vy&j00I
z{%IrnUvKp70P?>~J^$^0Wo%>le>re2ZSvRfes@dC-*e=DD1-j%<$^~4^4>Id5w^Fr
z{RWL>EbUCcyC%1980kOYqZAcgdz5cS8c^7%vvrc@CSPIx;X=RuodO2dxk17|am?HJ@d~Mp_l8H?T;5l0&WGFoTKM{eP!L-a0O8?w
zgBPhY78tqf^+xv4#OK2I#0L-cSbEUWH2z+sDur85*!hjEhFfD!i0Eyr-RRLFEm5(n
z-RV6Zf_qMxN5S6#8fr9vDL01PxzHr7wgOn%0Htmvk9*gP^Um=n^+7GLs#GmU&a#U^4jr)BkIubQO7oUG!4CneO2Ixa`e~+Jp9m{l6apL8SOqA^
zvrfEUPwnHQ8;yBt!&(hAwASmL?Axitiqvx%KZRRP?tj2521wyxN3ZD9buj4e;2y6U
zw=TKh$4%tt(eh|y#*{flUJ5t4VyP*@3af`hyY^YU3LCE3Z|22iRK7M7E;1SZVHbXF
zKVw!L?2bS|kl7rN4(*4h2qxyLjWG0vR@`M~QFPsf^KParmCX;Gh4OX6Uy9#4e_%oK
zv1DRnfvd$pu(kUoV(MmAc09ckDiuqS$a%!AQ1Z>@DM#}-yAP$l`oV`BDYpkqpk(I|+qk!yoo$TwWr6dRzLy(c
zi+qbVlYGz0XUq@;Fm3r~_p%by)S&SVWS+wS0rC9bk^3K^_@6N5|2rtF)wI>WJ=;Fz
zn8$h<|Dr%kN|nciMwJAv;_%3XG9sDnO@i&pKVNEfziH_gxKy{l
zo`2m4rnUT(qenuq9B0<#Iy(RPxP8R)=5~9wBku=%&EBoZ82x1GlV<>R=hIqf0PK!V
zw?{z9e^B`bGyg2nH!^x}06oE%J_JLk)^QyHLipoCs2MWIqc>vaxsJj(=gg1ZSa=u{
zt}od#V;e7sA4S(V9^<^TZ#InyVBFT(V#$fvI7Q+pgsr_2X`N~8)IOZtX}e(Bn(;eF
zsNj#qOF_bHl$nw5!ULY{lNx@93Fj}%R@lewUuJ*X*1$K`DNAFpE
z7_lPE+!}uZ6c?+6NY1!QREg#iFy=Z!OEW}CXBd~wW|r_9%zkUPR0A3m+@Nk%4p>)F
zXVut7$aOZ6`w}%+WV$te6-IX7g2yms@aLygaTlIv3=Jl#Nr}nN
zp|vH-3L03#%-1-!mY`1z?+K1E>8K09G~JcxfS)%DZbteGQnQhaCGE2Y<{ut#(k-DL
zh&5PLpi9x3$HM82dS!M?(Z
zEsqW?dx-K_GMQu5K54pYJD=5+Rn&@bGjB?3$xgYl-|`FElp}?zP&RAd<522c$Rv6}
zcM%rYClU%JB#GuS>FNb{P2q*oHy}UcQ-pZ2UlT~zXt5*k-ZalE(`p7<`0n7i(r2k{
zb84&^LA7+aW1Gx5!wK!xTbw0slM?6-i32CaOcLC2B>ZRI16d{&-$QBEu1fKF0dVU>GTP05x2>Tmdy`75Qx!
z^IG;HB9V1-D5&&)zjJ&~G}VU1-x7EUlT3QgNT<&eIDUPYey$M|RD6%mVkoDe|;2`8Z+_{0&scCq>Mh3hj|E*|W3;y@{$qhu77D)QJ`
znD9C1AHCKSAHQqdWBiP`-cAjq7`V%~JFES1=i-s5h6xVT<50kiAH_dn0KQB4t*=ua
zz}F@mcKjhB;^7ka@WbSJFZRPeYI&JFkpJ-!B
z!ju#!6IzJ;D@$Qhvz9IGY5!%TD&(db3<*sCpZ?U#1^9RWQ
zs*O-)j!E85SMKtoZzE^8{w%E0R0b2lwwSJ%@E}Lou)iLmPQyO=eirG8h#o&E4~eew
z;h><=|4m0$`ANTOixHQOGpksXlF0yy17E&JksB4_(vKR5s$Ve+i;gco2}^RRJI+~R
zWJ82WGigLIUwP!uSELh3AAs9HmY-kz=_EL-w|9}noKE#(a;QBpEx9
z4BT-zY=6dJT>72Hkz=9J1E=}*MC;zzzUWb@x(Ho8cU_aRZ?fxse5_Ru2YOvcr?kg&pt@v;{ai7G--k$LQtoYj+Wjk+nnZty;XzANsrhoH#7=xVqfPIW(p
zX5{YF+5=k4_LBnhLUZxX*O?29olfPS?u*ybhM_y
z*XHUqM6OLB#lyTB`v<BZ&YRs$N)S@5Kn_b3;gjz6>fh@^j%y2-ya({>Hd@kv{CZZ2e)tva7gxLLp
z`HoGW);eRtov~Ro5tetU2y72~
zQh>D`@dt@s^csdfN-*U&o*)i3c4oBufCa0e|BwT2y%Y~=U7A^ny}tx
zHwA>Wm|!SCko~UN?hporyQHRUWl3djIc722EKbTIXQ6>>iC!x+cq^sUxVSj~u)dsY
zW8QgfZlE*2Os%=K;_vy3wx{0u!2%A)qEG-$R^`($%AOfnA^LpkB_}Dd7AymC)zSQr
z>C&N8V57)aeX8ap!|7vWaK6=-3~ko9meugAlBKYGOjc#36+KJwQKRNa_`W@7;a>ot
zdRiJkz?+QgC$b}-Owzuaw3zBVLEugOp6UeMHAKo2$m4w
zpw?i%Lft^UtuLI}wd4(-9Z^*lVoa}11~+0|Hs6zAgJ01`dEA&^>Ai=mr0nC%eBd_B
zzgv2G_~1c1wr*q@QqVW*Wi1zn=}KCtSwLjwT>ndXE_Xa22HHL_xCDhkM(
zhbw+j4uZM|r&3h=Z#YrxGo}GX`)AZyv@7#7+nd-D?BZV>thtc|3jt30j$9{aIw9)v
zDY)*fsSLPQTNa&>UL^RWH(vpNXT7HBv@9=*=(Q?3#H*crA2>KYx7Ab?-(HU~a275)MBp~`P)hhzSsbj|d`aBe(L*(;zif{iFJu**ZR
zkL-tPyh!#*r-JVQJq>5b0?cCy!uSKef+R=$s3iA7*k*_l&*e!$F
zYwGI;=S^0)b`mP8&Ry@{R(dPfykD&?H)na^ihVS7KXkxb36TbGm%X1!QSmbV9^#>A
z-%X>wljnTMU0#d;tpw?O1W@{X-k*>aOImeG
z#N^x?ehaaQd}ReQykp>i;92q@%$a!y1PNyPYDIvMm&
zyYVwn;+0({W@3h(r&i#FuCDE)AC(y&Vu>4?1@j0|CWnhHUx4|zL7cdaA32RSk?wl%
zMK^n42@i5AU>f70(huWfOwaucbaToxj%+)7hnG^CjH|O`A}+GHZyQ-X57(WuiyRXV
zPf>0N3GJ<2Myg!sE4XJY?Z7@K3ZgHy8f7CS5ton0Eq)Cp`iLROAglnsiEXpnI+S8;
zZn>g2VqLxi^p8#F#Laf3<00AcT}Qh&kQnd^28u!9l1m^`lfh9+5$VNv=?(~Gl2wAl
zx(w$Z2!_oESg_3Kk0hUsBJ<;OTPyL(?z6xj6LG5|Ic4II*P+_=ac7KRJZ`(k2R$L#
zv|oWM@116K7r3^EL*j2ktjEEOY9c!IhnyqD&oy7+645^+@z5Y|;0+dyR2X6^%7GD*
zXrbPqTO}O={
z4cGaI#DdpP;5u?lcNb($V`l>H7k7otl_jQFu1hh>=(?CTPN#IPO%O_rlVX}_Nq;L<
z@YNiY>-W~&E@=EC5%o_z<^3YEw)i_c|NXxHF{=7U7Ev&C`c^0Z4-LGKXu*Hkk&Av=
zG&RAv{cR7o4${k~f{F~J48Ks&o(D@j-PQ2`LL@I~b=ifx3q!p6`d>~Y!<-^mMk3)e
zhi1;(YLU5KH}zzZNhl^`0HT(r`5FfmDEzxa
zk&J7WQ|!v~TyDWdXQ)!AN_Y%xM*!jv^`s)A`|F%;eGg27KYsrCE2H}7*r)zvum6B{
z$k5Har9pv!dcG%f|3hE(#hFH+12RZPycVi?2y`-9I7JHryMn3
z9Y8?==_(vOAJ7PnT<0&85`_jMD0#ipta~Q3M!q5H1D@Nj-YXI$W%OQplM(GWZ5Lpq
z-He6ul|3<;ZQsqs!{Y7x`FV@pOQc4|N;)qgtRe(Uf?|YqZv^$k8On7DJ5>f2%M=TV
zw~x}9o=mh$JVF{v4H5Su1pq66+mhTG6?F>Do}x{V(TgFwuLfvNP^ijkrp5#s4UT!~
zEU7pr8aA)2z1zb|X9IpmJykQcqI#(rS|A4&=TtWu@g^;JCN`2kL}%+K!KlgC
z>P)v+uCeI{1KZpewf>C=?N7%1e10Y3pQCZST1GT5fVyB1`q)JqCLXM
zSN0qlreH1=%Zg-5`(dlfSHI&2?^SQdbEE&W4#%Eve2-EnX>NfboD<2l((>>34lE%)
zS6PWibEvuBG7)KQo_`?KHSPk+2P;`}#xEs}0!;yPaTrR#j(2H|#-CbVnTt_?9aG`o
z(4IPU*n>`cw2V~HM#O`Z^bv|cK|K};buJ|#{reT8R)f+P2<3$0YGh!lqx3&a_wi2Q
zN^U|U$w4NP!Z>5|O)>$GjS5wqL3T8jTn%Vfg3_KnyUM{M`?bm)9oqZP&1w1)o=@+(5eUF@=P~
zk2B5AKxQ96n-6lyjh&xD!gHCzD$}OOdKQQk7LXS-fk2uy#h{ktqDo{o&>O!6%B|)`
zg?|JgcH{P*5SoE3(}QyGc=@hqlB5w;bnmF#pL4iH`TSuft$dE5j^qP2S)?)@pjRQZ
zBfo6g>c!|bN-Y|(Wah2o61Vd|OtXS?1`Fu&mFZ^yzUd4lgu7V|MRdGj3e#V`=mnk-
zZ@LHn?@dDi=I^}R?}mZwduik!hC%=Hcl56u{Wrk1|1SxlgnzG&e7Vzh*wNM(6Y!~m
z`cm8Ygc1$@z9u9=m5vs1(XXvH;q16fxyX4&e5dP-{!Kd555FD6G^sOXHyaCLka|8j
zKKW^E>}>URx736WWNf?U6Dbd37Va3wQkiE;5F!quSnVKnmaIRl)b5rM_ICu4txs+w
zj}nsd0I_VG^<%DMR8Zf}vh}kk;heOQTbl
ziEoE;9@FBIfR7OO9y4Pwyz02OeA$n)mESpj
zdd=xPwA`nO06uGGsXr4n>Cjot7m^~2X~V4yH&-
zv2llS{|und45}Pm1-_W@)a-`vFBpD~>eVP(-rVHIIA|HD@%7>k8JPI-O*<7X{L*Ik
zh^K`aEN!BteiRaY82FVo6<^8_22=aDIa8P&2A3V<(BQ;;x8Zs-1WuLRWjQvKv1rd2
zt%+fZ!L|ISVKT?$3iCK#7whp|1ivz1rV*R>yc5dS3kIKy_0`)n*%bfNyw%e7Uo}Mnnf>QwDgeH$X5eg_)!pI4EJjh6?kkG2oc6Af0py
z(txE}$ukD|Zn=c+R`Oq;m~CSY{ebu9?!is}01sOK_mB?{lSY33E=!KkKtMeI*FO2b
z%95awv9;Z|UDp3xm+aP*5I!R-_M2;GxeCRx3ATS0iF<_Do2Mi)Hk2
zjBF35VB>(oamIYjunu?g0O-?LuOvtfs5F(iiIicbu$HMPPF%F>pE@hIRjzT)>aa=m
zwe;H9&+2|S!m74!E3xfO{l3E_ab`Q^tZ4yH9=~o2DUEtEMDqG=&D*8!>?2uao%w`&)THr
z^>=L3HJquY>6)>dW4pCWbzrIB+>rdr{s}}cL_?#!sOPztRwPm1B=!jP7lQG|Iy6rP
zVqZDNA;xaUx&xUt?Ox|;`9?oz`C0#}mc<1Urs#vTW4wd{1_r`eX=BeSV
z_9WV*9mz>PH6b^z{VYQJ1nSTSqOFHE9u>cY)m`Q>=w1NzUShxcHsAxasnF2BG;NQ;
zqL1tjLjImz_`q=|bAOr_i5_NEijqYZ^;d5y3ZFj6kCYakJh**N_wbfH;ICXq?-p#r
z{{ljNDPSytOaG#7=yPmA&5gyYI%^7pLnMOw-RK}#*dk=@usL;|4US?{@K%7esmc&n
z5$D*+l&C9)Bo@$d;Nwipd!68&+NnOj^<~vRcKLX>e03E|;to;$ndgR;9~&S-ly5gf
z{rzj+j-g$;O|u?;wwxrEpD=8iFzUHQfl{B>bLHqH(9P
zI59SS2PEBE;{zJUlcmf(T4DrcO?XRWR}?fekN<($1&AJTRDyW+D*2(Gyi?Qx-i}gy
z&BpIO!NeVdLReO!YgdUfnT}7?5Z#~t5rMWqG+$N2n%5o#Np6ccNly}#IZQsW4?|NV
zR9hrcyP(l#A+U4XcQvT;4{#i)dU>HK>aS!k1<3s2LyAhm2(!Nu%vRC9T`_yn9D+r}
z1i&U~IcQ?4xhZYyH6WL-f%}qIhZkc&}n2N0PM|
z6|XA9d-y;!`D{p;xu*gv7a|zaZ*MiQ)}zPzW4GB0mr)}N-DmB&hl1&x`2@sxN572_
zS)RdJyR%<7kW0v3Q_|57JKy&9tUdbqz}|hwn84}U*0r^jt6Ssrp+#1y=JBcZ+F`f(N?O0XL1OFGN`1-r?S<#t4*C9|y~e)!UYZ
zRQ3M8m%~M)VriIvn~XzoP;5qeu(ZI>Y#r
zAd)J)G9)*BeE%gmm&M@Olg3DI_zokjh9NvdGbT
z+u4(Y&uC6tBBefIg~e=J#8i1Zxr>RT)#rGaB2C71usdsT=}mm`<#WY^6V{L*J6v&l
z1^Tkr6-+^PA)yC;s1O^3Q!)Reb=fxs)P~I*?i&j{Vbb(Juc?La;cA5(H7#FKIj0Or
zgV0BO{DUs`I9HgQ{-!g@5P^Vr|C4}~w6b=#`Zx0XcVSd?(04HUHwK(gJNafgQNB9Z
zCi3TgNXAeJ+x|X|b@27$RxuYYuNSUBqo#uyiH6H(b~K*#!@g__4i%HP5wb<+Q7GSb
zTZjJw96htUaGZ89$K_iBo4xEOJ#DT#KRu9ozu!GH0cqR>hP$nk=KXM%Y!(%vWQ#}s
zy=O#BZ>xjUejMH^F39Bf0}>D}yiAh^toa-ts#gt6Mk9h1D<9_mGMBhLT0Ce2O3d_U
znaTkBaxd-8XgwSp5)x-pqX5=+{cSuk6kyl@k|5DQ!5zLUVV%1X9vjY0gerbuG6nwZu5KDMdq(&UMLZ
zy?jW#F6joUtVyz`Y?-#Yc0=i*htOFwQ3`hk$8oq35D}0m$FAOp#UFTV3|U3F>@N?d
zeXLZCZjRC($%?dz(41e~)CN10qjh^1CdAcY(<=GMGk@`b1ptA&L*{L@_M{%Vd5b*x#b1(qh=7((<_l%ZUaHtmgq}
zjchBdiis{Afxf@3CjPR09E*2#X(`W#-n`~6PcbaL_(^3tfDLk?Nb6CkW9v!v#&pWJ3iV-9hz
zngp#Q`w`r~2wt&cQ9#S7z0CA^>Mzm7fpt72g<0y-KT{G~l-@L#edmjZQ}7{*$mLgSdJfS$Ge{hrD=mr;GD)uYq8}xS
zT>(w_;}894Kb}(P5~FOpFIEjadhmxD(PsZbKwa-qxVa7Oc7~ebPKMeN(pCRzq8s@l
z`|l^*X1eK1+Spz--WkSW_nK`Cs@JmkY4+p=U91nJoy{tSH;TzuIyS)Q_(S@;Iakua
zpuDo5W54Mo;jY@Ly1dY)j|+M%$FJ0`C=FW#%UvOd&?p}0QqL20Xt!#pr8ujy6CA-2
zFz6Ex5H1i)c9&HUNwG{8K%FRK7HL$RJwvGakleLLo}tsb>t_nBCIuABNo$G--_j!gV&t8L^4N6wC|aLC)l&w04CD6Vc#h^(YH@Zs4nwUGkhc_-yt{dK
zMZ<%$swLmUl8`E~RLihGt@J5v;r;vT&*Q!Cx
zZ55-zpb;W7_Q{tf$mQvF61(K>kwTq0x{#Din||)B{+6O#ArLi)kiHWVC4`fOT&B(h
zw&YV`J1|^FLx~9Q%r-SFhYl4PywI7sF2Q$>4o50~dfp5nn}XHv-_DM?RGs#+4gM;%
znU>k=81G~f6u%^Z{bcX&sUv*h|L+|mNq=W43y@{~C
zpL-TW3hYPs0^*OqS#KQwA^CGG_A-6#`_{1LBCD&*3nY0UHWJj1D|VP%oQlFxLllaA
zVI@2^)HZ%E*=RbQcFOKIP7?+|_xVK+2oG(t_EGl2y;Ovox
zZb^qVpe!4^reKvpIBFzx;Ji=PmrV>uu-Hb>`s?k?YZQ?>av45>i(w0V!|n?AP|v5H
zm`e&Tgli#lqGEt?=(?~fy<(%#nDU`O@}Vjib6^rfE2xn;qgU6{u36j_+Km%v*2RLnGpsvS+THbZ>p(B
zgb{QvqE?~50pkLP^0(`~K&
zjT=2Pt2nSnwmnDFi2>;*C|OM1dY|CAZ5R|%SAuU|5KkjRM!LW_)LC*A
zf{f>XaD+;rl6Y>Umr>M8y>lF+=nSxZX_-Z7lkTXyuZ(O6?UHw^q;
z&$Zsm4U~}KLWz8>_{p*WQ!OgxT1JC&B&>|+LE3Z2mFNTUho<0u?@r^d=2
z-av!n8r#5M|F%l;=D=S1mGLjgFsiYAOODAR}#e^a8
zfVt$k=_o}kt3PTz?EpLkt54dY}kyd$rU
zVqc9SN>0c
z753j-gdN~UiW*FUDMOpYEkVzP)}{Ds*3_)ZBi)4v26MQr140|QRqhFoP=a|;C{#KS
zD^9b-9HM11W+cb1Y)HAuk<^GUUo(ut!5kILBzAe)Vaxwu4Up!7Ql*#DDu
z>EB84&xSrh>0jT!*X81jJQq$CRHqNj29!V3FN9DCx)~bvZbLwSlo3l^zPb1sqBnp)
zfZpo|amY^H*I==3#8D%x3>zh#_SBf?r2QrD(Y@El!wa;Ja6G9Y1947P*DC|{9~nO&
z*vDnnU!8(cV%HevsraF%Y%2{Z>CL0?64eu9r^t#WjW4~3uw8d}WHzsV%oq-T)Y
z0-c!FWX5j1{1##?{aTeCW2b$PEnwe;t`VPCm@sQ`+$$L2=3kBR%2XU1{_|__XJ$xt
zibjY2QlDVs)RgHH*kl&+jn*JqquF)k_Ypibo00lcc<2RYqsi-G%}k0r(N97H7JEn7@E3ZTH0JK>d8)E~A-D
z!B&z9zJw0Bi^fgQZI%LirYaBKnWBXgc`An*qvO^*$xymqKOp(+3}IsnVhu?YnN7qz
zNJxDN-JWd7-vIiv2M9ih>x3gNVY%DzzY~dCnA}76IRl!`VM=6=TYQ=o&uuE8kHqZT
zoUNod0v+s9D)7aLJ|hVqL0li1hg)%&MAciI(4YJ=%D4H$fGQ&Lu-?@>>@pEgC;ERrL=
zI^cS&3q8fvEGTJZgZwL5j&jp%j9U^Of6pR{wA^u=tVt#yCQepXNIbynGnuWbsC_EE
zRyMFq{5DK692-*kyGy~An>AdVR9u___fzmmJ4;^s0yAGgO^h{YFmqJ%ZJ_^0BgCET
zE6(B*SzeZ4pAxear^B-YW<%BK->X&Cr`g9_;qH~pCle#
zdY|UB5cS<}DFRMO;&czbmV(?vzikf)Ks`d$LL801@HTP5@r><}$xp}+Ip`u_AZ~!K
zT}{+R9Wkj}DtC=4QIqJok5(~0Ll&_6PPVQ`hZ+2iX1H{YjI8axG_Bw#QJy`6T>1Nn
z%u^l`>XJ{^vX`L0
z1%w-ie!dE|!SP<>#c%ma9)8K4gm=!inHn2U+GR+~
zqZVoa!#aS0SP(|**WfQSe?cA=1|Jwk`UDsny%_y{@AV??N>xWekf>_IZLUEK3{Ksi
zWWW$if&Go~@Oz)`#=6t_bNtD$d9FMBN#&97+XKa+K2C@I9xWgTE{?Xnhc9_KKPcujj@NprM@e|KtV_SR+
zSpeJ!1FGJ=Te6={;;+;a46-*DW*FjTnBfeuzI_=I1yk8M(}IwEIGWV0Y~wia;}^dg
z{BK#G7^J`SE10z4(_Me=kF&4ld*}wpNs91%2Ute>Om`byv9qgK4VfwPj$`axsiZ)wxS4k4KTLb-d~!7I@^Jq`>?TrixHk|9
zqC