1717import lombok .SneakyThrows ;
1818import lombok .Synchronized ;
1919import lombok .extern .slf4j .Slf4j ;
20- import org .hamcrest .BaseMatcher ;
21- import org .hamcrest .Description ;
22- import org .rnorth .visibleassertions .VisibleAssertions ;
2320import org .testcontainers .dockerclient .DockerClientProviderStrategy ;
2421import org .testcontainers .dockerclient .DockerMachineClientProviderStrategy ;
2522import org .testcontainers .images .TimeLimitedLoggedPullImageResultCallback ;
@@ -68,12 +65,16 @@ public class DockerClientFactory {
6865 @ VisibleForTesting
6966 DockerClient dockerClient ;
7067
68+ @ VisibleForTesting
69+ RuntimeException cachedChecksFailure ;
70+
7171 private String activeApiVersion ;
7272 private String activeExecutionDriver ;
7373
7474 @ Getter (lazy = true )
7575 private final boolean fileMountingSupported = checkMountableFile ();
7676
77+
7778 static {
7879 System .setProperty ("org.testcontainers.shaded.io.netty.packagePrefix" , "org.testcontainers.shaded." );
7980 }
@@ -124,6 +125,12 @@ public DockerClient client() {
124125 return dockerClient ;
125126 }
126127
128+ // fail-fast if checks have failed previously
129+ if (cachedChecksFailure != null ) {
130+ log .debug ("There is a cached checks failure - throwing" , cachedChecksFailure );
131+ throw cachedChecksFailure ;
132+ }
133+
127134 final DockerClientProviderStrategy strategy = getOrInitializeStrategy ();
128135
129136 String hostIpAddress = strategy .getDockerHostIpAddress ();
@@ -140,51 +147,56 @@ public DockerClient client() {
140147 " Operating System: " + dockerInfo .getOperatingSystem () + "\n " +
141148 " Total Memory: " + dockerInfo .getMemTotal () / (1024 * 1024 ) + " MB" );
142149
143- String ryukContainerId = null ;
150+ final String ryukContainerId ;
151+
144152 boolean useRyuk = !Boolean .parseBoolean (System .getenv ("TESTCONTAINERS_RYUK_DISABLED" ));
145153 if (useRyuk ) {
154+ log .debug ("Ryuk is enabled" );
146155 ryukContainerId = ResourceReaper .start (hostIpAddress , client );
147156 log .info ("Ryuk started - will monitor and terminate Testcontainers containers on JVM exit" );
157+ } else {
158+ log .debug ("Ryuk is disabled" );
159+ ryukContainerId = null ;
148160 }
149161
150162 boolean checksEnabled = !TestcontainersConfiguration .getInstance ().isDisableChecks ();
151163 if (checksEnabled ) {
152- VisibleAssertions .info ("Checking the system..." );
153- checkDockerVersion (version .getVersion ());
154- if (ryukContainerId != null ) {
155- checkDiskSpace (client , ryukContainerId );
156- } else {
157- runInsideDocker (
158- client ,
159- createContainerCmd -> {
160- createContainerCmd .withName ("testcontainers-checks-" + SESSION_ID );
161- createContainerCmd .getHostConfig ().withAutoRemove (true );
162- createContainerCmd .withCmd ("tail" , "-f" , "/dev/null" );
163- },
164- (__ , containerId ) -> {
165- checkDiskSpace (client , containerId );
166- return "" ;
167- }
168- );
164+ log .debug ("Checks are enabled" );
165+
166+ try {
167+ log .info ("Checking the system..." );
168+ checkDockerVersion (version .getVersion ());
169+ if (ryukContainerId != null ) {
170+ checkDiskSpace (client , ryukContainerId );
171+ } else {
172+ runInsideDocker (
173+ client ,
174+ createContainerCmd -> {
175+ createContainerCmd .withName ("testcontainers-checks-" + SESSION_ID );
176+ createContainerCmd .getHostConfig ().withAutoRemove (true );
177+ createContainerCmd .withCmd ("tail" , "-f" , "/dev/null" );
178+ },
179+ (__ , containerId ) -> {
180+ checkDiskSpace (client , containerId );
181+ return "" ;
182+ }
183+ );
184+ }
185+ } catch (RuntimeException e ) {
186+ cachedChecksFailure = e ;
187+ throw e ;
169188 }
189+ } else {
190+ log .debug ("Checks are disabled" );
170191 }
171192
172193 dockerClient = client ;
173194 return dockerClient ;
174195 }
175196
176197 private void checkDockerVersion (String dockerVersion ) {
177- VisibleAssertions .assertThat ("Docker version" , dockerVersion , new BaseMatcher <String >() {
178- @ Override
179- public boolean matches (Object o ) {
180- return new ComparableVersion (o .toString ()).compareTo (new ComparableVersion ("1.6.0" )) >= 0 ;
181- }
182-
183- @ Override
184- public void describeTo (Description description ) {
185- description .appendText ("should be at least 1.6.0" );
186- }
187- });
198+ boolean versionIsSufficient = new ComparableVersion (dockerVersion ).compareTo (new ComparableVersion ("1.6.0" )) >= 0 ;
199+ check ("Docker server version should be at least 1.6.0" , versionIsSufficient );
188200 }
189201
190202 private void checkDiskSpace (DockerClient dockerClient , String id ) {
@@ -201,12 +213,21 @@ private void checkDiskSpace(DockerClient dockerClient, String id) {
201213
202214 DiskSpaceUsage df = parseAvailableDiskSpace (outputStream .toString ());
203215
204- VisibleAssertions . assertTrue (
216+ check (
205217 "Docker environment should have more than 2GB free disk space" ,
206218 df .availableMB .map (it -> it >= 2048 ).orElse (true )
207219 );
208220 }
209221
222+ private void check (String message , boolean isSuccessful ) {
223+ if (isSuccessful ) {
224+ log .info ("✔︎ {}" , message );
225+ } else {
226+ log .error ("❌ {}" , message );
227+ throw new IllegalStateException ("Check failed: " + message );
228+ }
229+ }
230+
210231 private boolean checkMountableFile () {
211232 DockerClient dockerClient = client ();
212233
@@ -267,8 +288,8 @@ private <T> T runInsideDocker(DockerClient client, Consumer<CreateContainerCmd>
267288 } finally {
268289 try {
269290 client .removeContainerCmd (id ).withRemoveVolumes (true ).withForce (true ).exec ();
270- } catch (NotFoundException | InternalServerErrorException ignored ) {
271- log .debug ("" , ignored );
291+ } catch (NotFoundException | InternalServerErrorException e ) {
292+ log .debug ("Swallowed exception while removing container " , e );
272293 }
273294 }
274295 }
@@ -286,7 +307,7 @@ DiskSpaceUsage parseAvailableDiskSpace(String dfOutput) {
286307 for (String line : lines ) {
287308 String [] fields = line .split ("\\ s+" );
288309 if (fields .length > 5 && fields [5 ].equals ("/" )) {
289- long availableKB = Long .valueOf (fields [3 ]);
310+ long availableKB = Long .parseLong (fields [3 ]);
290311 df .availableMB = Optional .of (availableKB / 1024L );
291312 df .usedPercent = Optional .of (Integer .valueOf (fields [4 ].replace ("%" , "" )));
292313 break ;
@@ -318,10 +339,4 @@ public String getActiveExecutionDriver() {
318339 public boolean isUsing (Class <? extends DockerClientProviderStrategy > providerStrategyClass ) {
319340 return strategy != null && providerStrategyClass .isAssignableFrom (this .strategy .getClass ());
320341 }
321-
322- private static class NotEnoughDiskSpaceException extends RuntimeException {
323- NotEnoughDiskSpaceException (String message ) {
324- super (message );
325- }
326- }
327342}
0 commit comments