Skip to content

Commit 4e66327

Browse files
authored
Merge pull request #48366 from holly-cummins/refactor-and-extend-redis-dev-service-tests
Refactor and extend Redis Dev Service tests
2 parents 32f756b + d85e9bb commit 4e66327

File tree

2 files changed

+223
-58
lines changed

2 files changed

+223
-58
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package io.quarkus.redis.devservices.continuoustesting.it;
2+
3+
import static io.restassured.RestAssured.when;
4+
import static org.hamcrest.Matchers.is;
5+
import static org.junit.jupiter.api.Assertions.assertEquals;
6+
import static org.junit.jupiter.api.Assertions.assertFalse;
7+
import static org.junit.jupiter.api.Assertions.assertTrue;
8+
9+
import java.util.Arrays;
10+
import java.util.Collection;
11+
import java.util.List;
12+
import java.util.stream.Collectors;
13+
14+
import org.jboss.shrinkwrap.api.ShrinkWrap;
15+
import org.jboss.shrinkwrap.api.asset.StringAsset;
16+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
17+
import org.junit.jupiter.api.Test;
18+
import org.junit.jupiter.api.extension.RegisterExtension;
19+
import org.testcontainers.DockerClientFactory;
20+
21+
import com.github.dockerjava.api.model.Container;
22+
23+
import io.quarkus.redis.devservices.it.PlainQuarkusTest;
24+
import io.quarkus.test.QuarkusDevModeTest;
25+
import io.quarkus.test.devservices.redis.BundledResource;
26+
27+
public class DevServicesDevModeTest {
28+
29+
@RegisterExtension
30+
public static QuarkusDevModeTest test = new QuarkusDevModeTest()
31+
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
32+
.addClass(BundledResource.class)
33+
.addAsResource(new StringAsset(""), "application.properties"))
34+
.setTestArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClass(PlainQuarkusTest.class));
35+
36+
@Test
37+
public void testDevModeServiceUpdatesContainersOnConfigChange() {
38+
// Interacting with the app will force a refresh
39+
// Note that driving continuous testing concurrently can sometimes cause 500s caused by containers not yet being available on slow machines
40+
ping();
41+
List<Container> started = getRedisContainers();
42+
43+
assertFalse(started.isEmpty());
44+
Container container = started.get(0);
45+
assertTrue(Arrays.stream(container.getPorts()).noneMatch(p -> p.getPublicPort() == 6377),
46+
"Expected random port, but got: " + Arrays.toString(container.getPorts()));
47+
48+
int newPort = 6388;
49+
test.modifyResourceFile("application.properties", s -> s + "quarkus.redis.devservices.port=" + newPort);
50+
51+
// Force another refresh
52+
ping();
53+
54+
List<Container> newContainers = getRedisContainersExcludingExisting(started);
55+
56+
// We expect 1 new containers, since test was not refreshed.
57+
// On some VMs that's what we get, but on others, a test-mode augmentation happens, and then we get two containers
58+
assertEquals(1, newContainers.size(),
59+
"There were " + newContainers.size() + " new containers, and should have been 1 or 2. New containers: "
60+
+ prettyPrintContainerList(newContainers)
61+
+ "\n Old containers: " + prettyPrintContainerList(started) + "\n All containers: "
62+
+ prettyPrintContainerList(getAllContainers())); // this can be wrong
63+
// We need to inspect the dev-mode container; we don't have a non-brittle way of distinguishing them, so just look in them all
64+
boolean hasRightPort = newContainers.stream()
65+
.anyMatch(newContainer -> hasPublicPort(newContainer, newPort));
66+
assertTrue(hasRightPort,
67+
"Expected port " + newPort + ", but got: "
68+
+ newContainers.stream().map(c -> Arrays.toString(c.getPorts())).collect(Collectors.joining(", ")));
69+
}
70+
71+
@Test
72+
public void testDevModeServiceDoesNotRestartContainersOnCodeChange() {
73+
ping();
74+
List<Container> started = getRedisContainers();
75+
76+
assertFalse(started.isEmpty());
77+
Container container = started.get(0);
78+
assertTrue(Arrays.stream(container.getPorts()).noneMatch(p -> p.getPublicPort() == 6377),
79+
"Expected random port 6377, but got: " + Arrays.toString(container.getPorts()));
80+
81+
// Make a change that shouldn't affect dev services
82+
test.modifySourceFile(BundledResource.class, s -> s.replaceAll("OK", "poink"));
83+
84+
ping();
85+
86+
List<Container> newContainers = getRedisContainersExcludingExisting(started);
87+
88+
// No new containers should have spawned
89+
assertEquals(0, newContainers.size(),
90+
"New containers: " + newContainers + "\n Old containers: " + started + "\n All containers: "
91+
+ getAllContainers()); // this can be wrong
92+
}
93+
94+
@Test
95+
public void testDevModeKeepsSameInstanceWhenRefreshedOnSecondChange() {
96+
// Step 1: Ensure we have a dev service running
97+
System.out.println("Step 1: Ensure we have a dev service running");
98+
ping();
99+
List<Container> step1Containers = getRedisContainers();
100+
assertFalse(step1Containers.isEmpty());
101+
Container container = step1Containers.get(0);
102+
assertFalse(hasPublicPort(container, 6377));
103+
104+
// Step 2: Make a change that should affect dev services
105+
System.out.println("Step 2: Make a change that should affect dev services");
106+
int someFixedPort = 36377;
107+
// Make a change that SHOULD affect dev services
108+
test.modifyResourceFile("application.properties",
109+
s -> s
110+
+ "quarkus.redis.devservices.port=" + someFixedPort + "\n");
111+
112+
ping();
113+
114+
List<Container> step2Containers = getRedisContainersExcludingExisting(step1Containers);
115+
116+
// New containers should have spawned
117+
assertEquals(1, step2Containers.size(),
118+
"New containers: " + step2Containers + "\n Old containers: " + step1Containers + "\n All containers: "
119+
+ getAllContainers());
120+
121+
assertTrue(hasPublicPort(step2Containers.get(0), someFixedPort));
122+
123+
// Step 3: Now change back to a random port, which should cause a new container to spawn
124+
System.out.println("Step 3: Now change back to a random port, which should cause a new container to spawn");
125+
test.modifyResourceFile("application.properties",
126+
s -> s.replaceAll("quarkus.redis.devservices.port=" + someFixedPort, ""));
127+
128+
ping();
129+
130+
List<Container> step3Containers = getRedisContainersExcludingExisting(step2Containers);
131+
132+
// New containers should have spawned
133+
assertEquals(1, step3Containers.size(),
134+
"New containers: " + step3Containers + "\n Old containers: " + step2Containers + "\n All containers: "
135+
+ getAllContainers());
136+
137+
// Step 4: Now make a change that should not affect dev services
138+
System.out.println("Step 4: Now make a change that should not affect dev services");
139+
test.modifySourceFile(BundledResource.class, s -> s.replaceAll("OK", "poink"));
140+
141+
ping();
142+
143+
List<Container> step4Containers = getRedisContainersExcludingExisting(step3Containers);
144+
145+
// No new containers should have spawned
146+
assertEquals(0, step4Containers.size(),
147+
"New containers: " + step4Containers + "\n Old containers: " + step3Containers + "\n All containers: "
148+
+ getAllContainers()); // this can be wrong
149+
150+
// Step 5: Now make a change that should not affect dev services, but is not the same as the previous change
151+
System.out.println(
152+
"Step 5: Now make a change that should not affect dev services, but is not the same as the previous change");
153+
test.modifySourceFile(BundledResource.class, s -> s.replaceAll("poink", "OK"));
154+
155+
ping();
156+
157+
List<Container> step5Containers = getRedisContainersExcludingExisting(step3Containers);
158+
159+
// No new containers should have spawned
160+
assertEquals(0, step5Containers.size(),
161+
"New containers: " + step5Containers + "\n Old containers: " + step5Containers + "\n All containers: "
162+
+ getAllContainers()); // this can be wrong
163+
}
164+
165+
private static List<Container> getAllContainers() {
166+
return DockerClientFactory.lazyClient().listContainersCmd().exec().stream()
167+
.filter(container -> isRedisContainer(container)).toList();
168+
}
169+
170+
private static List<Container> getRedisContainers() {
171+
return getAllContainers();
172+
}
173+
174+
private static List<Container> getRedisContainersExcludingExisting(Collection<Container> existingContainers) {
175+
return getRedisContainers().stream().filter(
176+
container -> existingContainers.stream().noneMatch(existing -> existing.getId().equals(container.getId())))
177+
.toList();
178+
}
179+
180+
private static List<Container> getAllContainersExcludingExisting(Collection<Container> existingContainers) {
181+
return getAllContainers().stream().filter(
182+
container -> existingContainers.stream().noneMatch(existing -> existing.getId().equals(container.getId())))
183+
.toList();
184+
}
185+
186+
private static boolean isRedisContainer(Container container) {
187+
// The output of getCommand() seems to vary by host OS (it's different on CI and mac), but the image name should be reliable
188+
return container.getImage().contains("redis");
189+
}
190+
191+
private static String prettyPrintContainerList(List<Container> newContainers) {
192+
return newContainers.stream()
193+
.map(c -> Arrays.toString(c.getPorts()) + " -- " + Arrays.toString(c.getNames()) + " -- " + c.getLabels())
194+
.collect(Collectors.joining(", \n"));
195+
}
196+
197+
private static boolean hasPublicPort(Container newContainer, int newPort) {
198+
return Arrays.stream(newContainer.getPorts()).anyMatch(p -> p.getPublicPort() == newPort);
199+
}
200+
201+
void ping() {
202+
when().get("/bundled/ping").then()
203+
.statusCode(200)
204+
.body(is("PONG"));
205+
}
206+
207+
}

integration-tests/redis-devservices/src/test/java/io/quarkus/redis/devservices/continuoustesting/it/DevServicesRedisContinuousTestingTest.java

Lines changed: 16 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
import org.jboss.shrinkwrap.api.ShrinkWrap;
1616
import org.jboss.shrinkwrap.api.asset.StringAsset;
1717
import org.jboss.shrinkwrap.api.spec.JavaArchive;
18+
import org.junit.jupiter.api.AfterAll;
1819
import org.junit.jupiter.api.Disabled;
1920
import org.junit.jupiter.api.Test;
2021
import org.junit.jupiter.api.extension.RegisterExtension;
2122
import org.testcontainers.DockerClientFactory;
2223

24+
import com.github.dockerjava.api.DockerClient;
2325
import com.github.dockerjava.api.model.Container;
2426
import com.github.dockerjava.api.model.ContainerPort;
2527

@@ -52,6 +54,11 @@ public class DevServicesRedisContinuousTestingTest {
5254
"application.properties"))
5355
.setTestArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClass(PlainQuarkusTest.class));
5456

57+
@AfterAll
58+
static void afterAll() {
59+
stopAllContainers();
60+
}
61+
5562
@Disabled("Not currently working")
5663
@Test
5764
public void testContinuousTestingDisablesDevServicesWhenPropertiesChange() {
@@ -69,15 +76,15 @@ public void testContinuousTestingDisablesDevServicesWhenPropertiesChange() {
6976
// We could check the container goes away, but we'd have to check slowly, because ryuk can be slow
7077
}
7178

72-
// This tests behaviour in dev mode proper (rather than continuous testing)
79+
// This tests behaviour in dev mode proper when combined with continuous testing. This creates a possibility of port conflicts, false sharing of state, and all sorts of race conditions.
7380
@Test
7481
public void testDevModeCoexistingWithContinuousTestingServiceUpdatesContainersOnConfigChange() {
75-
// Interacting with the app will force a refresh
7682
// Note that driving continuous testing concurrently can sometimes cause 500s caused by containers not yet being available on slow machines
7783
ContinuousTestingTestUtils continuousTestingTestUtils = new ContinuousTestingTestUtils();
7884
ContinuousTestingTestUtils.TestStatus result = continuousTestingTestUtils.waitForNextCompletion();
7985
assertEquals(result.getTotalTestsPassed(), 1);
8086
assertEquals(result.getTotalTestsFailed(), 0);
87+
// Interacting with the app will force a refresh
8188
ping();
8289

8390
List<Container> started = getRedisContainers();
@@ -123,62 +130,6 @@ public void testDevModeCoexistingWithContinuousTestingServiceUpdatesContainersOn
123130

124131
}
125132

126-
@Test
127-
public void testDevModeServiceUpdatesContainersOnConfigChange() {
128-
// Interacting with the app will force a refresh
129-
ping();
130-
List<Container> started = getRedisContainers();
131-
132-
assertFalse(started.isEmpty());
133-
Container container = started.get(0);
134-
assertTrue(Arrays.stream(container.getPorts()).noneMatch(p -> p.getPublicPort() == 6377),
135-
"Expected random port, but got: " + Arrays.toString(container.getPorts()));
136-
137-
int newPort = 6388;
138-
test.modifyResourceFile("application.properties",
139-
s -> ContinuousTestingTestUtils.appProperties("quarkus.redis.devservices.port=" + newPort));
140-
141-
// Force another refresh
142-
ping();
143-
List<Container> newContainers = getRedisContainersExcludingExisting(started);
144-
145-
// We expect 1 new containers, since test was not refreshed
146-
assertEquals(1, newContainers.size(),
147-
"New containers: "
148-
+ prettyPrintContainerList(newContainers)
149-
+ "\n Old containers: " + prettyPrintContainerList(started) + "\n All containers: "
150-
+ prettyPrintContainerList(getAllContainers())); // this can be wrong
151-
// We need to inspect the dev-mode container; we don't have a non-brittle way of distinguishing them, so just look in them all
152-
boolean hasRightPort = newContainers.stream()
153-
.anyMatch(newContainer -> hasPublicPort(newContainer, newPort));
154-
assertTrue(hasRightPort,
155-
"Expected port " + newPort + ", but got: "
156-
+ newContainers.stream().map(c -> Arrays.toString(c.getPorts())).collect(Collectors.joining(", ")));
157-
}
158-
159-
@Test
160-
public void testDevModeServiceDoesNotRestartContainersOnCodeChange() {
161-
ping();
162-
List<Container> started = getRedisContainers();
163-
164-
assertFalse(started.isEmpty());
165-
Container container = started.get(0);
166-
assertTrue(Arrays.stream(container.getPorts()).noneMatch(p -> p.getPublicPort() == 6377),
167-
"Expected random port 6377, but got: " + Arrays.toString(container.getPorts()));
168-
169-
// Make a change that shouldn't affect dev services
170-
test.modifySourceFile(BundledResource.class, s -> s.replaceAll("OK", "poink"));
171-
172-
ping();
173-
174-
List<Container> newContainers = getRedisContainersExcludingExisting(started);
175-
176-
// No new containers should have spawned
177-
assertEquals(0, newContainers.size(),
178-
"New containers: " + newContainers + "\n Old containers: " + started + "\n All containers: "
179-
+ getAllContainers()); // this can be wrong
180-
}
181-
182133
private static String prettyPrintContainerList(List<Container> newContainers) {
183134
return newContainers.stream()
184135
.map(c -> Arrays.toString(c.getPorts()) + "--" + Arrays.toString(c.getNames()))
@@ -278,6 +229,13 @@ private static List<Container> getAllContainers() {
278229
.filter(container -> isRedisContainer(container)).toList();
279230
}
280231

232+
private static void stopAllContainers() {
233+
DockerClient dockerClient = DockerClientFactory.lazyClient();
234+
dockerClient.listContainersCmd().exec().stream()
235+
.filter(DevServicesRedisContinuousTestingTest::isRedisContainer)
236+
.forEach(c -> dockerClient.stopContainerCmd(c.getId()).exec());
237+
}
238+
281239
private static List<Container> getRedisContainers() {
282240
return getAllContainers();
283241
}

0 commit comments

Comments
 (0)