Skip to content

Commit

Permalink
Allow for docker timestamps with timezone offsets (#4073)
Browse files Browse the repository at this point in the history
Docker prefers to use UTC time when making images/containers,
but it's happy to parse non-UTC times too. It will then pass those on in its
output.

DateTimeFormatter.ISO_OFFSET_DATE_TIME accepts a superset of what
DateTimeFormatter.ISO_INSTANT accepts, so replace use of
ISO_INSTANT with ISO_OFFSET_DATE_TIME.

Co-authored-by: Richard North <[email protected]>
  • Loading branch information
candrews and rnorth authored Jul 16, 2021
1 parent 014500b commit e89dce1
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static boolean isContainerRunning(InspectContainerResponse.ContainerState
if (minimumRunningDuration == null) {
return true;
}
Instant startedAt = DateTimeFormatter.ISO_INSTANT.parse(
Instant startedAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(
state.getStartedAt(), Instant::from);

if (startedAt.isBefore(now.minus(minimumRunningDuration))) {
Expand Down Expand Up @@ -75,7 +75,7 @@ public static boolean isDockerTimestampNonEmpty(String dockerTimestamp) {
return dockerTimestamp != null
&& !dockerTimestamp.isEmpty()
&& !dockerTimestamp.equals(DOCKER_TIMESTAMP_ZERO)
&& DateTimeFormatter.ISO_INSTANT.parse(dockerTimestamp, Instant::from).getEpochSecond() >= 0L;
&& DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(dockerTimestamp, Instant::from).getEpochSecond() >= 0L;
}

public static boolean isContainerExitCodeSuccess(InspectContainerResponse.ContainerState state) {
Expand Down
53 changes: 35 additions & 18 deletions core/src/test/java/org/testcontainers/utility/DockerStatusTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import com.github.dockerjava.api.command.InspectContainerResponse;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.mockito.Mockito;

import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

import static org.junit.Assert.assertFalse;
Expand All @@ -15,33 +18,47 @@
/**
*
*/
@RunWith(Parameterized.class)
public class DockerStatusTest {
private final DateTimeFormatter dateTimeFormatter;

private static Instant now = Instant.now();
private static final Instant now = Instant.now();

private static InspectContainerResponse.ContainerState running =
buildState(true, false, buildTimestamp(now.minusMillis(30)), DockerStatus.DOCKER_TIMESTAMP_ZERO);
private final InspectContainerResponse.ContainerState running;

private static InspectContainerResponse.ContainerState runningVariant =
buildState(true, false, buildTimestamp(now.minusMillis(30)), "");
private final InspectContainerResponse.ContainerState runningVariant;

private static InspectContainerResponse.ContainerState shortRunning =
buildState(true, false, buildTimestamp(now.minusMillis(10)), DockerStatus.DOCKER_TIMESTAMP_ZERO);
private final InspectContainerResponse.ContainerState shortRunning;

private static InspectContainerResponse.ContainerState created =
buildState(false, false, DockerStatus.DOCKER_TIMESTAMP_ZERO, DockerStatus.DOCKER_TIMESTAMP_ZERO);
private final InspectContainerResponse.ContainerState created;

// a container in the "created" state is not running, and has both startedAt and finishedAt empty.
private static InspectContainerResponse.ContainerState createdVariant =
buildState(false, false, null, null);
private final InspectContainerResponse.ContainerState createdVariant;

private static InspectContainerResponse.ContainerState exited =
buildState(false, false, buildTimestamp(now.minusMillis(100)), buildTimestamp(now.minusMillis(90)));
private final InspectContainerResponse.ContainerState exited;

private static InspectContainerResponse.ContainerState paused =
buildState(false, true, buildTimestamp(now.minusMillis(100)), DockerStatus.DOCKER_TIMESTAMP_ZERO);
private final InspectContainerResponse.ContainerState paused;

private static Duration minimumDuration = Duration.ofMillis(20);
private static final Duration minimumDuration = Duration.ofMillis(20);

public DockerStatusTest(DateTimeFormatter dateTimeFormatter) {
this.dateTimeFormatter = dateTimeFormatter;
running = buildState(true, false, buildTimestamp(now.minusMillis(30)), DockerStatus.DOCKER_TIMESTAMP_ZERO);
runningVariant = buildState(true, false, buildTimestamp(now.minusMillis(30)), "");
shortRunning = buildState(true, false, buildTimestamp(now.minusMillis(10)), DockerStatus.DOCKER_TIMESTAMP_ZERO);
created = buildState(false, false, DockerStatus.DOCKER_TIMESTAMP_ZERO, DockerStatus.DOCKER_TIMESTAMP_ZERO);
createdVariant = buildState(false, false, null, null);
exited = buildState(false, false, buildTimestamp(now.minusMillis(100)), buildTimestamp(now.minusMillis(90)));
paused = buildState(false, true, buildTimestamp(now.minusMillis(100)), DockerStatus.DOCKER_TIMESTAMP_ZERO);
}

@Parameterized.Parameters
public static Object[][] parameters() {
return new Object[][] {
{ DateTimeFormatter.ISO_INSTANT},
{ DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.of("America/New_York")) }
};
}

@Test
public void testRunning() throws Exception {
Expand All @@ -65,8 +82,8 @@ public void testStopped() throws Exception {
assertFalse(DockerStatus.isContainerStopped(paused));
}

private static String buildTimestamp(Instant instant) {
return DateTimeFormatter.ISO_INSTANT.format(instant);
private String buildTimestamp(Instant instant) {
return dateTimeFormatter.format(instant);
}

// ContainerState is a non-static inner class, with private member variables, in a different package.
Expand Down

0 comments on commit e89dce1

Please sign in to comment.