Skip to content

Commit 457b9f9

Browse files
authored
Merge pull request #89 from iExecBlockchainComputing/feature/integration-tests-with-testcontainers
Use `testcontainers` in integration tests
2 parents a1f5569 + 700bdd9 commit 457b9f9

File tree

5 files changed

+67
-66
lines changed

5 files changed

+67
-66
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ All notable changes to this project will be documented in this file.
1010
- Do not use `broker` to match ordes on chain. (#87 #88)
1111
### Bug Fixes
1212
- Fix security rule to access Swagger API. (#79)
13+
### Quality
14+
- Use `testcontainers` in integration tests. (#89)
1315
### Dependency Upgrades
1416
- Upgrade to `feign` 11.10. (#80)
1517
- Upgrade to `iexec-common` 8.1.0-NEXT-SNAPSHOT. (#83 #85)

build.gradle

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,8 @@ allprojects {
4747
}
4848

4949
configurations {
50-
integrationTestCompile.extendsFrom testCompile
51-
integrationTestRuntime.extendsFrom testRuntime
52-
integrationTestImplementation.extendsFrom testImplementation
50+
itestRuntime.extendsFrom testRuntime
51+
itestImplementation.extendsFrom testImplementation
5352
}
5453

5554
dependencies {
@@ -83,7 +82,9 @@ dependencies {
8382
implementation project(':iexec-blockchain-adapter-api-library')
8483

8584
testImplementation 'org.springframework.boot:spring-boot-starter-test'
86-
testImplementation 'org.awaitility:awaitility:4.0.1'
85+
testImplementation 'org.awaitility:awaitility'
86+
87+
itestImplementation 'org.testcontainers:junit-jupiter:1.18.1'
8788
}
8889

8990
springBoot {
@@ -98,32 +99,24 @@ tasks.named("bootJar") {
9899
}
99100

100101
sourceSets {
101-
integrationTest {
102+
itest {
102103
java {
103104
compileClasspath += main.output + test.output
104105
runtimeClasspath += main.output + test.output
105-
srcDir 'src/itest/java'
106106
}
107-
resources.srcDir 'src/itest/resources'
108107
}
109108
}
110109

111-
task itestSetup(type: Exec) {
112-
commandLine 'docker', 'compose', 'up', '-d'
113-
}
114-
115-
task itestTeardown(type: Exec) {
116-
commandLine 'docker', 'compose', 'down'
117-
}
118-
119-
task itest(type: Test) {
110+
tasks.register("itest", Test) {
111+
doFirst {
112+
exec {
113+
commandLine 'docker', 'compose', 'pull', '-q'
114+
}
115+
}
120116
group 'Verification'
121117
description 'Runs the integration tests.'
122-
dependsOn itestSetup
123-
finalizedBy itestTeardown
124-
setTestClassesDirs(sourceSets.integrationTest.output)
125-
classpath = sourceSets.integrationTest.runtimeClasspath
126-
useJUnitPlatform()
118+
setTestClassesDirs(sourceSets.itest.output)
119+
classpath = sourceSets.itest.runtimeClasspath
127120
}
128121

129122
jar {
@@ -151,7 +144,8 @@ publishing {
151144
}
152145
}
153146

154-
test {
147+
tasks.withType(Test).configureEach {
148+
finalizedBy jacocoTestReport
155149
useJUnitPlatform()
156150
}
157151

@@ -164,7 +158,6 @@ jacocoTestReport {
164158
xml.required = true
165159
}
166160
}
167-
tasks.test.finalizedBy tasks.jacocoTestReport
168161
tasks.sonarqube.dependsOn tasks.jacocoTestReport
169162

170163
ext.jarPathForOCI = relativePath(tasks.bootJar.outputs.files.singleFile)

docker-compose.yml

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,11 @@
1-
version: "3.5"
2-
3-
networks:
4-
iexec-blockchain-adapter-api-net:
5-
name: iexec-blockchain-adapter-api-net
6-
71
services:
82

93
ibaa-chain:
104
image: docker-regis.iex.ec/poco-chain:native-v5.4.2-5s
11-
container_name: ibaa-chain
12-
ports:
13-
- 28545:8545
14-
networks:
15-
- iexec-blockchain-adapter-api-net
5+
expose:
6+
- "8545"
167

178
ibaa-blockchain-adapter-mongo:
189
image: library/mongo:4.4
19-
container_name: ibaa-blockchain-adapter-mongo
2010
entrypoint: "/bin/bash"
2111
command: -c "mongod --bind_ip_all --port 13012"
22-
ports:
23-
- 23012:13012
24-
networks:
25-
- iexec-blockchain-adapter-api-net

src/itest/java/com/iexec/blockchain/IntegrationTests.java

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,27 +42,38 @@
4242
import org.junit.jupiter.api.Assertions;
4343
import org.junit.jupiter.api.BeforeEach;
4444
import org.junit.jupiter.api.Test;
45+
import org.junit.jupiter.api.TestInfo;
4546
import org.springframework.beans.factory.annotation.Autowired;
4647
import org.springframework.boot.test.context.SpringBootTest;
4748
import org.springframework.boot.web.server.LocalServerPort;
4849
import org.springframework.test.context.ActiveProfiles;
50+
import org.springframework.test.context.DynamicPropertyRegistry;
51+
import org.springframework.test.context.DynamicPropertySource;
52+
import org.testcontainers.containers.DockerComposeContainer;
53+
import org.testcontainers.containers.wait.strategy.Wait;
54+
import org.testcontainers.junit.jupiter.Container;
55+
import org.testcontainers.junit.jupiter.Testcontainers;
4956
import org.web3j.crypto.Hash;
5057
import org.web3j.crypto.Sign;
5158

59+
import java.io.File;
5260
import java.math.BigInteger;
5361
import java.util.ArrayList;
5462
import java.util.List;
5563
import java.util.Optional;
5664
import java.util.concurrent.CompletableFuture;
5765
import java.util.concurrent.TimeUnit;
66+
import java.util.concurrent.TimeoutException;
5867
import java.util.stream.IntStream;
5968

6069
import static com.iexec.commons.poco.chain.ChainTaskStatus.ACTIVE;
6170
import static com.iexec.commons.poco.chain.ChainTaskStatus.UNSET;
71+
import static org.assertj.core.api.Assertions.assertThat;
6272

6373
@Slf4j
64-
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
74+
@Testcontainers
6575
@ActiveProfiles("itest")
76+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
6677
class IntegrationTests {
6778

6879
public static final String USER = "admin";
@@ -71,8 +82,20 @@ class IntegrationTests {
7182
public static final int MAX_BLOCK_TO_WAIT = 3;
7283
public static final int POLLING_PER_BLOCK = 2;
7384
public static final int POLLING_INTERVAL_MS = BLOCK_TIME_MS / POLLING_PER_BLOCK;
74-
public static final int MAX_POLLING_ATTEMPTS = MAX_BLOCK_TO_WAIT
75-
* POLLING_PER_BLOCK;
85+
public static final int MAX_POLLING_ATTEMPTS = MAX_BLOCK_TO_WAIT * POLLING_PER_BLOCK;
86+
87+
@Container
88+
static DockerComposeContainer<?> environment = new DockerComposeContainer<>(new File("docker-compose.yml"))
89+
.withExposedService("ibaa-chain", 8545, Wait.forListeningPort())
90+
.withExposedService("ibaa-blockchain-adapter-mongo", 13012, Wait.forListeningPort());
91+
92+
@DynamicPropertySource
93+
static void registerProperties(DynamicPropertyRegistry registry) {
94+
registry.add("chain.id", () -> "65535");
95+
registry.add("chain.hubAddress", () -> "0xC129e7917b7c7DeDfAa5Fff1FB18d5D7050fE8ca");
96+
registry.add("chain.nodeAddress", () -> getServiceUrl(environment.getServicePort("ibaa-chain", 8545)));
97+
registry.add("spring.data.mongodb.port", () -> environment.getServicePort("ibaa-blockchain-adapter-mongo", 13012));
98+
}
7699

77100
@LocalServerPort
78101
private int randomServerPort;
@@ -91,9 +114,15 @@ class IntegrationTests {
91114
private BlockchainAdapterApiClient appClient;
92115

93116
@BeforeEach
94-
void setUp() {
117+
void setUp(TestInfo testInfo) {
118+
log.info(">>> {}", testInfo.getDisplayName());
95119
appClient = BlockchainAdapterApiClientBuilder
96-
.getInstanceWithBasicAuth(Logger.Level.FULL, "http://localhost:" + randomServerPort, USER, PASSWORD);
120+
.getInstanceWithBasicAuth(Logger.Level.FULL, getServiceUrl(randomServerPort), USER, PASSWORD);
121+
}
122+
123+
private static String getServiceUrl(int servicePort) {
124+
log.info("service url http://localhost:{}", servicePort);
125+
return "http://localhost:" + servicePort;
97126
}
98127

99128
@Test
@@ -137,7 +166,7 @@ public void shouldBeFinalized() throws Exception {
137166
}
138167

139168
@Test
140-
public void shouldBurstTransactionsWithAverageOfOneTxPerBlock(){
169+
public void shouldBurstTransactionsWithAverageOfOneTxPerBlock() throws Exception {
141170
int taskVolume = 10;//small volume ensures reasonable execution time on CI/CD
142171
String dealId = triggerDeal(taskVolume);
143172
List<CompletableFuture<Void>> txCompletionWatchers = new ArrayList<>();
@@ -167,7 +196,7 @@ public void shouldBurstTransactionsWithAverageOfOneTxPerBlock(){
167196
txCompletionWatchers.forEach(CompletableFuture::join);
168197
}
169198

170-
private String triggerDeal(int taskVolume) {
199+
private String triggerDeal(int taskVolume) throws Exception {
171200
int secondsPollingInterval = POLLING_INTERVAL_MS / 1000;
172201
int secondsTimeout = secondsPollingInterval * MAX_POLLING_ATTEMPTS;
173202
String appAddress = iexecHubService.createApp(buildRandomName("app"),
@@ -208,10 +237,9 @@ private String triggerDeal(int taskVolume) {
208237
String dealId = brokerService.matchOrders(brokerOrder);
209238
Assertions.assertTrue(StringUtils.isNotEmpty(dealId));
210239
log.info("Created deal: {}", dealId);
211-
//no need to wait since broker is synchronous, just checking deal
212-
//existence for double checking
240+
// no need to wait since broker is synchronous, just checking deal existence
213241
Optional<ChainDeal> chainDeal = iexecHubService.getChainDeal(dealId);
214-
Assertions.assertTrue(chainDeal.isPresent());
242+
assertThat(chainDeal).isPresent();
215243
return dealId;
216244
}
217245

@@ -303,18 +331,18 @@ private void waitStatus(String chainTaskId, ChainTaskStatus statusToWait, int po
303331
ChainTaskStatus status = null;
304332
int attempts = 0;
305333
while(true) {
306-
log.info("Status [status:{}, chainTaskId:{}]", status, chainTaskId);
334+
attempts++;
335+
log.info("Status [status:{}, chainTaskId:{}, attempt:{}]", status, chainTaskId, attempts);
307336
status = iexecHubService.getChainTask(chainTaskId)
308337
.map(ChainTask::getStatus)
309338
.orElse(UNSET);
310-
attempts++;
311339
if (status.equals(statusToWait) || attempts > maxAttempts) {
312340
break;
313341
}
314342
TimeUnit.MILLISECONDS.sleep(pollingTimeMs);
315343
}
316344
if (!status.equals(statusToWait)) {
317-
throw new Exception("Too long to wait for task: " + chainTaskId);
345+
throw new TimeoutException("Too long to wait for task: " + chainTaskId);
318346
}
319347
log.info("Status reached [status:{}, chainTaskId:{}]", status, chainTaskId);
320348
}
@@ -328,15 +356,16 @@ private void waitBeforeFinalizing(String chainTaskId) throws Exception {
328356
int winnerCounter = chainTask.getWinnerCounter();
329357
int revealCounter = chainTask.getRevealCounter();
330358
int attempts = 0;
359+
log.info("{} {}", POLLING_INTERVAL_MS, MAX_POLLING_ATTEMPTS);
331360
while (revealCounter != winnerCounter) {
332-
log.info("Waiting for reveals ({}/{})", revealCounter, winnerCounter);
333-
Thread.sleep(POLLING_INTERVAL_MS);
361+
attempts++;
362+
log.info("Waiting for reveals ({}/{}), attempt {}", revealCounter, winnerCounter, attempts);
363+
Thread.sleep(BLOCK_TIME_MS);
334364
revealCounter = iexecHubService.getChainTask(chainTaskId)
335365
.map(ChainTask::getRevealCounter)
336366
.orElse(0);
337-
attempts++;
338-
if (attempts == MAX_POLLING_ATTEMPTS) {
339-
throw new Exception("Too long to wait for reveal: " + chainTaskId);
367+
if (attempts == MAX_BLOCK_TO_WAIT) {
368+
throw new TimeoutException("Too long to wait for reveal: " + chainTaskId);
340369
}
341370
}
342371
log.info("All revealed ({}/{})", revealCounter, winnerCounter);

src/itest/resources/application-itest.yml

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)