From bc0d82617f51e9f2cc007ba5799b8932c991c001 Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Fri, 19 Jun 2026 05:54:58 +0000
Subject: [PATCH 001/141] Update actions/checkout action to v7
---
.github/workflows/broken-links.yml | 2 +-
.github/workflows/report-gha-workflow-security-problems.yml | 2 +-
.github/workflows/sync-labels.yml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/broken-links.yml b/.github/workflows/broken-links.yml
index ce623e98..a6c3ed5c 100644
--- a/.github/workflows/broken-links.yml
+++ b/.github/workflows/broken-links.yml
@@ -12,7 +12,7 @@ jobs:
link-checker:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- run: mv .github/.lycheeignore .lycheeignore
diff --git a/.github/workflows/report-gha-workflow-security-problems.yml b/.github/workflows/report-gha-workflow-security-problems.yml
index b17aa539..78470285 100644
--- a/.github/workflows/report-gha-workflow-security-problems.yml
+++ b/.github/workflows/report-gha-workflow-security-problems.yml
@@ -17,7 +17,7 @@ jobs:
# Only run this in our repos (Prevent notification spam by forks)
if: ${{ github.repository_owner == 'xdev-software' }}
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Check
id: check
diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml
index c43365ef..00e40162 100644
--- a/.github/workflows/sync-labels.yml
+++ b/.github/workflows/sync-labels.yml
@@ -15,7 +15,7 @@ jobs:
labels:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
with:
sparse-checkout: .github/labels.yml
From 3734ffa515c12f5262c94d4f8cd6fd163978a19a Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Sat, 27 Jun 2026 05:29:44 +0000
Subject: [PATCH 002/141] Update dependency ch.qos.logback:logback-classic to
v1.5.37
---
base-demo/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/base-demo/pom.xml b/base-demo/pom.xml
index d10c5824..bea6ca8a 100644
--- a/base-demo/pom.xml
+++ b/base-demo/pom.xml
@@ -45,7 +45,7 @@
ch.qos.logback
logback-classic
- 1.5.36
+ 1.5.37
test
From b2045ad13e6142f3643e5a11e9385f0081e6ae85 Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Sat, 27 Jun 2026 05:29:52 +0000
Subject: [PATCH 003/141] Update dependency
org.sonatype.central:central-publishing-maven-plugin to v0.11.0
---
db-jdbc-spring-orm-eclipselink/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/db-jdbc-spring-orm-eclipselink/pom.xml b/db-jdbc-spring-orm-eclipselink/pom.xml
index 3508dd9e..73350db1 100644
--- a/db-jdbc-spring-orm-eclipselink/pom.xml
+++ b/db-jdbc-spring-orm-eclipselink/pom.xml
@@ -226,7 +226,7 @@
org.sonatype.central
central-publishing-maven-plugin
- 0.10.0
+ 0.11.0
true
sonatype-central-portal
From 04c6e78a45c68127518dc31ddbe486931511aae5 Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Sun, 28 Jun 2026 05:42:31 +0000
Subject: [PATCH 004/141] Update org.hibernate.orm to v7.4.3.Final
---
db-jdbc-spring-orm-hibernate/pom.xml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/db-jdbc-spring-orm-hibernate/pom.xml b/db-jdbc-spring-orm-hibernate/pom.xml
index 5d61a77c..35bf0a4d 100644
--- a/db-jdbc-spring-orm-hibernate/pom.xml
+++ b/db-jdbc-spring-orm-hibernate/pom.xml
@@ -59,18 +59,18 @@
org.hibernate.orm
hibernate-core
- 7.4.2.Final
+ 7.4.3.Final
org.hibernate.orm
hibernate-scan-jandex
- 7.4.2.Final
+ 7.4.3.Final
org.hibernate.orm
hibernate-hikaricp
- 7.4.2.Final
+ 7.4.3.Final
From bbc7ec47a5287a96fb3fc544dbaf879c3e35751f Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Mon, 29 Jun 2026 05:52:24 +0000
Subject: [PATCH 005/141] Update dependency com.puppycrawl.tools:checkstyle to
v13.7.0
---
pom.xml | 2 +-
template-placeholder/pom.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/pom.xml b/pom.xml
index bb0d7cd9..193ce997 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,7 +45,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/template-placeholder/pom.xml b/template-placeholder/pom.xml
index 4b2b7364..c45d74f2 100644
--- a/template-placeholder/pom.xml
+++ b/template-placeholder/pom.xml
@@ -215,7 +215,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
From 8de5826a064bc8bcdbc4db9453addbe2acca1ed2 Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Mon, 29 Jun 2026 05:52:45 +0000
Subject: [PATCH 006/141] Update org.junit to v6.1.1
---
advanced-demo/integration-tests/pom.xml | 2 +-
advanced-demo/pom.xml | 2 +-
base-demo/pom.xml | 2 +-
base/pom.xml | 4 ++--
db-jdbc/pom.xml | 2 +-
junit-jupiter-api-support/pom.xml | 2 +-
spring-dao-support/pom.xml | 2 +-
7 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/advanced-demo/integration-tests/pom.xml b/advanced-demo/integration-tests/pom.xml
index 900e70f7..c0b3c112 100644
--- a/advanced-demo/integration-tests/pom.xml
+++ b/advanced-demo/integration-tests/pom.xml
@@ -109,7 +109,7 @@
org.junit
junit-bom
- 6.1.0
+ 6.1.1
pom
import
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 4425da10..2f58a665 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -70,7 +70,7 @@
org.junit
junit-bom
- 6.1.0
+ 6.1.1
pom
import
diff --git a/base-demo/pom.xml b/base-demo/pom.xml
index d10c5824..8da70954 100644
--- a/base-demo/pom.xml
+++ b/base-demo/pom.xml
@@ -38,7 +38,7 @@
org.junit.jupiter
junit-jupiter
- 6.1.0
+ 6.1.1
test
diff --git a/base/pom.xml b/base/pom.xml
index 853c63e2..0b2ae0cb 100644
--- a/base/pom.xml
+++ b/base/pom.xml
@@ -69,14 +69,14 @@
org.junit.platform
junit-platform-launcher
compile
- 6.1.0
+ 6.1.1
org.junit.jupiter
junit-jupiter
- 6.1.0
+ 6.1.1
test
diff --git a/db-jdbc/pom.xml b/db-jdbc/pom.xml
index 34598840..c82d5923 100644
--- a/db-jdbc/pom.xml
+++ b/db-jdbc/pom.xml
@@ -72,7 +72,7 @@
org.junit.jupiter
junit-jupiter
- 6.1.0
+ 6.1.1
test
diff --git a/junit-jupiter-api-support/pom.xml b/junit-jupiter-api-support/pom.xml
index 1d5a9864..6654bd9d 100644
--- a/junit-jupiter-api-support/pom.xml
+++ b/junit-jupiter-api-support/pom.xml
@@ -53,7 +53,7 @@
org.junit.jupiter
junit-jupiter-api
- 6.1.0
+ 6.1.1
compile
diff --git a/spring-dao-support/pom.xml b/spring-dao-support/pom.xml
index a5c9d8dc..c2034bf2 100644
--- a/spring-dao-support/pom.xml
+++ b/spring-dao-support/pom.xml
@@ -78,7 +78,7 @@
org.junit.jupiter
junit-jupiter
- 6.1.0
+ 6.1.1
test
From c60dd67e62cc23feb374571043d4c9b9d8e02485 Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Mon, 29 Jun 2026 05:52:49 +0000
Subject: [PATCH 007/141] Update dependency com.puppycrawl.tools:checkstyle to
v13.7.0
---
advanced-demo/pom.xml | 2 +-
base/pom.xml | 2 +-
db-jdbc-spring-orm-eclipselink/pom.xml | 2 +-
db-jdbc-spring-orm-hibernate/pom.xml | 2 +-
db-jdbc-spring-orm/pom.xml | 2 +-
db-jdbc/pom.xml | 2 +-
jacoco/pom.xml | 2 +-
jul-to-slf4j/pom.xml | 2 +-
junit-jupiter-api-support/pom.xml | 2 +-
mockserver/pom.xml | 2 +-
oidc-server-mock/pom.xml | 2 +-
pom.xml | 2 +-
selenium/pom.xml | 2 +-
spring-dao-support/pom.xml | 2 +-
14 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 4425da10..1563e37b 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -195,7 +195,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/base/pom.xml b/base/pom.xml
index 853c63e2..efc5d64b 100644
--- a/base/pom.xml
+++ b/base/pom.xml
@@ -284,7 +284,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/db-jdbc-spring-orm-eclipselink/pom.xml b/db-jdbc-spring-orm-eclipselink/pom.xml
index 3508dd9e..b3ba191e 100644
--- a/db-jdbc-spring-orm-eclipselink/pom.xml
+++ b/db-jdbc-spring-orm-eclipselink/pom.xml
@@ -248,7 +248,7 @@
com.puppycrawl.tools
checkstyle
- 13.5.0
+ 13.7.0
diff --git a/db-jdbc-spring-orm-hibernate/pom.xml b/db-jdbc-spring-orm-hibernate/pom.xml
index 5d61a77c..46bdf5d8 100644
--- a/db-jdbc-spring-orm-hibernate/pom.xml
+++ b/db-jdbc-spring-orm-hibernate/pom.xml
@@ -259,7 +259,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/db-jdbc-spring-orm/pom.xml b/db-jdbc-spring-orm/pom.xml
index 30410169..d4ae469d 100644
--- a/db-jdbc-spring-orm/pom.xml
+++ b/db-jdbc-spring-orm/pom.xml
@@ -258,7 +258,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/db-jdbc/pom.xml b/db-jdbc/pom.xml
index 34598840..e0e9e1f1 100644
--- a/db-jdbc/pom.xml
+++ b/db-jdbc/pom.xml
@@ -268,7 +268,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/jacoco/pom.xml b/jacoco/pom.xml
index 250c1adf..d59499dc 100644
--- a/jacoco/pom.xml
+++ b/jacoco/pom.xml
@@ -242,7 +242,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/jul-to-slf4j/pom.xml b/jul-to-slf4j/pom.xml
index 975557c2..2015b543 100644
--- a/jul-to-slf4j/pom.xml
+++ b/jul-to-slf4j/pom.xml
@@ -242,7 +242,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/junit-jupiter-api-support/pom.xml b/junit-jupiter-api-support/pom.xml
index 1d5a9864..9aea46d1 100644
--- a/junit-jupiter-api-support/pom.xml
+++ b/junit-jupiter-api-support/pom.xml
@@ -243,7 +243,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/mockserver/pom.xml b/mockserver/pom.xml
index 7a908c8f..a1483c80 100644
--- a/mockserver/pom.xml
+++ b/mockserver/pom.xml
@@ -264,7 +264,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/oidc-server-mock/pom.xml b/oidc-server-mock/pom.xml
index 25ba063d..b62d973d 100644
--- a/oidc-server-mock/pom.xml
+++ b/oidc-server-mock/pom.xml
@@ -248,7 +248,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/pom.xml b/pom.xml
index 16a6ad85..9fd2df1f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,7 +77,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/selenium/pom.xml b/selenium/pom.xml
index 9ae5d3ca..2a50cd7b 100644
--- a/selenium/pom.xml
+++ b/selenium/pom.xml
@@ -296,7 +296,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
diff --git a/spring-dao-support/pom.xml b/spring-dao-support/pom.xml
index a5c9d8dc..36933141 100644
--- a/spring-dao-support/pom.xml
+++ b/spring-dao-support/pom.xml
@@ -274,7 +274,7 @@
com.puppycrawl.tools
checkstyle
- 13.6.0
+ 13.7.0
From bf72b0de3e9b032dbbe6375813b23e585f01ebfe Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Tue, 30 Jun 2026 05:37:37 +0000
Subject: [PATCH 008/141] Update net.sourceforge.pmd to v7.26.0
---
pom.xml | 4 ++--
template-placeholder/pom.xml | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/pom.xml b/pom.xml
index bb0d7cd9..8a183196 100644
--- a/pom.xml
+++ b/pom.xml
@@ -83,12 +83,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/template-placeholder/pom.xml b/template-placeholder/pom.xml
index 4b2b7364..b083f7df 100644
--- a/template-placeholder/pom.xml
+++ b/template-placeholder/pom.xml
@@ -253,12 +253,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
From c09cbbb07c2d65f6d9f72d76c85ca25bf5a22f86 Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Tue, 30 Jun 2026 05:37:49 +0000
Subject: [PATCH 009/141] Update dependency
org.apache.httpcomponents.client5:httpclient5 to v5.6.2
---
oidc-server-mock/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/oidc-server-mock/pom.xml b/oidc-server-mock/pom.xml
index 25ba063d..56caf0e1 100644
--- a/oidc-server-mock/pom.xml
+++ b/oidc-server-mock/pom.xml
@@ -59,7 +59,7 @@
org.apache.httpcomponents.client5
httpclient5
- 5.6.1
+ 5.6.2
From 5343c2ad228a1ab34ac31f1c1eeb64663ff0d6bb Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Tue, 30 Jun 2026 05:37:52 +0000
Subject: [PATCH 010/141] Update dependency org.eclipse.persistence:eclipselink
to v5.0.1
---
db-jdbc-spring-orm-eclipselink/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/db-jdbc-spring-orm-eclipselink/pom.xml b/db-jdbc-spring-orm-eclipselink/pom.xml
index 3508dd9e..284cf60a 100644
--- a/db-jdbc-spring-orm-eclipselink/pom.xml
+++ b/db-jdbc-spring-orm-eclipselink/pom.xml
@@ -59,7 +59,7 @@
org.eclipse.persistence
eclipselink
- 5.0.0
+ 5.0.1
From 8d68bc3c54a0cbba5b294042fe4fc2aa3fde5954 Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Tue, 30 Jun 2026 05:37:56 +0000
Subject: [PATCH 011/141] Update net.sourceforge.pmd to v7.26.0
---
advanced-demo/pom.xml | 4 ++--
base/pom.xml | 4 ++--
db-jdbc-spring-orm-eclipselink/pom.xml | 4 ++--
db-jdbc-spring-orm-hibernate/pom.xml | 4 ++--
db-jdbc-spring-orm/pom.xml | 4 ++--
db-jdbc/pom.xml | 4 ++--
jacoco/pom.xml | 4 ++--
jul-to-slf4j/pom.xml | 4 ++--
junit-jupiter-api-support/pom.xml | 4 ++--
mockserver/pom.xml | 4 ++--
oidc-server-mock/pom.xml | 4 ++--
pom.xml | 4 ++--
selenium/pom.xml | 4 ++--
spring-dao-support/pom.xml | 4 ++--
14 files changed, 28 insertions(+), 28 deletions(-)
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 4425da10..746e99e5 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -236,12 +236,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/base/pom.xml b/base/pom.xml
index 853c63e2..e29748c2 100644
--- a/base/pom.xml
+++ b/base/pom.xml
@@ -322,12 +322,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/db-jdbc-spring-orm-eclipselink/pom.xml b/db-jdbc-spring-orm-eclipselink/pom.xml
index 3508dd9e..4efbfed9 100644
--- a/db-jdbc-spring-orm-eclipselink/pom.xml
+++ b/db-jdbc-spring-orm-eclipselink/pom.xml
@@ -286,12 +286,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/db-jdbc-spring-orm-hibernate/pom.xml b/db-jdbc-spring-orm-hibernate/pom.xml
index 5d61a77c..b0e1171e 100644
--- a/db-jdbc-spring-orm-hibernate/pom.xml
+++ b/db-jdbc-spring-orm-hibernate/pom.xml
@@ -297,12 +297,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/db-jdbc-spring-orm/pom.xml b/db-jdbc-spring-orm/pom.xml
index 30410169..73f02ae6 100644
--- a/db-jdbc-spring-orm/pom.xml
+++ b/db-jdbc-spring-orm/pom.xml
@@ -295,12 +295,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/db-jdbc/pom.xml b/db-jdbc/pom.xml
index 34598840..d019aea2 100644
--- a/db-jdbc/pom.xml
+++ b/db-jdbc/pom.xml
@@ -306,12 +306,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/jacoco/pom.xml b/jacoco/pom.xml
index 250c1adf..cc1b07ac 100644
--- a/jacoco/pom.xml
+++ b/jacoco/pom.xml
@@ -280,12 +280,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/jul-to-slf4j/pom.xml b/jul-to-slf4j/pom.xml
index 975557c2..70f6b157 100644
--- a/jul-to-slf4j/pom.xml
+++ b/jul-to-slf4j/pom.xml
@@ -280,12 +280,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/junit-jupiter-api-support/pom.xml b/junit-jupiter-api-support/pom.xml
index 1d5a9864..29ce4039 100644
--- a/junit-jupiter-api-support/pom.xml
+++ b/junit-jupiter-api-support/pom.xml
@@ -281,12 +281,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/mockserver/pom.xml b/mockserver/pom.xml
index 7a908c8f..fbe8b100 100644
--- a/mockserver/pom.xml
+++ b/mockserver/pom.xml
@@ -302,12 +302,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/oidc-server-mock/pom.xml b/oidc-server-mock/pom.xml
index 25ba063d..89d28900 100644
--- a/oidc-server-mock/pom.xml
+++ b/oidc-server-mock/pom.xml
@@ -286,12 +286,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/pom.xml b/pom.xml
index 16a6ad85..7a4570aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -115,12 +115,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/selenium/pom.xml b/selenium/pom.xml
index 9ae5d3ca..55b68bcc 100644
--- a/selenium/pom.xml
+++ b/selenium/pom.xml
@@ -334,12 +334,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
diff --git a/spring-dao-support/pom.xml b/spring-dao-support/pom.xml
index a5c9d8dc..a7d0d6fa 100644
--- a/spring-dao-support/pom.xml
+++ b/spring-dao-support/pom.xml
@@ -312,12 +312,12 @@
net.sourceforge.pmd
pmd-core
- 7.25.0
+ 7.26.0
net.sourceforge.pmd
pmd-java
- 7.25.0
+ 7.26.0
From 793d3112bd42c9d5a5857fe45d06c9ecc0c7f3bc Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 1 Jul 2026 07:50:24 +0200
Subject: [PATCH 012/141] Enable new PMD 7.26 rule
---
.config/pmd/java/ruleset.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.config/pmd/java/ruleset.xml b/.config/pmd/java/ruleset.xml
index b6d43e51..8dde42bc 100644
--- a/.config/pmd/java/ruleset.xml
+++ b/.config/pmd/java/ruleset.xml
@@ -141,6 +141,7 @@
+
From 0fb2aeb6cd624b28815850fa5961d05defefc747 Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 1 Jul 2026 08:02:30 +0200
Subject: [PATCH 013/141] CheckStyle-IDEA: Always use lastest (bundled) version
This prevents annoying popups whenever the plugin updates
---
.idea/checkstyle-idea.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml
index 3dbbcc7a..b8b753e6 100644
--- a/.idea/checkstyle-idea.xml
+++ b/.idea/checkstyle-idea.xml
@@ -1,7 +1,7 @@
- 13.5.0
+ latest
JavaOnlyWithTests
true
true
From 35b99ab618ad1bd0aa03bf36b9881ef96de07340 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 14:41:52 +0200
Subject: [PATCH 014/141] Improve db-jdbc-spring-orm
---
CHANGELOG.md | 5 +++
...pseLinkEntityManagerControllerFactory.java | 12 ++----
...bernateEntityManagerControllerFactory.java | 20 ++-------
.../SpringEntityManagerControllerFactory.java | 43 ++++++++++++++++---
4 files changed, 47 insertions(+), 33 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b428b456..79ae1a63 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+# 3.5.0
+* `db-jdbc-spring-orm` & implementations
+ * No longer use `setPersistenceProviderClassName` because it has no effect
+ * By default create `PersistenceProvider` only once to make it possible to cache redundant calls
+
# 3.4.1
* Remove dedicated code for Java 17 (no longer needed because Java 21 is the minimum)
diff --git a/db-jdbc-spring-orm-eclipselink/src/main/java/software/xdev/tci/db/persistence/eclipselink/EclipseLinkEntityManagerControllerFactory.java b/db-jdbc-spring-orm-eclipselink/src/main/java/software/xdev/tci/db/persistence/eclipselink/EclipseLinkEntityManagerControllerFactory.java
index 7308aab3..a344b7da 100644
--- a/db-jdbc-spring-orm-eclipselink/src/main/java/software/xdev/tci/db/persistence/eclipselink/EclipseLinkEntityManagerControllerFactory.java
+++ b/db-jdbc-spring-orm-eclipselink/src/main/java/software/xdev/tci/db/persistence/eclipselink/EclipseLinkEntityManagerControllerFactory.java
@@ -19,9 +19,6 @@
import java.util.Set;
import java.util.function.Supplier;
-import jakarta.persistence.EntityManagerFactory;
-import jakarta.persistence.spi.PersistenceUnitInfo;
-
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.jpa.PersistenceProvider;
import org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo;
@@ -30,7 +27,7 @@
public class EclipseLinkEntityManagerControllerFactory
- extends SpringEntityManagerControllerFactory
+ extends SpringEntityManagerControllerFactory
{
// Disabled (false) by default as it's not used by the default created SpringPersistenceUnitInfo anyway
protected boolean weavingEnabled;
@@ -69,7 +66,6 @@ protected SpringPersistenceUnitInfo createSpringPersistenceUnitInfo()
final SpringPersistenceUnitInfo spui = super.createSpringPersistenceUnitInfo();
// Required otherwise createContainerEntityManagerFactoryImpl crashes
spui.setPersistenceUnitRootUrl(this.getClass().getResource(""));
- spui.setPersistenceProviderClassName(PersistenceProvider.class.getName());
return spui;
}
@@ -85,10 +81,8 @@ protected Map buildProperties()
}
@Override
- protected EntityManagerFactory createEntityManagerFactory(
- final PersistenceUnitInfo pui,
- final Map properties)
+ protected PersistenceProvider createDefaultPersistenceProvider()
{
- return new PersistenceProvider().createContainerEntityManagerFactory(pui, properties);
+ return new PersistenceProvider();
}
}
diff --git a/db-jdbc-spring-orm-hibernate/src/main/java/software/xdev/tci/db/persistence/hibernate/HibernateEntityManagerControllerFactory.java b/db-jdbc-spring-orm-hibernate/src/main/java/software/xdev/tci/db/persistence/hibernate/HibernateEntityManagerControllerFactory.java
index 49fc9332..b2161daf 100644
--- a/db-jdbc-spring-orm-hibernate/src/main/java/software/xdev/tci/db/persistence/hibernate/HibernateEntityManagerControllerFactory.java
+++ b/db-jdbc-spring-orm-hibernate/src/main/java/software/xdev/tci/db/persistence/hibernate/HibernateEntityManagerControllerFactory.java
@@ -20,20 +20,16 @@
import java.util.Set;
import java.util.function.Supplier;
-import jakarta.persistence.EntityManagerFactory;
-import jakarta.persistence.spi.PersistenceUnitInfo;
-
import org.hibernate.cfg.JdbcSettings;
import org.hibernate.cfg.PersistenceSettings;
import org.hibernate.hikaricp.internal.HikariCPConnectionProvider;
import org.hibernate.jpa.HibernatePersistenceProvider;
-import org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo;
import software.xdev.tci.db.persistence.SpringEntityManagerControllerFactory;
public class HibernateEntityManagerControllerFactory
- extends SpringEntityManagerControllerFactory
+ extends SpringEntityManagerControllerFactory
{
protected String connectionProviderClassName = HikariCPConnectionProvider.class.getName();
protected boolean disableHibernateFormatter = true;
@@ -69,14 +65,6 @@ public HibernateEntityManagerControllerFactory withUseCachingStandardScanner(
return this;
}
- @Override
- protected SpringPersistenceUnitInfo createSpringPersistenceUnitInfo()
- {
- final SpringPersistenceUnitInfo spui = super.createSpringPersistenceUnitInfo();
- spui.setPersistenceProviderClassName(HibernatePersistenceProvider.class.getName());
- return spui;
- }
-
@Override
protected Map buildProperties()
{
@@ -95,10 +83,8 @@ protected Map buildProperties()
}
@Override
- protected EntityManagerFactory createEntityManagerFactory(
- final PersistenceUnitInfo pui,
- final Map properties)
+ protected HibernatePersistenceProvider createDefaultPersistenceProvider()
{
- return new HibernatePersistenceProvider().createContainerEntityManagerFactory(pui, properties);
+ return new HibernatePersistenceProvider();
}
}
diff --git a/db-jdbc-spring-orm/src/main/java/software/xdev/tci/db/persistence/SpringEntityManagerControllerFactory.java b/db-jdbc-spring-orm/src/main/java/software/xdev/tci/db/persistence/SpringEntityManagerControllerFactory.java
index 8231b06d..58d49639 100644
--- a/db-jdbc-spring-orm/src/main/java/software/xdev/tci/db/persistence/SpringEntityManagerControllerFactory.java
+++ b/db-jdbc-spring-orm/src/main/java/software/xdev/tci/db/persistence/SpringEntityManagerControllerFactory.java
@@ -20,21 +20,27 @@
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
+import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
+import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceUnitTransactionType;
import jakarta.persistence.spi.ClassTransformer;
+import jakarta.persistence.spi.PersistenceProvider;
import jakarta.persistence.spi.PersistenceUnitInfo;
import org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo;
@SuppressWarnings("java:S119")
-public abstract class SpringEntityManagerControllerFactory>
+public abstract class SpringEntityManagerControllerFactory<
+ P extends PersistenceProvider,
+ SELF extends SpringEntityManagerControllerFactory>
extends EntityManagerControllerFactory
{
protected boolean addJarFileUrls;
+ protected P persistenceProvider;
protected SpringEntityManagerControllerFactory()
{
@@ -61,6 +67,15 @@ public SELF withAddJarFileUrls(final boolean addJarFileUrls)
return this.self();
}
+ /**
+ * Allows to manually configure a (default) persistence provider
+ */
+ public SELF withPersistenceProvider(final P persistenceProvider)
+ {
+ this.persistenceProvider = persistenceProvider;
+ return this.self();
+ }
+
@SuppressWarnings("java:S4449") // can't add annotations to source code that we do not control
protected SpringPersistenceUnitInfo instantiateSpringPersistenceUnitInfo()
{
@@ -100,12 +115,6 @@ protected SpringPersistenceUnitInfo createSpringPersistenceUnitInfo()
return pui;
}
- @Override
- protected PersistenceUnitInfo createPersistenceUnitInfo()
- {
- return this.createSpringPersistenceUnitInfo().asStandardPersistenceUnitInfo();
- }
-
protected Collection jarFileUrlsToAdd()
{
try
@@ -119,4 +128,24 @@ protected Collection jarFileUrlsToAdd()
throw new UncheckedIOException(ioe);
}
}
+
+ @Override
+ protected PersistenceUnitInfo createPersistenceUnitInfo()
+ {
+ return this.createSpringPersistenceUnitInfo().asStandardPersistenceUnitInfo();
+ }
+
+ protected abstract P createDefaultPersistenceProvider();
+
+ @Override
+ protected EntityManagerFactory createEntityManagerFactory(
+ final PersistenceUnitInfo pui,
+ final Map properties)
+ {
+ if(this.persistenceProvider == null)
+ {
+ this.persistenceProvider = this.createDefaultPersistenceProvider();
+ }
+ return this.persistenceProvider.createContainerEntityManagerFactory(pui, properties);
+ }
}
From 6118a35d6d50a422b2d06ad0d255d3a3f0d74be2 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 14:42:01 +0200
Subject: [PATCH 015/141] Bump version
---
advanced-demo/entities-metamodel/pom.xml | 2 +-
advanced-demo/entities/pom.xml | 2 +-
.../persistence-it-eclipselink/pom.xml | 2 +-
.../persistence-it-hibernate/pom.xml | 2 +-
.../integration-tests/persistence-it/pom.xml | 2 +-
advanced-demo/integration-tests/pom.xml | 10 +++----
.../integration-tests/tci-db/pom.xml | 2 +-
.../integration-tests/tci-webapp/pom.xml | 2 +-
.../integration-tests/webapp-it/pom.xml | 2 +-
advanced-demo/persistence/pom.xml | 2 +-
advanced-demo/pom.xml | 8 +++---
advanced-demo/webapp/pom.xml | 2 +-
base-demo/pom.xml | 4 +--
base/pom.xml | 2 +-
bom/pom.xml | 26 +++++++++----------
db-jdbc-spring-orm-eclipselink/pom.xml | 4 +--
db-jdbc-spring-orm-hibernate/pom.xml | 4 +--
db-jdbc-spring-orm/pom.xml | 4 +--
db-jdbc/pom.xml | 4 +--
jacoco/pom.xml | 4 +--
jul-to-slf4j/pom.xml | 2 +-
junit-jupiter-api-support/pom.xml | 2 +-
mockserver/pom.xml | 4 +--
oidc-server-mock/pom.xml | 4 +--
pom.xml | 2 +-
selenium/pom.xml | 8 +++---
spring-dao-support/pom.xml | 4 +--
27 files changed, 58 insertions(+), 58 deletions(-)
diff --git a/advanced-demo/entities-metamodel/pom.xml b/advanced-demo/entities-metamodel/pom.xml
index 034a922d..73d24334 100644
--- a/advanced-demo/entities-metamodel/pom.xml
+++ b/advanced-demo/entities-metamodel/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo
advanced-demo
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
entities-metamodel
diff --git a/advanced-demo/entities/pom.xml b/advanced-demo/entities/pom.xml
index 13668e87..c5975091 100644
--- a/advanced-demo/entities/pom.xml
+++ b/advanced-demo/entities/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo
advanced-demo
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
entities
diff --git a/advanced-demo/integration-tests/persistence-it-eclipselink/pom.xml b/advanced-demo/integration-tests/persistence-it-eclipselink/pom.xml
index 3e4cbacb..0062e2db 100644
--- a/advanced-demo/integration-tests/persistence-it-eclipselink/pom.xml
+++ b/advanced-demo/integration-tests/persistence-it-eclipselink/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo.it
integration-tests
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
persistence-it-eclipselink
diff --git a/advanced-demo/integration-tests/persistence-it-hibernate/pom.xml b/advanced-demo/integration-tests/persistence-it-hibernate/pom.xml
index e4c6b8d9..8728d38d 100644
--- a/advanced-demo/integration-tests/persistence-it-hibernate/pom.xml
+++ b/advanced-demo/integration-tests/persistence-it-hibernate/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo.it
integration-tests
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
persistence-it-hibernate
diff --git a/advanced-demo/integration-tests/persistence-it/pom.xml b/advanced-demo/integration-tests/persistence-it/pom.xml
index 5e44cb8e..b3b2f7d8 100644
--- a/advanced-demo/integration-tests/persistence-it/pom.xml
+++ b/advanced-demo/integration-tests/persistence-it/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo.it
integration-tests
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
persistence-it
diff --git a/advanced-demo/integration-tests/pom.xml b/advanced-demo/integration-tests/pom.xml
index 900e70f7..de891695 100644
--- a/advanced-demo/integration-tests/pom.xml
+++ b/advanced-demo/integration-tests/pom.xml
@@ -7,12 +7,12 @@
software.xdev.tci.demo
advanced-demo
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci.demo.it
integration-tests
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
pom
@@ -30,7 +30,7 @@
software.xdev.tci.demo.it
persistence-it
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
test-jar
tests
@@ -38,13 +38,13 @@
software.xdev.tci.demo.it
tci-db
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci.demo.it
tci-webapp
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
diff --git a/advanced-demo/integration-tests/tci-db/pom.xml b/advanced-demo/integration-tests/tci-db/pom.xml
index 5cd6248c..8de104dc 100644
--- a/advanced-demo/integration-tests/tci-db/pom.xml
+++ b/advanced-demo/integration-tests/tci-db/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo.it
integration-tests
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
tci-db
diff --git a/advanced-demo/integration-tests/tci-webapp/pom.xml b/advanced-demo/integration-tests/tci-webapp/pom.xml
index affcb9cd..39fa8d66 100644
--- a/advanced-demo/integration-tests/tci-webapp/pom.xml
+++ b/advanced-demo/integration-tests/tci-webapp/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo.it
integration-tests
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
tci-webapp
diff --git a/advanced-demo/integration-tests/webapp-it/pom.xml b/advanced-demo/integration-tests/webapp-it/pom.xml
index e2c0a2a1..1cfe501e 100644
--- a/advanced-demo/integration-tests/webapp-it/pom.xml
+++ b/advanced-demo/integration-tests/webapp-it/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo.it
integration-tests
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
webapp-it
diff --git a/advanced-demo/persistence/pom.xml b/advanced-demo/persistence/pom.xml
index 27577c3b..e9fb9f28 100644
--- a/advanced-demo/persistence/pom.xml
+++ b/advanced-demo/persistence/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo
advanced-demo
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
persistence
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 4425da10..18b2779e 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci.demo
advanced-demo
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
pom
@@ -38,19 +38,19 @@
software.xdev.tci.demo
entities
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci.demo
entities-metamodel
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci.demo
persistence
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
diff --git a/advanced-demo/webapp/pom.xml b/advanced-demo/webapp/pom.xml
index 0a831534..4a0f2d57 100644
--- a/advanced-demo/webapp/pom.xml
+++ b/advanced-demo/webapp/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo
advanced-demo
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
webapp
diff --git a/base-demo/pom.xml b/base-demo/pom.xml
index d10c5824..653b9e11 100644
--- a/base-demo/pom.xml
+++ b/base-demo/pom.xml
@@ -7,11 +7,11 @@
software.xdev.tci
root
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
base-demo
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
diff --git a/base/pom.xml b/base/pom.xml
index 853c63e2..847d9724 100644
--- a/base/pom.xml
+++ b/base/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
base
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
base
diff --git a/bom/pom.xml b/bom/pom.xml
index de9df7dc..922ea4fd 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
bom
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
pom
bom
@@ -51,62 +51,62 @@
software.xdev.tci
base
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
db-jdbc
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
db-jdbc-spring-orm
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
db-jdbc-spring-orm-eclipselink
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
db-jdbc-spring-orm-hibernate
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
jacoco
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
jul-to-slf4j
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
junit-jupiter-api-support
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
mockserver
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
oidc-server-mock
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
selenium
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
spring-dao-support
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
diff --git a/db-jdbc-spring-orm-eclipselink/pom.xml b/db-jdbc-spring-orm-eclipselink/pom.xml
index 3508dd9e..2bb1b8a5 100644
--- a/db-jdbc-spring-orm-eclipselink/pom.xml
+++ b/db-jdbc-spring-orm-eclipselink/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
db-jdbc-spring-orm-eclipselink
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
db-jdbc-spring-orm-eclipselink
@@ -53,7 +53,7 @@
software.xdev.tci
db-jdbc-spring-orm
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
diff --git a/db-jdbc-spring-orm-hibernate/pom.xml b/db-jdbc-spring-orm-hibernate/pom.xml
index 5d61a77c..8e3cbcb2 100644
--- a/db-jdbc-spring-orm-hibernate/pom.xml
+++ b/db-jdbc-spring-orm-hibernate/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
db-jdbc-spring-orm-hibernate
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
db-jdbc-spring-orm-hibernate
@@ -53,7 +53,7 @@
software.xdev.tci
db-jdbc-spring-orm
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
diff --git a/db-jdbc-spring-orm/pom.xml b/db-jdbc-spring-orm/pom.xml
index 30410169..e96f8c07 100644
--- a/db-jdbc-spring-orm/pom.xml
+++ b/db-jdbc-spring-orm/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
db-jdbc-spring-orm
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
db-jdbc-spring-orm
@@ -53,7 +53,7 @@
software.xdev.tci
db-jdbc
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
diff --git a/db-jdbc/pom.xml b/db-jdbc/pom.xml
index 34598840..cf999e1a 100644
--- a/db-jdbc/pom.xml
+++ b/db-jdbc/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
db-jdbc
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
db-jdbc
@@ -53,7 +53,7 @@
software.xdev.tci
base
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
diff --git a/jacoco/pom.xml b/jacoco/pom.xml
index 250c1adf..c095b77d 100644
--- a/jacoco/pom.xml
+++ b/jacoco/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
jacoco
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
jacoco
@@ -53,7 +53,7 @@
software.xdev.tci
base
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
diff --git a/jul-to-slf4j/pom.xml b/jul-to-slf4j/pom.xml
index 975557c2..72eb16d7 100644
--- a/jul-to-slf4j/pom.xml
+++ b/jul-to-slf4j/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
jul-to-slf4j
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
jul-to-slf4j
diff --git a/junit-jupiter-api-support/pom.xml b/junit-jupiter-api-support/pom.xml
index 1d5a9864..50f3f0ff 100644
--- a/junit-jupiter-api-support/pom.xml
+++ b/junit-jupiter-api-support/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
junit-jupiter-api-support
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
junit-jupiter-api-support
diff --git a/mockserver/pom.xml b/mockserver/pom.xml
index 7a908c8f..07da6253 100644
--- a/mockserver/pom.xml
+++ b/mockserver/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
mockserver
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
mockserver
@@ -65,7 +65,7 @@
software.xdev.tci
base
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
diff --git a/oidc-server-mock/pom.xml b/oidc-server-mock/pom.xml
index 25ba063d..29ac41b7 100644
--- a/oidc-server-mock/pom.xml
+++ b/oidc-server-mock/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
oidc-server-mock
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
oidc-server-mock
@@ -53,7 +53,7 @@
software.xdev.tci
base
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
diff --git a/pom.xml b/pom.xml
index 16a6ad85..ba8dc50f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
root
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
pom
diff --git a/selenium/pom.xml b/selenium/pom.xml
index 9ae5d3ca..97c5dcbb 100644
--- a/selenium/pom.xml
+++ b/selenium/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
selenium
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
selenium
@@ -65,18 +65,18 @@
software.xdev.tci
base
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
jul-to-slf4j
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
software.xdev.tci
junit-jupiter-api-support
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
diff --git a/spring-dao-support/pom.xml b/spring-dao-support/pom.xml
index a5c9d8dc..dde5baea 100644
--- a/spring-dao-support/pom.xml
+++ b/spring-dao-support/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
spring-dao-support
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
jar
spring-dao-support
@@ -53,7 +53,7 @@
software.xdev.tci
db-jdbc
- 3.4.2-SNAPSHOT
+ 3.5.0-SNAPSHOT
From 0e793f47d4fac23f189c0381f946da36e6263275 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 14:43:14 +0200
Subject: [PATCH 016/141] Update CHANGELOG.md
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 79ae1a63..738e4f50 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,7 @@
# 3.5.0
* `db-jdbc-spring-orm` & implementations
* No longer use `setPersistenceProviderClassName` because it has no effect
- * By default create `PersistenceProvider` only once to make it possible to cache redundant calls
+ * Performance: Reuse `PersistenceProvider`
# 3.4.1
* Remove dedicated code for Java 17 (no longer needed because Java 21 is the minimum)
From bcac1ea4aea00f0388548d15757ed1ae5d9da989 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 14:46:23 +0200
Subject: [PATCH 017/141] Update CHANGELOG.md
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 738e4f50..4c407230 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
* `db-jdbc-spring-orm` & implementations
* No longer use `setPersistenceProviderClassName` because it has no effect
* Performance: Reuse `PersistenceProvider`
+* Updated dependencies
# 3.4.1
* Remove dedicated code for Java 17 (no longer needed because Java 21 is the minimum)
From 0c0687409a360d900be67734c4d3da25a13b0f4e Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 14:47:13 +0200
Subject: [PATCH 018/141] Update run-integration-tests.yml
---
.github/workflows/run-integration-tests.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 479f6bf2..102f0cf7 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -21,6 +21,7 @@ on:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
jobs:
run-integration-tests:
From 1fea56f49a65effc38c9b68a93fc1743f1458cbc Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Thu, 2 Jul 2026 12:49:56 +0000
Subject: [PATCH 019/141] Update dependency software.xdev.sse:bom to v2.5.1
---
advanced-demo/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 7f16714e..c2d8d72a 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -109,7 +109,7 @@
software.xdev.sse
bom
- 2.5.0
+ 2.5.1
pom
import
From 49495a4e4def082b3ccb7fa50c89dbb8715eac8d Mon Sep 17 00:00:00 2001
From: "Alex \"Blex\" B" <45384811+AB-xdev@users.noreply.github.com>
Date: Thu, 2 Jul 2026 14:50:26 +0200
Subject: [PATCH 020/141] Remove JaCoCo plugin
Latest version is not working and we don't really need the output visible on GitHub actions
---
.github/workflows/run-integration-tests.yml | 20 --------------------
1 file changed, 20 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 102f0cf7..8a7bb3bf 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -100,23 +100,3 @@ jobs:
name: webapp-jacoco-report-${{ matrix.java }}-${{ env.PROJECT_NORMALIZED }}-${{ matrix.parallel }}-${{ matrix.pre-start }}
path: advanced-demo/target/site/jacoco-aggregate
if-no-files-found: ignore
-
- - name: WebApp JaCoCo Code Coverage Report
- if: ${{ matrix.jacoco && success() }}
- id: jacoco_reporter
- uses: PavanMudigonda/jacoco-reporter@e8b54bfea6a667d1a68624dae8a06ba31670667d # v5.1
- with:
- coverage_results_path: advanced-demo/target/site/jacoco-aggregate/jacoco.xml
- coverage_report_name: WebApp Coverage (${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})
- coverage_report_title: JaCoCo
- github_token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Add WebApp JaCoCo report to workflow run summary
- if: ${{ matrix.jacoco && success() }}
- run: |
- echo "| Outcome | Value |" >> $GITHUB_STEP_SUMMARY
- echo "| --- | --- |" >> $GITHUB_STEP_SUMMARY
- echo "| Code Coverage % | ${{ steps.jacoco_reporter.outputs.coverage_percentage }} |" >> $GITHUB_STEP_SUMMARY
- echo "| :heavy_check_mark: Number of Lines Covered | ${{ steps.jacoco_reporter.outputs.covered_lines }} |" >> $GITHUB_STEP_SUMMARY
- echo "| :x: Number of Lines Missed | ${{ steps.jacoco_reporter.outputs.missed_lines }} |" >> $GITHUB_STEP_SUMMARY
- echo "| Total Number of Lines | ${{ steps.jacoco_reporter.outputs.total_lines }} |" >> $GITHUB_STEP_SUMMARY
From 83a9a665110bd1a889040ee5e94a16df6ce64ad2 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 14:51:56 +0200
Subject: [PATCH 021/141] Improve startup performance
---
advanced-demo/webapp/src/main/resources/application.yml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/advanced-demo/webapp/src/main/resources/application.yml b/advanced-demo/webapp/src/main/resources/application.yml
index ada5d52a..e6a4def9 100644
--- a/advanced-demo/webapp/src/main/resources/application.yml
+++ b/advanced-demo/webapp/src/main/resources/application.yml
@@ -1,9 +1,14 @@
spring:
jpa:
+ bootstrap: async
open-in-view: false
hibernate:
ddl-auto: none
properties:
+ jakarta:
+ persistence:
+ validation:
+ mode: none
hibernate:
hbm2ddl:
auto: none
From a57a7c08fefc340d85d9e589f1c1c3d2a5af7de4 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 14:54:13 +0200
Subject: [PATCH 022/141] Update run-integration-tests.yml
---
.github/workflows/run-integration-tests.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 8a7bb3bf..5e1538d0 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -45,7 +45,6 @@ jobs:
if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
permissions:
contents: read
- checks: write # JaCoCo Coverage Report check
steps:
- uses: actions/checkout@v7
From 23f5093b9aee21fc1784e84dbe56cab0e10f1010 Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Sun, 5 Jul 2026 07:31:30 +0000
Subject: [PATCH 023/141] Update org.hibernate.orm to v7.4.4.Final
---
db-jdbc-spring-orm-hibernate/pom.xml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/db-jdbc-spring-orm-hibernate/pom.xml b/db-jdbc-spring-orm-hibernate/pom.xml
index 222312c2..050ae61c 100644
--- a/db-jdbc-spring-orm-hibernate/pom.xml
+++ b/db-jdbc-spring-orm-hibernate/pom.xml
@@ -59,18 +59,18 @@
org.hibernate.orm
hibernate-core
- 7.4.3.Final
+ 7.4.4.Final
org.hibernate.orm
hibernate-scan-jandex
- 7.4.3.Final
+ 7.4.4.Final
org.hibernate.orm
hibernate-hikaricp
- 7.4.3.Final
+ 7.4.4.Final
From 9ef1b9cda4041678476fc2ca223c556f0160590a Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Mon, 6 Jul 2026 08:26:01 +0000
Subject: [PATCH 024/141] Update dependency
software.xdev:testcontainers-selenium to v2.0.1
---
selenium/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/selenium/pom.xml b/selenium/pom.xml
index b6c4abc9..98e1c7c5 100644
--- a/selenium/pom.xml
+++ b/selenium/pom.xml
@@ -82,7 +82,7 @@
software.xdev
testcontainers-selenium
- 2.0.0
+ 2.0.1
From a7b47644cef31f09cc84bcf2d48d017af0bfa840 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 15:29:30 +0200
Subject: [PATCH 025/141] Execute integration tests faster by building docker
image natively
---
.github/workflows/run-integration-tests.yml | 94 ++++++++++++++++++---
advanced-demo/webapp/.dockerignore | 5 ++
advanced-demo/webapp/Dockerfile | 11 ++-
3 files changed, 95 insertions(+), 15 deletions(-)
create mode 100644 advanced-demo/webapp/.dockerignore
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 5e1538d0..c0e8617b 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -24,25 +24,23 @@ concurrency:
cancel-in-progress: true
jobs:
- run-integration-tests:
+ persistence:
strategy:
fail-fast: false
matrix:
- project: [persistence-it-hibernate, webapp-it]
+ project: [persistence-it-hibernate]
parallel: [0, 2]
pre-start: [false, true]
java: [21]
include:
- - project: webapp-it
- jacoco: true
- video: true
- project: persistence-it-eclipselink
parallel: 2
pre-start: true
java: 21
- name: "run-integration-tests (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
+ name: "persistence (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
runs-on: ubuntu-latest
if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
+ timeout-minutes: 10
permissions:
contents: read
steps:
@@ -58,16 +56,84 @@ jobs:
uses: actions/cache@v6
with:
path: ~/.m2/repository
- key: ${{ runner.os }}-mvn-it-build-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-mvn-it-build-
- ${{ runner.os }}-mvn-build-
+ key: ${{ runner.os }}-mvn-it-build-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+
+ - name: Test
+ run: |
+ ./mvnw -B test \
+ -pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
+ -P run-it \
+ ${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
+ ${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
+
+ selenium:
+ strategy:
+ fail-fast: false
+ matrix:
+ project: [webapp-it]
+ parallel: [0, 2]
+ pre-start: [false, true]
+ native-image-build: [true]
+ java: [21]
+ include:
+ - project: webapp-it
+ parallel: 2
+ pre-start: true
+ native-image-build: false
+ java: 21
+ name: "selenium (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
+ runs-on: ubuntu-latest
+ if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
+ timeout-minutes: 30
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@v7
+
+ - name: Set up JDK
+ uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: ${{ matrix.java }}
+
+ - name: Cache Maven
+ uses: actions/cache@v6
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-mvn-it-build-selenium-${{ matrix.project }}-${{ matrix.native-image-build }}-${{ hashFiles('**/pom.xml') }}
+
+ - name: Build webapp
+ if: ${{ matrix.native-image-build }}
+ run: |
+ ./mvnw -B package -pl advanced-demo/webapp -am -Pprod,dev-log -T2C -Dmaven.test.skip
+
+ - name: Setup QEMU
+ if: ${{ matrix.native-image-build }}
+ uses: docker/setup-qemu-action@v4
+
+ - name: Setup buildx
+ if: ${{ matrix.native-image-build }}
+ uses: docker/setup-buildx-action@v4
+
+ - name: Build webapp docker image
+ if: ${{ matrix.native-image-build }}
+ uses: docker/build-push-action@v7
+ with:
+ context: advanced-demo/webapp
+ build-args: |
+ JACOCO_AGENT_ENABLED=1
+ push: false
+ load: true
+ tags: webapp
+ cache-from: type=gha,scope=webapp-it
+ cache-to: type=gha,mode=max,scope=webapp-it
- name: Test
run: |
./mvnw -B test \
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
- -P run-it${{ matrix.jacoco && ',jacoco' || '' }} \
+ -P run-it,jacoco \
+ ${{ matrix.native-image-build && '-DappDockerImage=webapp' || '' }} \
${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
@@ -79,7 +145,7 @@ jobs:
run: echo PROJECT_NORMALIZED=${PROJECT/\//-} >> $GITHUB_ENV
- name: Upload videos of test failures
- if: ${{ matrix.video && failure() }}
+ if: ${{ failure() }}
uses: actions/upload-artifact@v7
with:
name: test-fail-videos-${{ matrix.java }}-${{ env.PROJECT_NORMALIZED }}-${{ matrix.parallel }}-${{ matrix.pre-start }}
@@ -87,13 +153,13 @@ jobs:
if-no-files-found: ignore
- name: Aggregate WebApp JaCoCo report
- if: ${{ matrix.jacoco && success() }}
+ if: ${{ success() }}
# Important: Also run compile (if not already done) so that we have all class files - otherwise the report will be incomplete
run: |
./mvnw -B compile jacoco-aggregator:report-aggregate-all -pl "advanced-demo/webapp" -am -T2C
- name: Upload WebApp JaCoCo report
- if: ${{ matrix.jacoco && success() }}
+ if: ${{ success() }}
uses: actions/upload-artifact@v7
with:
name: webapp-jacoco-report-${{ matrix.java }}-${{ env.PROJECT_NORMALIZED }}-${{ matrix.parallel }}-${{ matrix.pre-start }}
diff --git a/advanced-demo/webapp/.dockerignore b/advanced-demo/webapp/.dockerignore
new file mode 100644
index 00000000..c84ae3e6
--- /dev/null
+++ b/advanced-demo/webapp/.dockerignore
@@ -0,0 +1,5 @@
+# by default ignore everything!
+*
+
+# allow only things that should be used in the context
+!target/webapp.jar
diff --git a/advanced-demo/webapp/Dockerfile b/advanced-demo/webapp/Dockerfile
index b20b1c25..47f962f1 100644
--- a/advanced-demo/webapp/Dockerfile
+++ b/advanced-demo/webapp/Dockerfile
@@ -45,6 +45,9 @@ RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted
FROM jre-minimized
+ARG JACOCO_AGENT_ENABLED
+ARG JACOCO_AGENT_VERSION="0.8.14"
+
ARG user=app
ARG group=app
ARG uid=1000
@@ -55,7 +58,13 @@ ARG APP_DIR=/opt/app
RUN mkdir -p ${APP_DIR} \
&& chown ${uid}:${gid} ${APP_DIR} \
&& addgroup -g ${gid} ${group} \
- && adduser -h "$APP_DIR" -u ${uid} -G ${group} -s /bin/bash -D ${user}
+ && adduser -h "$APP_DIR" -u ${uid} -G ${group} -s /bin/bash -D ${user}; \
+ # Download JaCoCoAgent if required
+ if [ "$JACOCO_AGENT_ENABLED" = "1" ]; then \
+ mkdir -p /jacoco/report \
+ && chown $uid:$gid /jacoco/report \
+ && wget https://repo.maven.apache.org/maven2/org/jacoco/org.jacoco.agent/$JACOCO_AGENT_VERSION/org.jacoco.agent-$JACOCO_AGENT_VERSION-runtime.jar -O /jacoco/agent.jar; \
+ fi
WORKDIR ${APP_DIR}
From 53984ed1aa8814073f4257c00aab8ef7384db17e Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 15:55:40 +0200
Subject: [PATCH 026/141] New image caching approach
---
.github/workflows/run-integration-tests.yml | 74 +++++++++------------
1 file changed, 33 insertions(+), 41 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index c0e8617b..4856dfd0 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -73,14 +73,7 @@ jobs:
project: [webapp-it]
parallel: [0, 2]
pre-start: [false, true]
- native-image-build: [true]
java: [21]
- include:
- - project: webapp-it
- parallel: 2
- pre-start: true
- native-image-build: false
- java: 21
name: "selenium (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
runs-on: ubuntu-latest
if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
@@ -100,55 +93,54 @@ jobs:
uses: actions/cache@v6
with:
path: ~/.m2/repository
- key: ${{ runner.os }}-mvn-it-build-selenium-${{ matrix.project }}-${{ matrix.native-image-build }}-${{ hashFiles('**/pom.xml') }}
-
- - name: Build webapp
- if: ${{ matrix.native-image-build }}
- run: |
- ./mvnw -B package -pl advanced-demo/webapp -am -Pprod,dev-log -T2C -Dmaven.test.skip
+ key: ${{ runner.os }}-mvn-it-build-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
- - name: Setup QEMU
- if: ${{ matrix.native-image-build }}
- uses: docker/setup-qemu-action@v4
-
- - name: Setup buildx
- if: ${{ matrix.native-image-build }}
- uses: docker/setup-buildx-action@v4
-
- - name: Build webapp docker image
- if: ${{ matrix.native-image-build }}
- uses: docker/build-push-action@v7
+ - name: Restore docker image cache
+ uses: actions/cache/restore@v6
+ id: restore-docker-image-cache
with:
- context: advanced-demo/webapp
- build-args: |
- JACOCO_AGENT_ENABLED=1
- push: false
- load: true
- tags: webapp
- cache-from: type=gha,scope=webapp-it
- cache-to: type=gha,mode=max,scope=webapp-it
+ path: ~/.cache/docker/
+ key: ${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ ${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-
+
+ - name: Load docker image cache
+ run: |
+ cat ~/.cache/docker/images.tar | docker load && echo "Restored docker image cache" && docker image ls || true
- name: Test
run: |
./mvnw -B test \
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
-P run-it,jacoco \
- ${{ matrix.native-image-build && '-DappDockerImage=webapp' || '' }} \
${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
- # Replace '/' with '-'
- - name: Normalize project name
- if: ${{ !cancelled() }}
- env:
- PROJECT: ${{ matrix.project }}
- run: echo PROJECT_NORMALIZED=${PROJECT/\//-} >> $GITHUB_ENV
+ - name: Create docker image cache
+ run: |
+ echo "Images before pruning" && docker image ls
+ docker image prune -af --filter "until=2160h"
+ docker image prune -f
+ echo "Currently available images" && docker image ls
+ echo "Will NOT cache images" && docker image ls --f="label=org.testcontainers.sessionId"
+ mkdir -p ~/.cache/docker
+ image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q --f="label=org.testcontainers.sessionId" | sort))
+ echo "Saving image ids" && echo $image_ids_to_save
+ docker save $image_ids_to_save -o ~/.cache/docker/images.tar
+ ls -lha ~/.cache/docker
+
+ - name: Save docker image cache
+ uses: actions/cache/restore@v6
+ with:
+ path: ~/.cache/docker/
+ key: ${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
- name: Upload videos of test failures
if: ${{ failure() }}
uses: actions/upload-artifact@v7
with:
- name: test-fail-videos-${{ matrix.java }}-${{ env.PROJECT_NORMALIZED }}-${{ matrix.parallel }}-${{ matrix.pre-start }}
+ name: test-fail-videos-${{ matrix.java }}-${{ matrix.project }}-${{ matrix.parallel }}-${{ matrix.pre-start }}
path: advanced-demo/integration-tests/${{ matrix.project }}/target/records
if-no-files-found: ignore
@@ -162,6 +154,6 @@ jobs:
if: ${{ success() }}
uses: actions/upload-artifact@v7
with:
- name: webapp-jacoco-report-${{ matrix.java }}-${{ env.PROJECT_NORMALIZED }}-${{ matrix.parallel }}-${{ matrix.pre-start }}
+ name: webapp-jacoco-report-${{ matrix.java }}-${{ matrix.project }}-${{ matrix.parallel }}-${{ matrix.pre-start }}
path: advanced-demo/target/site/jacoco-aggregate
if-no-files-found: ignore
From b337a59cc343fa1b8133cd1bc1b76dbd0ec5a046 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 16:03:42 +0200
Subject: [PATCH 027/141] Update run-integration-tests.yml
---
.github/workflows/run-integration-tests.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 4856dfd0..e12958b7 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -125,7 +125,7 @@ jobs:
echo "Currently available images" && docker image ls
echo "Will NOT cache images" && docker image ls --f="label=org.testcontainers.sessionId"
mkdir -p ~/.cache/docker
- image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q --f="label=org.testcontainers.sessionId" | sort))
+ image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q -f="label=org.testcontainers.sessionId" | sort))
echo "Saving image ids" && echo $image_ids_to_save
docker save $image_ids_to_save -o ~/.cache/docker/images.tar
ls -lha ~/.cache/docker
From 3733d2f02b0df393333715b7af62b2f9e16c536c Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 16:03:51 +0200
Subject: [PATCH 028/141] Update run-integration-tests.yml
---
.github/workflows/run-integration-tests.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index e12958b7..ea305f8b 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -123,7 +123,7 @@ jobs:
docker image prune -af --filter "until=2160h"
docker image prune -f
echo "Currently available images" && docker image ls
- echo "Will NOT cache images" && docker image ls --f="label=org.testcontainers.sessionId"
+ echo "Will NOT cache images" && docker image ls -f="label=org.testcontainers.sessionId"
mkdir -p ~/.cache/docker
image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q -f="label=org.testcontainers.sessionId" | sort))
echo "Saving image ids" && echo $image_ids_to_save
From 2dc1997ab430f6ca935663f72019ce14990bc23a Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 16:06:59 +0200
Subject: [PATCH 029/141] Update run-integration-tests.yml
---
.github/workflows/run-integration-tests.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index ea305f8b..8b07d99b 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -107,6 +107,9 @@ jobs:
- name: Load docker image cache
run: |
+ echo "Deleting preinstalled images" && docker image ls
+ docker image prune -af
+
cat ~/.cache/docker/images.tar | docker load && echo "Restored docker image cache" && docker image ls || true
- name: Test
@@ -123,11 +126,14 @@ jobs:
docker image prune -af --filter "until=2160h"
docker image prune -f
echo "Currently available images" && docker image ls
+
echo "Will NOT cache images" && docker image ls -f="label=org.testcontainers.sessionId"
+
mkdir -p ~/.cache/docker
image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q -f="label=org.testcontainers.sessionId" | sort))
echo "Saving image ids" && echo $image_ids_to_save
docker save $image_ids_to_save -o ~/.cache/docker/images.tar
+
ls -lha ~/.cache/docker
- name: Save docker image cache
From 91dd68a82c04665a3210f0b4104c408d7941316f Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 16:22:17 +0200
Subject: [PATCH 030/141] Update run-integration-tests.yml
---
.github/workflows/run-integration-tests.yml | 41 ++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 8b07d99b..b8abcea8 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -58,6 +58,23 @@ jobs:
path: ~/.m2/repository
key: ${{ runner.os }}-mvn-it-build-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ - name: Restore docker image cache
+ uses: actions/cache/restore@v6
+ id: restore-docker-image-cache
+ with:
+ path: ~/.cache/docker/
+ key: ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-
+
+ - name: Load docker image cache
+ run: |
+ echo "Deleting preinstalled images" && docker image ls
+ docker image prune -af
+
+ cat ~/.cache/docker/images.tar | docker load && echo "Restored docker image cache" && docker image ls || true
+
- name: Test
run: |
./mvnw -B test \
@@ -66,6 +83,28 @@ jobs:
${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
+ - name: Create docker image cache
+ run: |
+ echo "Images before pruning" && docker image ls
+ docker image prune -af --filter "until=2160h"
+ docker image prune -f
+ echo "Currently available images" && docker image ls
+
+ echo "Will NOT cache images" && docker image ls -f="label=org.testcontainers.sessionId"
+
+ mkdir -p ~/.cache/docker
+ image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q -f="label=org.testcontainers.sessionId" | sort))
+ echo "Saving image ids" && echo $image_ids_to_save
+ docker save $image_ids_to_save -o ~/.cache/docker/images.tar
+
+ ls -lha ~/.cache/docker
+
+ - name: Save docker image cache
+ uses: actions/cache/save@v6
+ with:
+ path: ~/.cache/docker/
+ key: ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+
selenium:
strategy:
fail-fast: false
@@ -137,7 +176,7 @@ jobs:
ls -lha ~/.cache/docker
- name: Save docker image cache
- uses: actions/cache/restore@v6
+ uses: actions/cache/save@v6
with:
path: ~/.cache/docker/
key: ${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
From 80057e0b599179dbec4270926bb36fa4095bff25 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 2 Jul 2026 16:30:35 +0200
Subject: [PATCH 031/141] Update run-integration-tests.yml
---
.github/workflows/run-integration-tests.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index b8abcea8..30e16bf3 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -84,6 +84,8 @@ jobs:
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
- name: Create docker image cache
+ # Cache can't be saved when it's already present
+ if: steps.cache.outputs.cache-hit != 'true'
run: |
echo "Images before pruning" && docker image ls
docker image prune -af --filter "until=2160h"
@@ -100,6 +102,7 @@ jobs:
ls -lha ~/.cache/docker
- name: Save docker image cache
+ if: steps.cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v6
with:
path: ~/.cache/docker/
@@ -160,6 +163,8 @@ jobs:
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
- name: Create docker image cache
+ # Cache can't be saved when it's already present
+ if: steps.cache.outputs.cache-hit != 'true'
run: |
echo "Images before pruning" && docker image ls
docker image prune -af --filter "until=2160h"
@@ -176,6 +181,7 @@ jobs:
ls -lha ~/.cache/docker
- name: Save docker image cache
+ if: steps.cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v6
with:
path: ~/.cache/docker/
From 01bed2fe563dd57f5acb5b83ed4a8475af2ade56 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 3 Jul 2026 13:23:57 +0200
Subject: [PATCH 032/141] Tag intermediate builds
---
.github/workflows/run-integration-tests.yml | 1 +
advanced-demo/integration-tests/pom.xml | 2 +-
.../containers/WebAppContainerBuilder.java | 149 ++++++++++--------
.../tci/webapp/factory/WebAppTCIFactory.java | 34 ++--
.../xdev/tci/concurrent/Suppliers.java | 112 +++++++++++++
selenium/pom.xml | 2 +-
6 files changed, 213 insertions(+), 87 deletions(-)
create mode 100644 base/src/main/java/software/xdev/tci/concurrent/Suppliers.java
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 30e16bf3..76529549 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -159,6 +159,7 @@ jobs:
./mvnw -B test \
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
-P run-it,jacoco \
+ -DtagIntermediate=1 \
${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
diff --git a/advanced-demo/integration-tests/pom.xml b/advanced-demo/integration-tests/pom.xml
index 2a891042..cea820dc 100644
--- a/advanced-demo/integration-tests/pom.xml
+++ b/advanced-demo/integration-tests/pom.xml
@@ -118,7 +118,7 @@
software.xdev
testcontainers-advanced-imagebuilder
- 2.5.0
+ 3.0.1
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
index e6bffafb..2cee6c56 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
@@ -3,14 +3,15 @@
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.time.Duration;
import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+import java.util.concurrent.CompletableFuture;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import software.xdev.tci.concurrent.TCIExecutorServiceHolder;
import software.xdev.tci.jacoco.testbase.config.JaCoCoConfig;
import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
import software.xdev.testcontainers.imagebuilder.compat.DockerfileCOPYParentsEmulator;
@@ -30,7 +31,7 @@ private WebAppContainerBuilder()
{
}
- public static synchronized String getBuiltImageName()
+ public static synchronized String getBuiltImageName(final boolean tagIntermediate)
{
if(builtImageName != null)
{
@@ -42,81 +43,97 @@ public static synchronized String getBuiltImageName()
final AdvancedImageFromDockerFile builder =
new AdvancedImageFromDockerFile("webapp-it-local", false)
.withLoggerForBuild(LOG_CONTAINER_BUILD)
- .withPostGitIgnoreLines(
- // Ignore git-folder, as it will be provided in the Dockerfile
- ".git/**",
- // Ignore other unused folders and extensions
- "*.iml",
- "*.cmd",
- "*.md",
- "_dev_infra/**",
- "_resource_metrics/**",
- // Ignore other Dockerfiles (our required file will always be transferred)
- "Dockerfile",
- // Ignore not required test-modules that may have changed
- // sources only - otherwise the parent pom doesn't find the resources
- "integration-tests/**",
- "**/src/test/**",
- // Ignore resources that are just used for development
- "webapp/src/main/resources-dev/**",
- // Most files from these folders need to be ignored -> Down there for highest prio
- "node_modules",
- "target")
.withDockerFilePath(Paths.get("../../integration-tests/tci-webapp/Dockerfile"))
.withBaseDir(Paths.get("../../"))
- // File is in root directory - we can't access it
- .withBaseDirRelativeIgnoreFile(null)
- .withDockerFileLinesModifier(new DockerfileCOPYParentsEmulator())
- .withTransferArchiveTARCompressorCustomizer(c -> c
- // Rewrite parent pom to exclude integration tests
- // This way changes in test pom's cause no redownload of dependencies
- .withContentModifier(new FileLinesContentModifier()
- {
- @Override
- public boolean shouldApply(
- final Path sourcePath,
- final String targetPath,
- final TarArchiveEntry tarArchiveEntry)
- {
- return "pom.xml".equals(targetPath);
- }
-
- @Override
- public List modify(
- final List lines,
- final Path sourcePath,
- final String targetPath,
- final TarArchiveEntry tarArchiveEntry) throws IOException
- {
- return lines.stream()
- // Remove integration tests module
- .filter(s -> !s.contains("integration-tests"))
- .toList();
- }
-
- @Override
- public boolean isIdentical(final List original, final List created)
+ .withCreateTransferFilesCache(tagIntermediate)
+ .configureFilesToTransferHandler(h -> h
+ .withPostGitIgnoreLines(
+ // Ignore git-folder, as it will be provided in the Dockerfile
+ ".git/**",
+ // Ignore other unused folders and extensions
+ "*.iml",
+ "*.cmd",
+ "*.md",
+ "_dev_infra/**",
+ "_resource_metrics/**",
+ // Ignore other Dockerfiles (our required file will always be transferred)
+ "Dockerfile",
+ // Ignore not required test-modules that may have changed
+ // sources only - otherwise the parent pom doesn't find the resources
+ "integration-tests/**",
+ "**/src/test/**",
+ // Ignore resources that are just used for development
+ "webapp/src/main/resources-dev/**",
+ // Most files from these folders need to be ignored -> Down there for highest prio
+ "node_modules",
+ "target")
+ // File is in root directory - we can't access it
+ .withBaseDirRelativeIgnoreFile(null)
+ .withDockerFileLinesModifier(new DockerfileCOPYParentsEmulator())
+ .withTransferArchiveTARCompressorCustomizer(c -> c
+ // Rewrite parent pom to exclude integration tests
+ // This way changes in test pom's cause no redownload of dependencies
+ .withContentModifier(new FileLinesContentModifier()
{
- return original.size() == created.size();
- }
- }));
+ @Override
+ public boolean shouldApply(
+ final Path sourcePath,
+ final String targetPath,
+ final TarArchiveEntry tarArchiveEntry)
+ {
+ return "pom.xml".equals(targetPath);
+ }
+
+ @Override
+ public List modify(
+ final List lines,
+ final Path sourcePath,
+ final String targetPath,
+ final TarArchiveEntry tarArchiveEntry) throws IOException
+ {
+ return lines.stream()
+ // Remove integration tests module
+ .filter(s -> !s.contains("integration-tests"))
+ .toList();
+ }
+
+ @Override
+ public boolean isIdentical(final List original, final List created)
+ {
+ return original.size() == created.size();
+ }
+ }))
+ );
if(JaCoCoConfig.instance().enabled())
{
builder.withBuildArg("JACOCO_AGENT_ENABLED", "1");
}
- try
- {
- builtImageName = builder.get(5, TimeUnit.MINUTES);
- }
- catch(final TimeoutException tex)
- {
- throw new IllegalStateException("Timed out", tex);
- }
+ builtImageName = builder.build(Duration.ofMinutes(5));
LOG.info("Built Image; Name ='{}'", builtImageName);
+ if(tagIntermediate)
+ {
+ CompletableFuture.runAsync(
+ () -> {
+ try
+ {
+ builder.copyForIntermediateTag("webapp-it-local-builder", "builder")
+ .build(Duration.ofMinutes(1));
+ builder.cleanCreatedTransferFilesCache();
+
+ LOG.info("Tagged intermediate image");
+ }
+ catch(final Exception ex)
+ {
+ LOG.warn("Tagging intermediate target failed", ex);
+ }
+ },
+ TCIExecutorServiceHolder.instance());
+ }
+
return builtImageName;
}
}
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
index 05498a62..39f7cab2 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
@@ -1,8 +1,11 @@
package software.xdev.tci.demo.tci.webapp.factory;
import java.time.Duration;
+import java.util.Objects;
import java.util.function.Consumer;
+import java.util.function.Supplier;
+import software.xdev.tci.concurrent.Suppliers;
import software.xdev.tci.demo.tci.webapp.WebAppTCI;
import software.xdev.tci.demo.tci.webapp.containers.WebAppContainer;
import software.xdev.tci.demo.tci.webapp.containers.WebAppContainerBuilder;
@@ -14,8 +17,17 @@
public class WebAppTCIFactory extends PreStartableTCIFactory
{
public static final String PROPERTY_APP_DOCKERIMAGE = "appDockerImage";
+ public static final String PROPERTY_TAG_INTERMEDIATE = "tagIntermediate";
- protected static String appImageName;
+ protected static final Supplier APP_IMAGE_NAME_SUPPLIER = Suppliers.memoize(() ->
+ Objects.requireNonNullElseGet(
+ System.getProperty(PROPERTY_APP_DOCKERIMAGE),
+ () -> {
+ String property = System.getProperty(PROPERTY_TAG_INTERMEDIATE);
+ return WebAppContainerBuilder.getBuiltImageName(
+ "1".equals(property) || Boolean.parseBoolean(property));
+ })
+ );
@SuppressWarnings("checkstyle:MagicNumber")
public WebAppTCIFactory(final Consumer additionalContainerBuilder)
@@ -23,7 +35,7 @@ public WebAppTCIFactory(final Consumer additionalContainerBuild
super(
WebAppTCI::new,
() -> {
- final WebAppContainer container = new WebAppContainer(getAppImageName(), true)
+ final WebAppContainer container = new WebAppContainer(APP_IMAGE_NAME_SUPPLIER.get(), true)
.withDefaultWaitStrategy(
Duration.ofSeconds(40L + 20L * EnvironmentPerformance.cpuSlownessFactor()),
WebAppTCI.ACTUATOR_USERNAME,
@@ -47,23 +59,7 @@ public WebAppTCIFactory(final Consumer additionalContainerBuild
@Override
protected void warmUpInternal()
{
- getAppImageName();
+ APP_IMAGE_NAME_SUPPLIER.get();
super.warmUpInternal();
}
-
- protected static synchronized String getAppImageName()
- {
- if(appImageName != null)
- {
- return appImageName;
- }
-
- appImageName = System.getProperty(PROPERTY_APP_DOCKERIMAGE);
- if(appImageName == null)
- {
- appImageName = WebAppContainerBuilder.getBuiltImageName();
- }
-
- return appImageName;
- }
}
diff --git a/base/src/main/java/software/xdev/tci/concurrent/Suppliers.java b/base/src/main/java/software/xdev/tci/concurrent/Suppliers.java
new file mode 100644
index 00000000..9c5df503
--- /dev/null
+++ b/base/src/main/java/software/xdev/tci/concurrent/Suppliers.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.concurrent;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
+
+/**
+ * Based on
+ *
+ * Guava Supplier
+ *
+ * and minimized.
+ */
+public final class Suppliers
+{
+ private Suppliers()
+ {
+ }
+
+ /**
+ * Returns a supplier which caches the instance retrieved during the first call to {@code get()} and returns that
+ * value on subsequent calls to {@code get()}. See: memoization
+ *
+ * The returned supplier is thread-safe. The delegate's {@code get()} method will be invoked at
+ * most once unless the underlying {@code get()} throws an exception. The supplier's serialized form does not
+ * contain the cached value, which will be recalculated when {@code get()} is called on the deserialized instance.
+ *
+ *
When the underlying delegate throws an exception then this memoizing supplier will keep
+ * delegating calls until it returns valid data.
+ *
+ *
If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is
+ * returned directly.
+ */
+ public static Supplier memoize(final Supplier delegate)
+ {
+ if(delegate instanceof NonSerializableMemoizingSupplier)
+ {
+ return delegate;
+ }
+ return new NonSerializableMemoizingSupplier<>(delegate);
+ }
+
+ @SuppressWarnings("java:S3077")
+ static class NonSerializableMemoizingSupplier implements Supplier
+ {
+ private final Object lock = new Object();
+
+ @SuppressWarnings("UnnecessaryLambda") // Must be a fixed singleton object
+ private static final Supplier SUCCESSFULLY_COMPUTED =
+ () -> {
+ throw new IllegalStateException(); // Should never get called.
+ };
+
+ private volatile Supplier delegate;
+ // "value" does not need to be volatile; visibility piggy-backs on volatile read of "delegate".
+ private T value;
+
+ NonSerializableMemoizingSupplier(final Supplier delegate)
+ {
+ this.delegate = Objects.requireNonNull(delegate);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked") // Cast from Supplier to Supplier is always valid
+ public T get()
+ {
+ // Because Supplier is read-heavy, we use the "double-checked locking" pattern.
+ if(this.delegate != SUCCESSFULLY_COMPUTED)
+ {
+ synchronized(this.lock)
+ {
+ if(this.delegate != SUCCESSFULLY_COMPUTED)
+ {
+ final T t = this.delegate.get();
+ this.value = t;
+ this.delegate = (Supplier)SUCCESSFULLY_COMPUTED;
+ return t;
+ }
+ }
+ }
+ // This is safe because we checked `delegate`.
+ return this.value;
+ }
+
+ @Override
+ public String toString()
+ {
+ final Supplier currentDelegate = this.delegate;
+ return "Suppliers.memoize("
+ + (currentDelegate == SUCCESSFULLY_COMPUTED
+ ? ""
+ : currentDelegate)
+ + ")";
+ }
+ }
+}
diff --git a/selenium/pom.xml b/selenium/pom.xml
index b6c4abc9..98e1c7c5 100644
--- a/selenium/pom.xml
+++ b/selenium/pom.xml
@@ -82,7 +82,7 @@
software.xdev
testcontainers-selenium
- 2.0.0
+ 2.0.1
From b7c66e7a90483e5469d7aef11e9050b466102e4c Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 3 Jul 2026 13:38:45 +0200
Subject: [PATCH 033/141] Fix PMD problems
---
.../java/software/xdev/tci/concurrent/Suppliers.java | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/base/src/main/java/software/xdev/tci/concurrent/Suppliers.java b/base/src/main/java/software/xdev/tci/concurrent/Suppliers.java
index 9c5df503..c1780e63 100644
--- a/base/src/main/java/software/xdev/tci/concurrent/Suppliers.java
+++ b/base/src/main/java/software/xdev/tci/concurrent/Suppliers.java
@@ -16,6 +16,7 @@
package software.xdev.tci.concurrent;
import java.util.Objects;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
@@ -59,7 +60,7 @@ public static Supplier memoize(final Supplier delegate)
@SuppressWarnings("java:S3077")
static class NonSerializableMemoizingSupplier implements Supplier
{
- private final Object lock = new Object();
+ private final ReentrantLock lock = new ReentrantLock();
@SuppressWarnings("UnnecessaryLambda") // Must be a fixed singleton object
private static final Supplier SUCCESSFULLY_COMPUTED =
@@ -67,6 +68,7 @@ static class NonSerializableMemoizingSupplier implements Supplier
throw new IllegalStateException(); // Should never get called.
};
+ @SuppressWarnings("PMD.AvoidUsingVolatile")
private volatile Supplier delegate;
// "value" does not need to be volatile; visibility piggy-backs on volatile read of "delegate".
private T value;
@@ -83,7 +85,8 @@ public T get()
// Because Supplier is read-heavy, we use the "double-checked locking" pattern.
if(this.delegate != SUCCESSFULLY_COMPUTED)
{
- synchronized(this.lock)
+ this.lock.lock();
+ try
{
if(this.delegate != SUCCESSFULLY_COMPUTED)
{
@@ -93,6 +96,10 @@ public T get()
return t;
}
}
+ finally
+ {
+ this.lock.unlock();
+ }
}
// This is safe because we checked `delegate`.
return this.value;
From 88d4b39cf48f286e6402b5c203dd33d07cde972d Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 3 Jul 2026 13:54:24 +0200
Subject: [PATCH 034/141] Cleanup code use memoizing supplier
---
.../tci/db/containers/DBContainerBuilder.java | 34 ++++++-------------
.../tci/demo/tci/db/factory/DBTCIFactory.java | 9 +++--
.../containers/WebAppContainerBuilder.java | 15 +++-----
.../tci/webapp/factory/WebAppTCIFactory.java | 8 ++---
4 files changed, 26 insertions(+), 40 deletions(-)
diff --git a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java
index 00545b95..c8e66909 100644
--- a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java
+++ b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java
@@ -1,8 +1,7 @@
package software.xdev.tci.demo.tci.db.containers;
import java.nio.file.Paths;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+import java.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -17,42 +16,31 @@ public final class DBContainerBuilder
private static final Logger LOG_CONTAINER_BUILD =
LoggerFactory.getLogger("container.build.db");
- private static String builtImageName;
private DBContainerBuilder()
{
}
- public static synchronized String getBuiltImageName()
+ public static synchronized String getImageName()
{
- if(builtImageName != null)
- {
- return builtImageName;
- }
-
LOG.info("Building Webapp-db-DockerImage...");
final AdvancedImageFromDockerFile builder =
new AdvancedImageFromDockerFile("webapp-db", false)
.withLoggerForBuild(LOG_CONTAINER_BUILD)
- .withPostGitIgnoreLines(
- // Ignore everything
- "**")
.withDockerFilePath(Paths.get("../tci-db/Dockerfile"))
.withBaseDir(Paths.get("../"))
- .withBaseDirRelativeIgnoreFile(null);
+ .configureFilesToTransferHandler(h -> h
+ .withPostGitIgnoreLines(
+ // Ignore everything
+ "**")
+ .withBaseDirRelativeIgnoreFile(null)
+ );
- try
- {
- builtImageName = builder.get(5, TimeUnit.MINUTES);
- }
- catch(final TimeoutException tex)
- {
- throw new IllegalStateException("Timed out", tex);
- }
+ final String name = builder.build(Duration.ofMinutes(5));
- LOG.info("Built Image; Name ='{}'", builtImageName);
+ LOG.info("Built Image; Name ='{}'", name);
- return builtImageName;
+ return name;
}
}
diff --git a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java
index c4a0ca85..29a9dc15 100644
--- a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java
+++ b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java
@@ -1,5 +1,8 @@
package software.xdev.tci.demo.tci.db.factory;
+import java.util.function.Supplier;
+
+import software.xdev.tci.concurrent.Suppliers;
import software.xdev.tci.db.factory.BaseDBTCIFactory;
import software.xdev.tci.demo.tci.db.DBTCI;
import software.xdev.tci.demo.tci.db.containers.DBContainer;
@@ -10,6 +13,8 @@
public class DBTCIFactory extends BaseDBTCIFactory
{
+ protected static final Supplier IMAGE_NAME_SUPPLIER = Suppliers.memoize(DBContainerBuilder::getImageName);
+
public DBTCIFactory()
{
this(true);
@@ -20,7 +25,7 @@ public DBTCIFactory(final boolean migrateAndInitializeEMC)
{
super(
(c, n) -> new DBTCI(c, n, migrateAndInitializeEMC),
- () -> new DBContainer(DBContainerBuilder.getBuiltImageName())
+ () -> new DBContainer(DBContainerBuilder.getImageName())
.withDatabaseName(DBTCI.DB_DATABASE)
.withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M)));
this.withSnapshotManager(new CommitedImageSnapshotManager("/var/lib/mysql"));
@@ -29,7 +34,7 @@ public DBTCIFactory(final boolean migrateAndInitializeEMC)
@Override
protected void warmUpInternal()
{
- DBContainerBuilder.getBuiltImageName();
+ DBContainerBuilder.getImageName();
super.warmUpInternal();
}
}
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
index 2cee6c56..bbea26c7 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
@@ -25,19 +25,12 @@ public final class WebAppContainerBuilder
private static final Logger LOG_CONTAINER_BUILD =
LoggerFactory.getLogger("container.build.webapp");
- private static String builtImageName;
-
private WebAppContainerBuilder()
{
}
- public static synchronized String getBuiltImageName(final boolean tagIntermediate)
+ public static String getImageName(final boolean tagIntermediate)
{
- if(builtImageName != null)
- {
- return builtImageName;
- }
-
LOG.info("Building WebApp-DockerImage...");
final AdvancedImageFromDockerFile builder =
@@ -110,9 +103,9 @@ public boolean isIdentical(final List original, final List creat
builder.withBuildArg("JACOCO_AGENT_ENABLED", "1");
}
- builtImageName = builder.build(Duration.ofMinutes(5));
+ final String imageName = builder.build(Duration.ofMinutes(5));
- LOG.info("Built Image; Name ='{}'", builtImageName);
+ LOG.info("Built Image; Name ='{}'", imageName);
if(tagIntermediate)
{
@@ -134,6 +127,6 @@ public boolean isIdentical(final List original, final List creat
TCIExecutorServiceHolder.instance());
}
- return builtImageName;
+ return imageName;
}
}
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
index 39f7cab2..5a36772a 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
@@ -19,12 +19,12 @@ public class WebAppTCIFactory extends PreStartableTCIFactory APP_IMAGE_NAME_SUPPLIER = Suppliers.memoize(() ->
+ protected static final Supplier IMAGE_NAME_SUPPLIER = Suppliers.memoize(() ->
Objects.requireNonNullElseGet(
System.getProperty(PROPERTY_APP_DOCKERIMAGE),
() -> {
String property = System.getProperty(PROPERTY_TAG_INTERMEDIATE);
- return WebAppContainerBuilder.getBuiltImageName(
+ return WebAppContainerBuilder.getImageName(
"1".equals(property) || Boolean.parseBoolean(property));
})
);
@@ -35,7 +35,7 @@ public WebAppTCIFactory(final Consumer additionalContainerBuild
super(
WebAppTCI::new,
() -> {
- final WebAppContainer container = new WebAppContainer(APP_IMAGE_NAME_SUPPLIER.get(), true)
+ final WebAppContainer container = new WebAppContainer(IMAGE_NAME_SUPPLIER.get(), true)
.withDefaultWaitStrategy(
Duration.ofSeconds(40L + 20L * EnvironmentPerformance.cpuSlownessFactor()),
WebAppTCI.ACTUATOR_USERNAME,
@@ -59,7 +59,7 @@ public WebAppTCIFactory(final Consumer additionalContainerBuild
@Override
protected void warmUpInternal()
{
- APP_IMAGE_NAME_SUPPLIER.get();
+ IMAGE_NAME_SUPPLIER.get();
super.warmUpInternal();
}
}
From b3d3b29c761a81ad9fadacb7ad51e041adbd4249 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 3 Jul 2026 13:55:46 +0200
Subject: [PATCH 035/141] DEBUG
---
.github/workflows/run-integration-tests.yml | 167 ++++++++++----------
advanced-demo/pom.xml | 2 +
2 files changed, 85 insertions(+), 84 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 76529549..d6bb3375 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -24,89 +24,89 @@ concurrency:
cancel-in-progress: true
jobs:
- persistence:
- strategy:
- fail-fast: false
- matrix:
- project: [persistence-it-hibernate]
- parallel: [0, 2]
- pre-start: [false, true]
- java: [21]
- include:
- - project: persistence-it-eclipselink
- parallel: 2
- pre-start: true
- java: 21
- name: "persistence (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
- runs-on: ubuntu-latest
- if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
- timeout-minutes: 10
- permissions:
- contents: read
- steps:
- - uses: actions/checkout@v7
-
- - name: Set up JDK
- uses: actions/setup-java@v5
- with:
- distribution: temurin
- java-version: ${{ matrix.java }}
-
- - name: Cache Maven
- uses: actions/cache@v6
- with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-mvn-it-build-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
-
- - name: Restore docker image cache
- uses: actions/cache/restore@v6
- id: restore-docker-image-cache
- with:
- path: ~/.cache/docker/
- key: ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
- ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-
-
- - name: Load docker image cache
- run: |
- echo "Deleting preinstalled images" && docker image ls
- docker image prune -af
-
- cat ~/.cache/docker/images.tar | docker load && echo "Restored docker image cache" && docker image ls || true
-
- - name: Test
- run: |
- ./mvnw -B test \
- -pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
- -P run-it \
- ${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
- ${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
-
- - name: Create docker image cache
- # Cache can't be saved when it's already present
- if: steps.cache.outputs.cache-hit != 'true'
- run: |
- echo "Images before pruning" && docker image ls
- docker image prune -af --filter "until=2160h"
- docker image prune -f
- echo "Currently available images" && docker image ls
-
- echo "Will NOT cache images" && docker image ls -f="label=org.testcontainers.sessionId"
-
- mkdir -p ~/.cache/docker
- image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q -f="label=org.testcontainers.sessionId" | sort))
- echo "Saving image ids" && echo $image_ids_to_save
- docker save $image_ids_to_save -o ~/.cache/docker/images.tar
-
- ls -lha ~/.cache/docker
-
- - name: Save docker image cache
- if: steps.cache.outputs.cache-hit != 'true'
- uses: actions/cache/save@v6
- with:
- path: ~/.cache/docker/
- key: ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ # persistence:
+ # strategy:
+ # fail-fast: false
+ # matrix:
+ # project: [persistence-it-hibernate]
+ # parallel: [0, 2]
+ # pre-start: [false, true]
+ # java: [21]
+ # include:
+ # - project: persistence-it-eclipselink
+ # parallel: 2
+ # pre-start: true
+ # java: 21
+ # name: "persistence (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
+ # runs-on: ubuntu-latest
+ # if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
+ # timeout-minutes: 10
+ # permissions:
+ # contents: read
+ # steps:
+ # - uses: actions/checkout@v7
+
+ # - name: Set up JDK
+ # uses: actions/setup-java@v5
+ # with:
+ # distribution: temurin
+ # java-version: ${{ matrix.java }}
+
+ # - name: Cache Maven
+ # uses: actions/cache@v6
+ # with:
+ # path: ~/.m2/repository
+ # key: ${{ runner.os }}-mvn-it-build-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+
+ # - name: Restore docker image cache
+ # uses: actions/cache/restore@v6
+ # id: restore-docker-image-cache
+ # with:
+ # path: ~/.cache/docker/
+ # key: ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ # restore-keys: |
+ # ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ # ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-
+
+ # - name: Load docker image cache
+ # run: |
+ # echo "Deleting preinstalled images" && docker image ls
+ # docker image prune -af
+
+ # cat ~/.cache/docker/images.tar | docker load && echo "Restored docker image cache" && docker image ls || true
+
+ # - name: Test
+ # run: |
+ # ./mvnw -B test \
+ # -pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
+ # -P run-it \
+ # ${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
+ # ${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
+
+ # - name: Create docker image cache
+ # # Cache can't be saved when it's already present
+ # if: steps.cache.outputs.cache-hit != 'true'
+ # run: |
+ # echo "Images before pruning" && docker image ls
+ # docker image prune -af --filter "until=2160h"
+ # docker image prune -f
+ # echo "Currently available images" && docker image ls
+
+ # echo "Will NOT cache images" && docker image ls -f="label=org.testcontainers.sessionId"
+
+ # mkdir -p ~/.cache/docker
+ # image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q -f="label=org.testcontainers.sessionId" | sort))
+ # echo "Saving image ids" && echo $image_ids_to_save
+ # docker save $image_ids_to_save -o ~/.cache/docker/images.tar
+
+ # ls -lha ~/.cache/docker
+
+ # - name: Save docker image cache
+ # if: steps.cache.outputs.cache-hit != 'true'
+ # uses: actions/cache/save@v6
+ # with:
+ # path: ~/.cache/docker/
+ # key: ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
selenium:
strategy:
@@ -169,7 +169,6 @@ jobs:
run: |
echo "Images before pruning" && docker image ls
docker image prune -af --filter "until=2160h"
- docker image prune -f
echo "Currently available images" && docker image ls
echo "Will NOT cache images" && docker image ls -f="label=org.testcontainers.sessionId"
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index c2d8d72a..d94323f8 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -14,6 +14,8 @@
https://xdev.software
+
+
21
${javaVersion}
From 47975c138f4e69089b0bd8aa46a47530d98fc133 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 3 Jul 2026 13:57:10 +0200
Subject: [PATCH 036/141] DEBUG
---
.github/workflows/run-integration-tests.yml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index d6bb3375..5991ebc4 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -167,14 +167,14 @@ jobs:
# Cache can't be saved when it's already present
if: steps.cache.outputs.cache-hit != 'true'
run: |
- echo "Images before pruning" && docker image ls
+ echo "Images before pruning" && docker image ls -a
docker image prune -af --filter "until=2160h"
- echo "Currently available images" && docker image ls
+ echo "Currently available images" && docker image ls -a
- echo "Will NOT cache images" && docker image ls -f="label=org.testcontainers.sessionId"
+ echo "Will NOT cache images" && docker image ls -a -f="label=org.testcontainers.sessionId"
mkdir -p ~/.cache/docker
- image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q -f="label=org.testcontainers.sessionId" | sort))
+ image_ids_to_save=$(comm -23 <(docker images -a -q | sort) <(docker image ls -a -q -f="label=org.testcontainers.sessionId" | sort))
echo "Saving image ids" && echo $image_ids_to_save
docker save $image_ids_to_save -o ~/.cache/docker/images.tar
From 60408c77c77423826c845e22331284103d32e277 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 3 Jul 2026 13:59:42 +0200
Subject: [PATCH 037/141] DEBUG
---
.github/workflows/run-integration-tests.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 5991ebc4..da8cf417 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -113,8 +113,8 @@ jobs:
fail-fast: false
matrix:
project: [webapp-it]
- parallel: [0, 2]
- pre-start: [false, true]
+ parallel: [2] # [0, 2]
+ pre-start: [true] # [false, true]
java: [21]
name: "selenium (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
runs-on: ubuntu-latest
From 1dad77099d8efc951e3eba91859abc670be81ed4 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 3 Jul 2026 14:01:00 +0200
Subject: [PATCH 038/141] DEBUG
---
.../java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java | 2 ++
.../tci/demo/webapp/cases/LongButLittleResourceUsageTest.java | 2 ++
2 files changed, 4 insertions(+)
diff --git a/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java
index 73487d3a..a44d709e 100644
--- a/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java
+++ b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java
@@ -1,6 +1,7 @@
package software.xdev.tci.demo.webapp.cases;
import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
@@ -9,6 +10,7 @@
import software.xdev.tci.selenium.TestBrowser;
+@Disabled // TODO
class LoginOIDCTest extends InfraPerCaseTest
{
@DisplayName("Check Login and Logout")
diff --git a/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java
index 29614c07..6db3e816 100644
--- a/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java
+++ b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java
@@ -3,6 +3,7 @@
import java.util.stream.IntStream;
import java.util.stream.Stream;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -13,6 +14,7 @@
// These tests were written to show the advantages of PreStarting
+@Disabled // TODO
class LongButLittleResourceUsageTest extends InfraPerCaseTest
{
static Stream simulatedLongTest()
From 748b66ee8291b44c3e496fa7074b2c804ba37edc Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 3 Jul 2026 14:08:49 +0200
Subject: [PATCH 039/141] Update run-integration-tests.yml
---
.github/workflows/run-integration-tests.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index da8cf417..0c2d8a65 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -169,6 +169,7 @@ jobs:
run: |
echo "Images before pruning" && docker image ls -a
docker image prune -af --filter "until=2160h"
+ # docker image prune -f
echo "Currently available images" && docker image ls -a
echo "Will NOT cache images" && docker image ls -a -f="label=org.testcontainers.sessionId"
From 35ac26d228e24ccb4a6dd3fb1c6ee0f260a2b179 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 3 Jul 2026 14:23:56 +0200
Subject: [PATCH 040/141] Correctly get image name
---
.../xdev/tci/demo/tci/db/containers/DBContainerBuilder.java | 2 +-
.../software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java
index c8e66909..6751fb54 100644
--- a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java
+++ b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java
@@ -21,7 +21,7 @@ private DBContainerBuilder()
{
}
- public static synchronized String getImageName()
+ public static String getImageName()
{
LOG.info("Building Webapp-db-DockerImage...");
diff --git a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java
index 29a9dc15..7b285276 100644
--- a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java
+++ b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java
@@ -25,7 +25,7 @@ public DBTCIFactory(final boolean migrateAndInitializeEMC)
{
super(
(c, n) -> new DBTCI(c, n, migrateAndInitializeEMC),
- () -> new DBContainer(DBContainerBuilder.getImageName())
+ () -> new DBContainer(IMAGE_NAME_SUPPLIER.get())
.withDatabaseName(DBTCI.DB_DATABASE)
.withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M)));
this.withSnapshotManager(new CommitedImageSnapshotManager("/var/lib/mysql"));
@@ -34,7 +34,7 @@ public DBTCIFactory(final boolean migrateAndInitializeEMC)
@Override
protected void warmUpInternal()
{
- DBContainerBuilder.getImageName();
+ IMAGE_NAME_SUPPLIER.get();
super.warmUpInternal();
}
}
From 581e19c6ee1d1af90ab235d8cb414ae4df9b776d Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 3 Jul 2026 14:29:26 +0200
Subject: [PATCH 041/141] DEBUG
---
.github/workflows/run-integration-tests.yml | 10 +++++-----
.../tci/webapp/containers/WebAppContainerBuilder.java | 7 +++++--
advanced-demo/pom.xml | 2 +-
3 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 0c2d8a65..9d23ee9e 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -167,15 +167,15 @@ jobs:
# Cache can't be saved when it's already present
if: steps.cache.outputs.cache-hit != 'true'
run: |
- echo "Images before pruning" && docker image ls -a
+ echo "Images before pruning" && docker image ls
docker image prune -af --filter "until=2160h"
- # docker image prune -f
- echo "Currently available images" && docker image ls -a
+ docker image prune -f
+ echo "Currently available images" && docker image ls
- echo "Will NOT cache images" && docker image ls -a -f="label=org.testcontainers.sessionId"
+ echo "Will NOT cache images" && docker image ls -f="label=org.testcontainers.sessionId"
mkdir -p ~/.cache/docker
- image_ids_to_save=$(comm -23 <(docker images -a -q | sort) <(docker image ls -a -q -f="label=org.testcontainers.sessionId" | sort))
+ image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q -f="label=org.testcontainers.sessionId" | sort))
echo "Saving image ids" && echo $image_ids_to_save
docker save $image_ids_to_save -o ~/.cache/docker/images.tar
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
index bbea26c7..910b6d58 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
@@ -6,6 +6,7 @@
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import java.util.stream.Stream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.slf4j.Logger;
@@ -113,8 +114,10 @@ public boolean isIdentical(final List original, final List creat
() -> {
try
{
- builder.copyForIntermediateTag("webapp-it-local-builder", "builder")
- .build(Duration.ofMinutes(1));
+ Stream.of("jre-base", "jre-minimized", "builder")
+ .forEach(target -> builder.copyForIntermediateTag(target)
+ .build(Duration.ofMinutes(1)));
+
builder.cleanCreatedTransferFilesCache();
LOG.info("Tagged intermediate image");
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index d94323f8..2007e98a 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -14,7 +14,7 @@
https://xdev.software
-
+
21
From e035673ccafc5bc0966852c2f7f37c55105e414c Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 3 Jul 2026 14:30:47 +0200
Subject: [PATCH 042/141] Silence preinstall image prune
---
.github/workflows/run-integration-tests.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 9d23ee9e..f0172db3 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -150,7 +150,7 @@ jobs:
- name: Load docker image cache
run: |
echo "Deleting preinstalled images" && docker image ls
- docker image prune -af
+ docker image prune -af > /dev/null
cat ~/.cache/docker/images.tar | docker load && echo "Restored docker image cache" && docker image ls || true
From 1bc8bb1c07654ab9ce76aabae7262014c5dea15a Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 8 Jul 2026 14:31:48 +0200
Subject: [PATCH 043/141] Try to use new caching strategy
---
.github/workflows/run-integration-tests.yml | 6 ++-
advanced-demo/integration-tests/pom.xml | 2 +-
.../containers/WebAppContainerBuilder.java | 45 ++++++-------------
.../tci/webapp/factory/WebAppTCIFactory.java | 12 +----
4 files changed, 20 insertions(+), 45 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index f0172db3..a99ae406 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -152,14 +152,16 @@ jobs:
echo "Deleting preinstalled images" && docker image ls
docker image prune -af > /dev/null
- cat ~/.cache/docker/images.tar | docker load && echo "Restored docker image cache" && docker image ls || true
+ docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
- name: Test
run: |
./mvnw -B test \
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
-P run-it,jacoco \
- -DtagIntermediate=1 \
+ -Dapp-docker-image.delete-on-exit=true \
+ -Dapp-docker-image.cache-from="type=gha,scope=webapp-gha-1" \
+ -Dapp-docker-image.cache-to="type=gha,mode=max,scope=webapp-gha-1" \
${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
diff --git a/advanced-demo/integration-tests/pom.xml b/advanced-demo/integration-tests/pom.xml
index cea820dc..c38fc0bc 100644
--- a/advanced-demo/integration-tests/pom.xml
+++ b/advanced-demo/integration-tests/pom.xml
@@ -118,7 +118,7 @@
software.xdev
testcontainers-advanced-imagebuilder
- 3.0.1
+ 4.0.0
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
index 910b6d58..4598c8f3 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
@@ -1,20 +1,17 @@
package software.xdev.tci.demo.tci.webapp.containers;
-import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.stream.Stream;
+import java.util.Optional;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import software.xdev.tci.concurrent.TCIExecutorServiceHolder;
import software.xdev.tci.jacoco.testbase.config.JaCoCoConfig;
-import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
+import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
import software.xdev.testcontainers.imagebuilder.compat.DockerfileCOPYParentsEmulator;
import software.xdev.testcontainers.imagebuilder.transfer.fcm.FileLinesContentModifier;
@@ -26,20 +23,21 @@ public final class WebAppContainerBuilder
private static final Logger LOG_CONTAINER_BUILD =
LoggerFactory.getLogger("container.build.webapp");
+ public static final String PROPERTY = "app-docker-image";
+
private WebAppContainerBuilder()
{
}
- public static String getImageName(final boolean tagIntermediate)
+ public static String getImageName()
{
LOG.info("Building WebApp-DockerImage...");
- final AdvancedImageFromDockerFile builder =
- new AdvancedImageFromDockerFile("webapp-it-local", false)
+ final NativeAdvancedImageFromDockerfile builder =
+ new NativeAdvancedImageFromDockerfile("webapp-it-local", Boolean.getBoolean(PROPERTY + ".delete-on-exit"))
.withLoggerForBuild(LOG_CONTAINER_BUILD)
.withDockerFilePath(Paths.get("../../integration-tests/tci-webapp/Dockerfile"))
.withBaseDir(Paths.get("../../"))
- .withCreateTransferFilesCache(tagIntermediate)
.configureFilesToTransferHandler(h -> h
.withPostGitIgnoreLines(
// Ignore git-folder, as it will be provided in the Dockerfile
@@ -83,7 +81,7 @@ public List modify(
final List lines,
final Path sourcePath,
final String targetPath,
- final TarArchiveEntry tarArchiveEntry) throws IOException
+ final TarArchiveEntry tarArchiveEntry)
{
return lines.stream()
// Remove integration tests module
@@ -99,6 +97,11 @@ public boolean isIdentical(final List original, final List creat
}))
);
+ Optional.ofNullable(System.getProperty(PROPERTY + ".cache-from"))
+ .ifPresent(builder::withCacheFrom);
+ Optional.ofNullable(System.getProperty(PROPERTY + ".cache-to"))
+ .ifPresent(builder::withCacheTo);
+
if(JaCoCoConfig.instance().enabled())
{
builder.withBuildArg("JACOCO_AGENT_ENABLED", "1");
@@ -108,28 +111,6 @@ public boolean isIdentical(final List original, final List creat
LOG.info("Built Image; Name ='{}'", imageName);
- if(tagIntermediate)
- {
- CompletableFuture.runAsync(
- () -> {
- try
- {
- Stream.of("jre-base", "jre-minimized", "builder")
- .forEach(target -> builder.copyForIntermediateTag(target)
- .build(Duration.ofMinutes(1)));
-
- builder.cleanCreatedTransferFilesCache();
-
- LOG.info("Tagged intermediate image");
- }
- catch(final Exception ex)
- {
- LOG.warn("Tagging intermediate target failed", ex);
- }
- },
- TCIExecutorServiceHolder.instance());
- }
-
return imageName;
}
}
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
index 5a36772a..0cc5ef65 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
@@ -16,18 +16,10 @@
public class WebAppTCIFactory extends PreStartableTCIFactory
{
- public static final String PROPERTY_APP_DOCKERIMAGE = "appDockerImage";
- public static final String PROPERTY_TAG_INTERMEDIATE = "tagIntermediate";
-
protected static final Supplier IMAGE_NAME_SUPPLIER = Suppliers.memoize(() ->
Objects.requireNonNullElseGet(
- System.getProperty(PROPERTY_APP_DOCKERIMAGE),
- () -> {
- String property = System.getProperty(PROPERTY_TAG_INTERMEDIATE);
- return WebAppContainerBuilder.getImageName(
- "1".equals(property) || Boolean.parseBoolean(property));
- })
- );
+ System.getProperty(WebAppContainerBuilder.PROPERTY),
+ WebAppContainerBuilder::getImageName));
@SuppressWarnings("checkstyle:MagicNumber")
public WebAppTCIFactory(final Consumer additionalContainerBuilder)
From 7c6ca7c8f7c9ba75fbe14d04633c3edfcfdc3493 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 09:09:25 +0200
Subject: [PATCH 044/141] Add JavaCoCo config docs
---
jacoco/README.md | 29 ++++++++++++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/jacoco/README.md b/jacoco/README.md
index 61c1b198..def6f75d 100644
--- a/jacoco/README.md
+++ b/jacoco/README.md
@@ -7,6 +7,33 @@ Provides support for [JaCoCo](https://github.com/jacoco/jacoco) Code Coverage in
NOTE: Using this is a bit complex as it also requires some changes to the java application in the container.
Please have a look at the [advanced-demo](../advanced-demo/) and the corresponding [GitHub Actions workflow](../.github/workflows/run-integration-tests.yml) for details.
-### Example Report
+## Config
+
+The configuration is dynamically loaded from (sorted by highest priority)
+
+* Environment variables
+ * prefixed with ``TCI_JACOCO_``
+ * all properties are in UPPERCASE and use `_` instead of `.` or `-`
+* System properties
+ * prefixed with ``tci.jacoco.``
+
+
+
+Full list of configuration options
+
+_Please note that the preconfigured values usually work out of the box.
_
+_You should know exactly what you're doing when doing modifications._
+
+| Property | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `enabled` | `bool` | `false` | You should probably set this to `true` |
+| `execution-data-files-dir` | `string` | `target/jacoco-execution-data-files` | Location where the jacoco execution reports/execution data files are stored |
+| `move-old-execution-data-files-dir` | `bool` | `true` | If `execution-data-files-dir` already exists it will be moved to `execution-data-files-dir-old` |
+| `execution-data-files-dir-old` | `string` | `target/jacoco-execution-data-files-old` | Location where previous jacoco execution reports/execution data files are moved to. If the directory already exists it will be deleted. |
+| `execution-data-file-suffix` | `string` | `-jacoco.exec` | Suffix for the jacoco reports/execution data files |
+
+
+
+## Example Report


From 596a089876a301482116ecdbe727bc7d6b2695c4 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 10:10:50 +0200
Subject: [PATCH 045/141] Add build caching
---
.github/workflows/run-integration-tests.yml | 21 +-
advanced-demo/integration-tests/pom.xml | 12 +-
.../integration-tests/tci-db/pom.xml | 9 +-
.../tci/db/containers/DBContainerBuilder.java | 27 +-
.../integration-tests/tci-webapp/pom.xml | 6 +-
.../containers/WebAppContainerBuilder.java | 16 +-
.../tci/webapp/factory/WebAppTCIFactory.java | 7 +-
bom/pom.xml | 5 +
image-build/README.md | 31 ++
image-build/pom.xml | 311 ++++++++++++++++++
.../tci/imagebuild/CreateImageHandler.java | 12 +
.../imagebuild/DefaultCreateImageHandler.java | 72 ++++
.../xdev/tci/imagebuild/ImageCreator.java | 28 ++
.../config/CreateImageHandlerConfig.java | 22 ++
.../DefaultCreateImageHandlerConfig.java | 62 ++++
...are.xdev.tci.imagebuild.CreateImageHandler | 1 +
...imagebuild.config.CreateImageHandlerConfig | 1 +
pom.xml | 1 +
18 files changed, 591 insertions(+), 53 deletions(-)
create mode 100644 image-build/README.md
create mode 100644 image-build/pom.xml
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/CreateImageHandler.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/DefaultCreateImageHandler.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/ImageCreator.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/config/CreateImageHandlerConfig.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultCreateImageHandlerConfig.java
create mode 100644 image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.CreateImageHandler
create mode 100644 image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.config.CreateImageHandlerConfig
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index a99ae406..d5270144 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -154,16 +154,31 @@ jobs:
docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
+ # These are required for caching
+ # https://docs.docker.com/build/cache/backends/gha/#authentication
+ - name: Get Actions Runtime credentials
+ id: actions-vars
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ core.setOutput('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN'])
+ core.setOutput('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL'])
+
+ - uses: docker/setup-buildx-action@v4
+
- name: Test
run: |
./mvnw -B test \
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
-P run-it,jacoco \
- -Dapp-docker-image.delete-on-exit=true \
- -Dapp-docker-image.cache-from="type=gha,scope=webapp-gha-1" \
- -Dapp-docker-image.cache-to="type=gha,mode=max,scope=webapp-gha-1" \
+ -Dtci.image-build.delete-on-exit=true \
+ -Dtci.image-build.cache-from="type=gha,scope=§image" \
+ -Dtci.image-build.cache-to="type=gha,mode=max,scope=§image" \
${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
+ env:
+ ACTIONS_RUNTIME_TOKEN: ${{ steps.actions-vars.outputs.ACTIONS_RUNTIME_TOKEN }}
+ ACTIONS_RESULTS_URL: ${{ steps.actions-vars.outputs.ACTIONS_RESULTS_URL }}
- name: Create docker image cache
# Cache can't be saved when it's already present
diff --git a/advanced-demo/integration-tests/pom.xml b/advanced-demo/integration-tests/pom.xml
index c38fc0bc..f3278c28 100644
--- a/advanced-demo/integration-tests/pom.xml
+++ b/advanced-demo/integration-tests/pom.xml
@@ -67,6 +67,11 @@
db-jdbc-spring-orm-hibernate
${project.version}
+
+ software.xdev.tci
+ image-build
+ ${project.version}
+
software.xdev.tci
jacoco
@@ -114,13 +119,6 @@
import
-
-
- software.xdev
- testcontainers-advanced-imagebuilder
- 4.0.0
-
-
org.javassist
diff --git a/advanced-demo/integration-tests/tci-db/pom.xml b/advanced-demo/integration-tests/tci-db/pom.xml
index 8de104dc..a48ccef7 100644
--- a/advanced-demo/integration-tests/tci-db/pom.xml
+++ b/advanced-demo/integration-tests/tci-db/pom.xml
@@ -21,6 +21,10 @@
software.xdev.tci
db-jdbc-spring-orm-hibernate
+
+ software.xdev.tci
+ image-build
+
@@ -34,10 +38,5 @@
testcontainers-jdbc
compile
-
-
- software.xdev
- testcontainers-advanced-imagebuilder
-
diff --git a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java
index 6751fb54..e05bce43 100644
--- a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java
+++ b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java
@@ -6,16 +6,13 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
+import software.xdev.tci.imagebuild.ImageCreator;
+import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
-@SuppressWarnings("PMD.MoreThanOneLogger")
public final class DBContainerBuilder
{
private static final Logger LOG = LoggerFactory.getLogger(DBContainerBuilder.class);
- private static final Logger LOG_CONTAINER_BUILD =
- LoggerFactory.getLogger("container.build.db");
-
private DBContainerBuilder()
{
@@ -25,17 +22,15 @@ public static String getImageName()
{
LOG.info("Building Webapp-db-DockerImage...");
- final AdvancedImageFromDockerFile builder =
- new AdvancedImageFromDockerFile("webapp-db", false)
- .withLoggerForBuild(LOG_CONTAINER_BUILD)
- .withDockerFilePath(Paths.get("../tci-db/Dockerfile"))
- .withBaseDir(Paths.get("../"))
- .configureFilesToTransferHandler(h -> h
- .withPostGitIgnoreLines(
- // Ignore everything
- "**")
- .withBaseDirRelativeIgnoreFile(null)
- );
+ final NativeAdvancedImageFromDockerfile builder = ImageCreator.nativeImage("tci-demo-db")
+ .withDockerFilePath(Paths.get("../tci-db/Dockerfile"))
+ .withBaseDir(Paths.get("../"))
+ .configureFilesToTransferHandler(h -> h
+ .withPostGitIgnoreLines(
+ // Ignore everything
+ "**")
+ .withBaseDirRelativeIgnoreFile(null)
+ );
final String name = builder.build(Duration.ofMinutes(5));
diff --git a/advanced-demo/integration-tests/tci-webapp/pom.xml b/advanced-demo/integration-tests/tci-webapp/pom.xml
index 39fa8d66..e745257f 100644
--- a/advanced-demo/integration-tests/tci-webapp/pom.xml
+++ b/advanced-demo/integration-tests/tci-webapp/pom.xml
@@ -18,11 +18,11 @@
software.xdev.tci
- jacoco
+ image-build
- software.xdev
- testcontainers-advanced-imagebuilder
+ software.xdev.tci
+ jacoco
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
index 4598c8f3..541d5ca8 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
@@ -4,26 +4,21 @@
import java.nio.file.Paths;
import java.time.Duration;
import java.util.List;
-import java.util.Optional;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import software.xdev.tci.imagebuild.ImageCreator;
import software.xdev.tci.jacoco.testbase.config.JaCoCoConfig;
import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
import software.xdev.testcontainers.imagebuilder.compat.DockerfileCOPYParentsEmulator;
import software.xdev.testcontainers.imagebuilder.transfer.fcm.FileLinesContentModifier;
-@SuppressWarnings("PMD.MoreThanOneLogger")
public final class WebAppContainerBuilder
{
private static final Logger LOG = LoggerFactory.getLogger(WebAppContainerBuilder.class);
- private static final Logger LOG_CONTAINER_BUILD =
- LoggerFactory.getLogger("container.build.webapp");
-
- public static final String PROPERTY = "app-docker-image";
private WebAppContainerBuilder()
{
@@ -33,9 +28,7 @@ public static String getImageName()
{
LOG.info("Building WebApp-DockerImage...");
- final NativeAdvancedImageFromDockerfile builder =
- new NativeAdvancedImageFromDockerfile("webapp-it-local", Boolean.getBoolean(PROPERTY + ".delete-on-exit"))
- .withLoggerForBuild(LOG_CONTAINER_BUILD)
+ final NativeAdvancedImageFromDockerfile builder = ImageCreator.nativeImage("tci-demo-webapp")
.withDockerFilePath(Paths.get("../../integration-tests/tci-webapp/Dockerfile"))
.withBaseDir(Paths.get("../../"))
.configureFilesToTransferHandler(h -> h
@@ -97,11 +90,6 @@ public boolean isIdentical(final List original, final List creat
}))
);
- Optional.ofNullable(System.getProperty(PROPERTY + ".cache-from"))
- .ifPresent(builder::withCacheFrom);
- Optional.ofNullable(System.getProperty(PROPERTY + ".cache-to"))
- .ifPresent(builder::withCacheTo);
-
if(JaCoCoConfig.instance().enabled())
{
builder.withBuildArg("JACOCO_AGENT_ENABLED", "1");
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
index 0cc5ef65..f9ea42fd 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
@@ -1,7 +1,6 @@
package software.xdev.tci.demo.tci.webapp.factory;
import java.time.Duration;
-import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -16,10 +15,8 @@
public class WebAppTCIFactory extends PreStartableTCIFactory
{
- protected static final Supplier IMAGE_NAME_SUPPLIER = Suppliers.memoize(() ->
- Objects.requireNonNullElseGet(
- System.getProperty(WebAppContainerBuilder.PROPERTY),
- WebAppContainerBuilder::getImageName));
+ protected static final Supplier IMAGE_NAME_SUPPLIER =
+ Suppliers.memoize(WebAppContainerBuilder::getImageName);
@SuppressWarnings("checkstyle:MagicNumber")
public WebAppTCIFactory(final Consumer additionalContainerBuilder)
diff --git a/bom/pom.xml b/bom/pom.xml
index 922ea4fd..fa5a77d4 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -73,6 +73,11 @@
db-jdbc-spring-orm-hibernate
3.5.0-SNAPSHOT
+
+ software.xdev.tci
+ image-build
+ 3.5.0-SNAPSHOT
+
software.xdev.tci
jacoco
diff --git a/image-build/README.md b/image-build/README.md
new file mode 100644
index 00000000..1d81d786
--- /dev/null
+++ b/image-build/README.md
@@ -0,0 +1,31 @@
+# Image-Build
+
+Provides common utility and configuration to build images.
+
+## Config
+
+The configuration is dynamically loaded from (sorted by highest priority)
+
+* Environment variables
+ * prefixed with ``TCI_IMAGE-BUILD_``
+ * all properties are in UPPERCASE and use `_` instead of `.` or `-`
+* System properties
+ * prefixed with ``tci.image-build.``
+
+
+
+Full list of configuration options
+
+_Please note that the preconfigured values usually work out of the box.
_
+_You should know exactly what you're doing when doing modifications._
+
+| Property | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `delete-on-exit` | `bool` | `false` | Should the image be deleted on exit? |
+| `logger-for-build-prefix` | `string` | `container.build.` | Prefix used for the build logger |
+| `cache-from` | `string` | - | Only applies to BuildKit (native) build.
See [Docker docs](https://docs.docker.com/build/cache/backends/) for details. |
+| `cache-to` | `string` | - | Only applies to BuildKit (native) build.
See [Docker docs](https://docs.docker.com/build/cache/backends/) for details. |
+
+
+
+
diff --git a/image-build/pom.xml b/image-build/pom.xml
new file mode 100644
index 00000000..8ab64c14
--- /dev/null
+++ b/image-build/pom.xml
@@ -0,0 +1,311 @@
+
+
+ 4.0.0
+
+ software.xdev.tci
+ image-build
+ 3.5.0-SNAPSHOT
+ jar
+
+ image-build
+ TCI - image-build
+ https://github.com/xdev-software/tci
+
+
+ https://github.com/xdev-software/tci
+ scm:git:https://github.com/xdev-software/tci.git
+
+
+ 2025
+
+
+ XDEV Software
+ https://xdev.software
+
+
+
+
+ XDEV Software
+ XDEV Software
+ https://xdev.software
+
+
+
+
+
+ Apache-2.0
+ https://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ 21
+ ${javaVersion}
+
+ UTF-8
+ UTF-8
+
+
+
+
+ software.xdev.tci
+ base
+ 3.5.0-SNAPSHOT
+
+
+ software.xdev
+ testcontainers-advanced-imagebuilder
+ 4.0.0
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-site-plugin
+ 4.0.0-M16
+
+
+ org.apache.maven.plugins
+ maven-project-info-reports-plugin
+ 3.9.0
+
+
+
+
+
+ com.mycila
+ license-maven-plugin
+ 5.0.0
+
+
+ ${project.organization.url}
+
+
+
+ com/mycila/maven/plugin/license/templates/APACHE-2.txt
+
+ src/main/java/**
+ src/test/java/**
+
+
+
+
+
+
+ first
+
+ format
+
+ process-sources
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.15.0
+
+ ${maven.compiler.release}
+
+ -proc:none
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.12.0
+
+
+ attach-javadocs
+ package
+
+ jar
+
+
+
+
+ true
+ none
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.4.0
+
+
+ attach-sources
+ package
+
+ jar-no-fork
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.6
+
+
+
+
+
+ ignore-service-loading
+
+
+
+ src/main/resources
+
+ META-INF/services/**
+
+
+
+
+
+
+ publish
+
+
+
+ org.codehaus.mojo
+ flatten-maven-plugin
+ 1.7.3
+
+ ossrh
+
+
+
+ flatten
+ process-resources
+
+ flatten
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 3.2.8
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+ --pinentry-mode
+ loopback
+
+
+
+
+
+
+
+
+
+ publish-sonatype-central-portal
+
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.11.0
+ true
+
+ sonatype-central-portal
+ true
+
+
+
+
+
+
+ checkstyle
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.6.0
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 13.7.0
+
+
+
+ ../.config/checkstyle/checkstyle.xml
+ true
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+ pmd
+
+
+
+ org.apache.maven.plugins
+ maven-pmd-plugin
+ 3.28.0
+
+ true
+ true
+ true
+
+ ../.config/pmd/java/ruleset.xml
+
+
+
+
+ net.sourceforge.pmd
+ pmd-core
+ 7.26.0
+
+
+ net.sourceforge.pmd
+ pmd-java
+ 7.26.0
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jxr-plugin
+ 3.6.0
+
+
+
+
+
+
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/CreateImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/CreateImageHandler.java
new file mode 100644
index 00000000..5056a293
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/CreateImageHandler.java
@@ -0,0 +1,12 @@
+package software.xdev.tci.imagebuild;
+
+import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
+import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
+
+
+public interface CreateImageHandler
+{
+ NativeAdvancedImageFromDockerfile nativeImage(String dockerImage);
+
+ AdvancedImageFromDockerFile image(String dockerImage);
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultCreateImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultCreateImageHandler.java
new file mode 100644
index 00000000..939b6340
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultCreateImageHandler.java
@@ -0,0 +1,72 @@
+package software.xdev.tci.imagebuild;
+
+import java.util.regex.Pattern;
+
+import org.slf4j.LoggerFactory;
+
+import software.xdev.tci.imagebuild.config.CreateImageHandlerConfig;
+import software.xdev.testcontainers.imagebuilder.AbstractImageFromDockerfile;
+import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
+import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
+
+
+public class DefaultCreateImageHandler implements CreateImageHandler
+{
+ protected static final Pattern DOCKER_IMAGE_SANITIZATION_PATTERN =
+ Pattern.compile("[^A-Za-z0-9-_]");
+
+ protected final CreateImageHandlerConfig config;
+
+ public DefaultCreateImageHandler()
+ {
+ this(CreateImageHandlerConfig.instance());
+ }
+
+ public DefaultCreateImageHandler(final CreateImageHandlerConfig config)
+ {
+ this.config = config;
+ }
+
+ @Override
+ public NativeAdvancedImageFromDockerfile nativeImage(final String dockerImage)
+ {
+ final String sanitizeDockerImageName = this.sanitizeDockerImageName(dockerImage);
+ final NativeAdvancedImageFromDockerfile builder =
+ new NativeAdvancedImageFromDockerfile(dockerImage, this.config.deleteOnExit());
+
+ this.configureAbstract(builder, sanitizeDockerImageName);
+
+ this.config.cacheFrom()
+ .map(s -> s.replace("$image", sanitizeDockerImageName))
+ .map(s -> s.replace("§image", sanitizeDockerImageName))
+ .ifPresent(builder::withCacheFrom);
+ this.config.cacheTo()
+ .map(s -> s.replace("$image", sanitizeDockerImageName))
+ .map(s -> s.replace("§image", sanitizeDockerImageName))
+ .ifPresent(builder::withCacheTo);
+
+ return builder;
+ }
+
+ @Override
+ public AdvancedImageFromDockerFile image(final String dockerImage)
+ {
+ final String sanitizeDockerImageName = this.sanitizeDockerImageName(dockerImage);
+ final AdvancedImageFromDockerFile builder =
+ new AdvancedImageFromDockerFile(dockerImage, this.config.deleteOnExit());
+
+ return this.configureAbstract(builder, sanitizeDockerImageName);
+ }
+
+ protected > I configureAbstract(
+ final I builder, final String sanitizeDockerImageName)
+ {
+ return builder.withLoggerForBuild(
+ LoggerFactory.getLogger(this.config.loggerForBuildPrefix() + sanitizeDockerImageName));
+ }
+
+ protected String sanitizeDockerImageName(final String dockerImage)
+ {
+ return DOCKER_IMAGE_SANITIZATION_PATTERN.matcher(dockerImage).replaceAll("_");
+ }
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/ImageCreator.java b/image-build/src/main/java/software/xdev/tci/imagebuild/ImageCreator.java
new file mode 100644
index 00000000..4be898b5
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/ImageCreator.java
@@ -0,0 +1,28 @@
+package software.xdev.tci.imagebuild;
+
+import software.xdev.tci.serviceloading.TCIServiceLoaderHolder;
+import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
+import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
+
+
+public final class ImageCreator
+{
+ public static NativeAdvancedImageFromDockerfile nativeImage(final String dockerImage)
+ {
+ return impl().nativeImage(dockerImage);
+ }
+
+ public static AdvancedImageFromDockerFile image(final String dockerImage)
+ {
+ return impl().image(dockerImage);
+ }
+
+ public static CreateImageHandler impl()
+ {
+ return TCIServiceLoaderHolder.instance().service(CreateImageHandler.class);
+ }
+
+ private ImageCreator()
+ {
+ }
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/config/CreateImageHandlerConfig.java b/image-build/src/main/java/software/xdev/tci/imagebuild/config/CreateImageHandlerConfig.java
new file mode 100644
index 00000000..bfa48e15
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/config/CreateImageHandlerConfig.java
@@ -0,0 +1,22 @@
+package software.xdev.tci.imagebuild.config;
+
+import java.util.Optional;
+
+import software.xdev.tci.serviceloading.TCIServiceLoaderHolder;
+
+
+public interface CreateImageHandlerConfig
+{
+ boolean deleteOnExit();
+
+ String loggerForBuildPrefix();
+
+ Optional cacheFrom();
+
+ Optional cacheTo();
+
+ static CreateImageHandlerConfig instance()
+ {
+ return TCIServiceLoaderHolder.instance().service(CreateImageHandlerConfig.class);
+ }
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultCreateImageHandlerConfig.java b/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultCreateImageHandlerConfig.java
new file mode 100644
index 00000000..d40b1f6e
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultCreateImageHandlerConfig.java
@@ -0,0 +1,62 @@
+package software.xdev.tci.imagebuild.config;
+
+import java.util.Optional;
+
+import software.xdev.tci.config.DefaultConfig;
+
+
+@SuppressWarnings({"java:S2789", "OptionalAssignedToNull", "OptionalUsedAsFieldOrParameterType"})
+public class DefaultCreateImageHandlerConfig extends DefaultConfig implements CreateImageHandlerConfig
+{
+ protected Boolean deleteOnExit;
+ protected String loggerForBuildPrefix;
+ protected Optional cacheFrom;
+ protected Optional cacheTo;
+
+ @Override
+ protected String propertyNamePrefix()
+ {
+ return "tci.image-build";
+ }
+
+ @Override
+ public boolean deleteOnExit()
+ {
+ if(this.deleteOnExit == null)
+ {
+ this.deleteOnExit = this.resolveBool("delete-on-exit", false);
+ }
+ return this.deleteOnExit;
+ }
+
+ @Override
+ public String loggerForBuildPrefix()
+ {
+ if(this.loggerForBuildPrefix == null)
+ {
+ this.loggerForBuildPrefix = this.resolve("logger-for-build-prefix")
+ .orElse("container.build.");
+ }
+ return this.loggerForBuildPrefix;
+ }
+
+ @Override
+ public Optional cacheFrom()
+ {
+ if(this.cacheFrom == null)
+ {
+ this.cacheFrom = this.resolve("cache-from");
+ }
+ return this.cacheFrom;
+ }
+
+ @Override
+ public Optional cacheTo()
+ {
+ if(this.cacheTo == null)
+ {
+ this.cacheTo = this.resolve("cache-to");
+ }
+ return this.cacheTo;
+ }
+}
diff --git a/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.CreateImageHandler b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.CreateImageHandler
new file mode 100644
index 00000000..f76ede0c
--- /dev/null
+++ b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.CreateImageHandler
@@ -0,0 +1 @@
+software.xdev.tci.imagebuild.DefaultCreateImageHandler
diff --git a/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.config.CreateImageHandlerConfig b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.config.CreateImageHandlerConfig
new file mode 100644
index 00000000..9360b4bb
--- /dev/null
+++ b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.config.CreateImageHandlerConfig
@@ -0,0 +1 @@
+software.xdev.tci.imagebuild.config.DefaultCreateImageHandlerConfig
diff --git a/pom.xml b/pom.xml
index aa90e987..7525bc1f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,6 +21,7 @@
db-jdbc-spring-orm
db-jdbc-spring-orm-eclipselink
db-jdbc-spring-orm-hibernate
+ image-build
jacoco
jul-to-slf4j
junit-jupiter-api-support
From ebc77887fd7af55995754a4858abcc94561b5d43 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 10:49:49 +0200
Subject: [PATCH 046/141] Abstract code
---
.../docker-image-cache/save/action.yml | 38 ++++++++++++
.../docker-image-cache/setup/action.yml | 56 ++++++++++++++++++
.github/workflows/run-integration-tests.yml | 59 ++++---------------
3 files changed, 104 insertions(+), 49 deletions(-)
create mode 100644 .github/actions/docker-image-cache/save/action.yml
create mode 100644 .github/actions/docker-image-cache/setup/action.yml
diff --git a/.github/actions/docker-image-cache/save/action.yml b/.github/actions/docker-image-cache/save/action.yml
new file mode 100644
index 00000000..b2e168a1
--- /dev/null
+++ b/.github/actions/docker-image-cache/save/action.yml
@@ -0,0 +1,38 @@
+name: 'Save docker image cache'
+description: 'Save the docker image cache'
+# Based on https://github.com/actions/cache/blob/v6/restore/action.yml
+inputs:
+ key:
+ description: 'An explicit key for restoring the cache'
+ required: true
+ cache-hit:
+ description: 'A boolean value to indicate an exact match was found for the primary key'
+ required: false
+runs:
+ using: "composite"
+ steps:
+ - name: Create docker image cache
+ # Cache can't be saved when it's already present
+ if: inputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ echo "Images before pruning" && docker image ls
+ docker image prune -af --filter "until=2160h"
+ docker image prune -f
+ echo "Currently available images" && docker image ls
+
+ echo "Will NOT cache images" && docker image ls ${CI_CACHE_DOCKER_SAVE_IGNORE_FILTERS:--f="label=org.testcontainers.sessionId"}
+
+ mkdir -p ~/.cache/docker
+ image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q ${CI_CACHE_DOCKER_SAVE_IGNORE_FILTERS:--f="label=org.testcontainers.sessionId"} | sort))
+ echo "Saving image ids" && echo $image_ids_to_save
+ docker save $image_ids_to_save -o ~/.cache/docker/images.tar
+
+ ls -lha ~/.cache/docker
+
+ - name: Save docker image cache
+ if: inputs.cache-hit != 'true'
+ uses: actions/cache/save@v6
+ with:
+ path: ~/.cache/docker/
+ key: ${{ inputs.key }}
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/setup/action.yml
new file mode 100644
index 00000000..0f7c6a86
--- /dev/null
+++ b/.github/actions/docker-image-cache/setup/action.yml
@@ -0,0 +1,56 @@
+name: 'Restore docker image cache'
+description: 'Restore and load docker image cache'
+# Based on https://github.com/actions/cache/blob/v6/restore/action.yml
+inputs:
+ key:
+ description: 'An explicit key for restoring the cache'
+ required: true
+ restore-keys:
+ description: 'An ordered multiline string listing the prefix-matched keys, that are used for restoring stale cache if no cache hit occurred for key. Note `cache-hit` returns false in this case.'
+ required: false
+outputs:
+ key:
+ description: 'Identical to the input - can be used to prevent copy pasting'
+ value: ${{ inputs.key }}
+ cache-hit:
+ description: 'A boolean value to indicate an exact match was found for the primary key'
+ value: ${{ steps.restore-docker-image-cache.outputs.cache-hit }}
+ ACTIONS_RUNTIME_TOKEN:
+ value: ${{ steps.actions-vars.outputs.ACTIONS_RUNTIME_TOKEN }}
+ ACTIONS_RESULTS_URL:
+ value: ${{ steps.actions-vars.outputs.ACTIONS_RESULTS_URL }}
+runs:
+ using: "composite"
+ steps:
+ # RESTORE "NORMAL" DOCKER IMAGE CACHE
+ - name: Restore docker image cache
+ uses: actions/cache/restore@v6
+ id: restore-docker-image-cache
+ with:
+ path: ~/.cache/docker/
+ key: ${{ inputs.key }}
+ restore-keys: ${{ inputs.restore-keys }}
+
+ - name: Load docker image cache
+ shell: bash
+ run: |
+ echo "Deleting preinstalled images" && docker image ls
+ docker image prune -af > /dev/null
+
+ docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
+
+ # CONFIGURE BUILDX FOR CACHING
+
+ # These are required for caching
+ # https://docs.docker.com/build/cache/backends/gha/#authentication
+ - name: Get Actions Runtime credentials
+ if: runner.os != 'Windows'
+ id: actions-vars
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ core.setOutput('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN'])
+ core.setOutput('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL'])
+
+ - uses: docker/setup-buildx-action@v4
+ if: runner.os != 'Windows'
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index d5270144..ee734ca4 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -137,35 +137,15 @@ jobs:
path: ~/.m2/repository
key: ${{ runner.os }}-mvn-it-build-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
- - name: Restore docker image cache
- uses: actions/cache/restore@v6
- id: restore-docker-image-cache
+ - name: Docker Image Cache Setup
+ id: docker-image-cache-setup
+ uses: ./.github/actions/docker-image-cache/setup
with:
- path: ~/.cache/docker/
- key: ${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ key: it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-
- - name: Load docker image cache
- run: |
- echo "Deleting preinstalled images" && docker image ls
- docker image prune -af > /dev/null
-
- docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
-
- # These are required for caching
- # https://docs.docker.com/build/cache/backends/gha/#authentication
- - name: Get Actions Runtime credentials
- id: actions-vars
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- script: |
- core.setOutput('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN'])
- core.setOutput('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL'])
-
- - uses: docker/setup-buildx-action@v4
-
- name: Test
run: |
./mvnw -B test \
@@ -177,33 +157,14 @@ jobs:
${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
env:
- ACTIONS_RUNTIME_TOKEN: ${{ steps.actions-vars.outputs.ACTIONS_RUNTIME_TOKEN }}
- ACTIONS_RESULTS_URL: ${{ steps.actions-vars.outputs.ACTIONS_RESULTS_URL }}
-
- - name: Create docker image cache
- # Cache can't be saved when it's already present
- if: steps.cache.outputs.cache-hit != 'true'
- run: |
- echo "Images before pruning" && docker image ls
- docker image prune -af --filter "until=2160h"
- docker image prune -f
- echo "Currently available images" && docker image ls
-
- echo "Will NOT cache images" && docker image ls -f="label=org.testcontainers.sessionId"
-
- mkdir -p ~/.cache/docker
- image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q -f="label=org.testcontainers.sessionId" | sort))
- echo "Saving image ids" && echo $image_ids_to_save
- docker save $image_ids_to_save -o ~/.cache/docker/images.tar
-
- ls -lha ~/.cache/docker
+ ACTIONS_RUNTIME_TOKEN: ${{ steps.docker-image-cache-setup.outputs.ACTIONS_RUNTIME_TOKEN }}
+ ACTIONS_RESULTS_URL: ${{ steps.docker-image-cache-setup.outputs.ACTIONS_RESULTS_URL }}
- - name: Save docker image cache
- if: steps.cache.outputs.cache-hit != 'true'
- uses: actions/cache/save@v6
+ - name: Docker Image Cache Save
+ uses: ./.github/actions/docker-image-cache/save
with:
- path: ~/.cache/docker/
- key: ${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ key: ${{ steps.docker-image-cache-setup.outputs.key }}
+ cache-hit: ${{ steps.docker-image-cache-setup.outputs.cache-hit }}
- name: Upload videos of test failures
if: ${{ failure() }}
From 053223736ac4705996debfa393edf3c53f4fa7b1 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 11:01:08 +0200
Subject: [PATCH 047/141] Test github parallelization
---
.../docker-image-cache/setup/action.yml | 21 ++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/setup/action.yml
index 0f7c6a86..cb07b77e 100644
--- a/.github/actions/docker-image-cache/setup/action.yml
+++ b/.github/actions/docker-image-cache/setup/action.yml
@@ -23,13 +23,20 @@ runs:
using: "composite"
steps:
# RESTORE "NORMAL" DOCKER IMAGE CACHE
- - name: Restore docker image cache
- uses: actions/cache/restore@v6
- id: restore-docker-image-cache
- with:
- path: ~/.cache/docker/
- key: ${{ inputs.key }}
- restore-keys: ${{ inputs.restore-keys }}
+ - parallel:
+ - name: Delete preinstalled images
+ shell: bash
+ run: |
+ echo "Deleting preinstalled images" && docker image ls
+ docker image prune -af > /dev/null
+
+ - name: Restore docker image cache
+ uses: actions/cache/restore@v6
+ id: restore-docker-image-cache
+ with:
+ path: ~/.cache/docker/
+ key: ${{ inputs.key }}
+ restore-keys: ${{ inputs.restore-keys }}
- name: Load docker image cache
shell: bash
From e2ce726e0f5a7c3cccab45c025e200f49fdedbf7 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 11:17:34 +0200
Subject: [PATCH 048/141] Use parallel execution
---
.../docker-image-cache/setup/action.yml | 28 +++++------
.github/workflows/run-integration-tests.yml | 46 +++++++++++--------
2 files changed, 41 insertions(+), 33 deletions(-)
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/setup/action.yml
index cb07b77e..19b7ac2b 100644
--- a/.github/actions/docker-image-cache/setup/action.yml
+++ b/.github/actions/docker-image-cache/setup/action.yml
@@ -23,20 +23,22 @@ runs:
using: "composite"
steps:
# RESTORE "NORMAL" DOCKER IMAGE CACHE
- - parallel:
- - name: Delete preinstalled images
- shell: bash
- run: |
- echo "Deleting preinstalled images" && docker image ls
- docker image prune -af > /dev/null
- - name: Restore docker image cache
- uses: actions/cache/restore@v6
- id: restore-docker-image-cache
- with:
- path: ~/.cache/docker/
- key: ${{ inputs.key }}
- restore-keys: ${{ inputs.restore-keys }}
+ # NOTE 2026-07-09 Parallel execution is not yet available for composite actions
+ # https://github.blog/changelog/2026-06-25-actions-steps-can-now-be-run-in-parallel/
+ - name: Delete preinstalled images
+ shell: bash
+ run: |
+ echo "Deleting preinstalled images" && docker image ls
+ docker image prune -af > /dev/null
+
+ - name: Restore docker image cache
+ uses: actions/cache/restore@v6
+ id: restore-docker-image-cache
+ with:
+ path: ~/.cache/docker/
+ key: ${{ inputs.key }}
+ restore-keys: ${{ inputs.restore-keys }}
- name: Load docker image cache
shell: bash
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index ee734ca4..92369ca2 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -125,26 +125,27 @@ jobs:
steps:
- uses: actions/checkout@v7
- - name: Set up JDK
- uses: actions/setup-java@v5
- with:
- distribution: temurin
- java-version: ${{ matrix.java }}
-
- - name: Cache Maven
- uses: actions/cache@v6
- with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-mvn-it-build-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
-
- - name: Docker Image Cache Setup
- id: docker-image-cache-setup
- uses: ./.github/actions/docker-image-cache/setup
- with:
- key: it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
- ${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-
+ - parallel:
+ - name: Set up JDK
+ uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: ${{ matrix.java }}
+
+ - name: Cache Maven
+ uses: actions/cache@v6
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-mvn-it-build-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+
+ - name: Docker Image Cache Setup
+ id: docker-image-cache-setup
+ uses: ./.github/actions/docker-image-cache/setup
+ with:
+ key: it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ ${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-
- name: Test
run: |
@@ -165,6 +166,7 @@ jobs:
with:
key: ${{ steps.docker-image-cache-setup.outputs.key }}
cache-hit: ${{ steps.docker-image-cache-setup.outputs.cache-hit }}
+ background: true
- name: Upload videos of test failures
if: ${{ failure() }}
@@ -173,6 +175,7 @@ jobs:
name: test-fail-videos-${{ matrix.java }}-${{ matrix.project }}-${{ matrix.parallel }}-${{ matrix.pre-start }}
path: advanced-demo/integration-tests/${{ matrix.project }}/target/records
if-no-files-found: ignore
+ background: true
- name: Aggregate WebApp JaCoCo report
if: ${{ success() }}
@@ -187,3 +190,6 @@ jobs:
name: webapp-jacoco-report-${{ matrix.java }}-${{ matrix.project }}-${{ matrix.parallel }}-${{ matrix.pre-start }}
path: advanced-demo/target/site/jacoco-aggregate
if-no-files-found: ignore
+
+ - name: Wait for async jobs to finish
+ wait-all:
From f28fea3d76a69969eff1699ac4859541d2cb6064 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 12:55:49 +0200
Subject: [PATCH 049/141] Move lock to correct place
---
.../tci/serviceloading/TCIServiceLoader.java | 29 ++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/base/src/main/java/software/xdev/tci/serviceloading/TCIServiceLoader.java b/base/src/main/java/software/xdev/tci/serviceloading/TCIServiceLoader.java
index 891a9156..45c8047f 100644
--- a/base/src/main/java/software/xdev/tci/serviceloading/TCIServiceLoader.java
+++ b/base/src/main/java/software/xdev/tci/serviceloading/TCIServiceLoader.java
@@ -24,6 +24,9 @@
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
/**
* Central point for service loading
@@ -31,6 +34,8 @@
@SuppressWarnings({"java:S6548", "java:S2789"}) // Don't force us to write our own Optional!?!
public class TCIServiceLoader
{
+ private static final Logger LOG = LoggerFactory.getLogger(TCIServiceLoader.class);
+
protected final Object globalInitServiceLockHandlerLock = new Object();
protected final InheritableThreadLocal>> tlDetectRecursiveInitServices =
new InheritableThreadLocal<>();
@@ -84,9 +89,12 @@ protected void initService(final Class clazz)
lock = this.servicesLoadingSyncLocks.computeIfAbsent(
clazz,
ignored -> new ReentrantLock());
- lock.lock();
+ this.reportLockAction("got", clazz);
}
+ lock.lock();
+ this.reportLockAction("acquired", clazz);
+
try
{
// Already initialized?
@@ -97,6 +105,11 @@ protected void initService(final Class clazz)
this.loadedServices.put(clazz, this.loadService(clazz));
}
+ catch(final RuntimeException rex)
+ {
+ LOG.debug("Service loading failed", rex);
+ throw rex;
+ }
finally
{
synchronized(this.globalInitServiceLockHandlerLock)
@@ -106,10 +119,24 @@ protected void initService(final Class clazz)
this.servicesLoadingSyncLocks.remove(clazz);
lock.unlock();
+ this.reportLockAction("release", clazz);
}
}
}
+ protected void reportLockAction(final String action, final Class clazz)
+ {
+ if(LOG.isTraceEnabled())
+ {
+ LOG.trace(
+ "Lock {} {} - T:{}",
+ action,
+ clazz,
+ Thread.currentThread().getName(),
+ new RuntimeException());
+ }
+ }
+
protected Optional loadService(final Class clazz)
{
return ServiceLoader.load(clazz)
From e10067d1d5fcc4757451077499f603534d3cdd7d Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 12:56:35 +0200
Subject: [PATCH 050/141] Execute JULtoSLF4J redirect only once when called
multiple times
---
.../xdev/tci/logging/JULtoSLF4JRedirector.java | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/jul-to-slf4j/src/main/java/software/xdev/tci/logging/JULtoSLF4JRedirector.java b/jul-to-slf4j/src/main/java/software/xdev/tci/logging/JULtoSLF4JRedirector.java
index be2d0675..6c41107d 100644
--- a/jul-to-slf4j/src/main/java/software/xdev/tci/logging/JULtoSLF4JRedirector.java
+++ b/jul-to-slf4j/src/main/java/software/xdev/tci/logging/JULtoSLF4JRedirector.java
@@ -35,6 +35,17 @@ protected void redirectInternal()
{
return;
}
+
+ this.redirectInternalSync();
+ }
+
+ protected synchronized void redirectInternalSync()
+ {
+ if(this.installed)
+ {
+ return;
+ }
+
if(SLF4JBridgeHandler.isInstalled())
{
this.installed = true;
From 6b2be2ee6fefcdc26d3c72a97587ea97031b76d6 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 12:58:17 +0200
Subject: [PATCH 051/141] Build image in background
---
.../software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java | 4 +++-
.../xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java | 4 +++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java
index 7b285276..77bd06bd 100644
--- a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java
+++ b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java
@@ -1,8 +1,10 @@
package software.xdev.tci.demo.tci.db.factory;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import software.xdev.tci.concurrent.Suppliers;
+import software.xdev.tci.concurrent.TCIExecutorServiceHolder;
import software.xdev.tci.db.factory.BaseDBTCIFactory;
import software.xdev.tci.demo.tci.db.DBTCI;
import software.xdev.tci.demo.tci.db.containers.DBContainer;
@@ -34,7 +36,7 @@ public DBTCIFactory(final boolean migrateAndInitializeEMC)
@Override
protected void warmUpInternal()
{
- IMAGE_NAME_SUPPLIER.get();
+ CompletableFuture.runAsync(IMAGE_NAME_SUPPLIER::get, TCIExecutorServiceHolder.instance());
super.warmUpInternal();
}
}
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
index f9ea42fd..848b0b7b 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java
@@ -1,10 +1,12 @@
package software.xdev.tci.demo.tci.webapp.factory;
import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Supplier;
import software.xdev.tci.concurrent.Suppliers;
+import software.xdev.tci.concurrent.TCIExecutorServiceHolder;
import software.xdev.tci.demo.tci.webapp.WebAppTCI;
import software.xdev.tci.demo.tci.webapp.containers.WebAppContainer;
import software.xdev.tci.demo.tci.webapp.containers.WebAppContainerBuilder;
@@ -48,7 +50,7 @@ public WebAppTCIFactory(final Consumer additionalContainerBuild
@Override
protected void warmUpInternal()
{
- IMAGE_NAME_SUPPLIER.get();
+ CompletableFuture.runAsync(IMAGE_NAME_SUPPLIER::get, TCIExecutorServiceHolder.instance());
super.warmUpInternal();
}
}
From a4577a5867e5c32e21f7d91c08178088afdb08f9 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 12:58:35 +0200
Subject: [PATCH 052/141] Do not wait forever when warmUp hangs
---
.../registry/DefaultTCIFactoryRegistry.java | 26 ++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/base/src/main/java/software/xdev/tci/factory/registry/DefaultTCIFactoryRegistry.java b/base/src/main/java/software/xdev/tci/factory/registry/DefaultTCIFactoryRegistry.java
index c5380c44..95b0658f 100644
--- a/base/src/main/java/software/xdev/tci/factory/registry/DefaultTCIFactoryRegistry.java
+++ b/base/src/main/java/software/xdev/tci/factory/registry/DefaultTCIFactoryRegistry.java
@@ -21,6 +21,9 @@
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -58,7 +61,28 @@ public void warmUp()
.filter(f -> !this.warmedUpFactories.contains(f))
.map(this::warmUpFactory)
.toList()
- .forEach(CompletableFuture::join);
+ .forEach(this::waitForWarmUpCF);
+ }
+ }
+
+ protected void waitForWarmUpCF(final CompletableFuture> cf)
+ {
+ try
+ {
+ cf.get(10, TimeUnit.MINUTES);
+ }
+ catch(final TimeoutException ex)
+ {
+ throw new IllegalStateException("Timed out", ex);
+ }
+ catch(final InterruptedException ex)
+ {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("Got interrupted", ex);
+ }
+ catch(final ExecutionException ex)
+ {
+ throw new IllegalStateException("Execution failed", ex);
}
}
From d2e19bc86dd4ce8457a9af9eb48d9f4136686cd3 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 12:59:25 +0200
Subject: [PATCH 053/141] Add missing license headers
---
.../xdev/tci/imagebuild/CreateImageHandler.java | 15 +++++++++++++++
.../tci/imagebuild/DefaultCreateImageHandler.java | 15 +++++++++++++++
.../xdev/tci/imagebuild/ImageCreator.java | 15 +++++++++++++++
.../config/CreateImageHandlerConfig.java | 15 +++++++++++++++
.../config/DefaultCreateImageHandlerConfig.java | 15 +++++++++++++++
5 files changed, 75 insertions(+)
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/CreateImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/CreateImageHandler.java
index 5056a293..b447f6c7 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/CreateImageHandler.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/CreateImageHandler.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.imagebuild;
import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultCreateImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultCreateImageHandler.java
index 939b6340..efc1814d 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultCreateImageHandler.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultCreateImageHandler.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.imagebuild;
import java.util.regex.Pattern;
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/ImageCreator.java b/image-build/src/main/java/software/xdev/tci/imagebuild/ImageCreator.java
index 4be898b5..ef452c0d 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/ImageCreator.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/ImageCreator.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.imagebuild;
import software.xdev.tci.serviceloading.TCIServiceLoaderHolder;
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/config/CreateImageHandlerConfig.java b/image-build/src/main/java/software/xdev/tci/imagebuild/config/CreateImageHandlerConfig.java
index bfa48e15..80ac8081 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/config/CreateImageHandlerConfig.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/config/CreateImageHandlerConfig.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.imagebuild.config;
import java.util.Optional;
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultCreateImageHandlerConfig.java b/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultCreateImageHandlerConfig.java
index d40b1f6e..80d09f16 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultCreateImageHandlerConfig.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultCreateImageHandlerConfig.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.imagebuild.config;
import java.util.Optional;
From a38352f554cbfd6a01553cdd55c2ee4b75369934 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 13:13:40 +0200
Subject: [PATCH 054/141] Try to improve cache restore speed
---
.../actions/docker-image-cache/setup/action.yml | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/setup/action.yml
index 19b7ac2b..7d9a71c4 100644
--- a/.github/actions/docker-image-cache/setup/action.yml
+++ b/.github/actions/docker-image-cache/setup/action.yml
@@ -26,12 +26,6 @@ runs:
# NOTE 2026-07-09 Parallel execution is not yet available for composite actions
# https://github.blog/changelog/2026-06-25-actions-steps-can-now-be-run-in-parallel/
- - name: Delete preinstalled images
- shell: bash
- run: |
- echo "Deleting preinstalled images" && docker image ls
- docker image prune -af > /dev/null
-
- name: Restore docker image cache
uses: actions/cache/restore@v6
id: restore-docker-image-cache
@@ -44,9 +38,15 @@ runs:
shell: bash
run: |
echo "Deleting preinstalled images" && docker image ls
- docker image prune -af > /dev/null
+ image_ids_to_delete=$(docker images -a -q)
+ echo "Deleting image ids" && echo $image_ids_to_save
+ # Delete images in background while new images are loaded in
+ (time docker image rm -f $image_ids_to_delete || true) &
+ rm_pid=$!
+
+ time docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
- docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
+ wait $rm_pid
# CONFIGURE BUILDX FOR CACHING
From e8b25b4204a61a765547c495a571ff79da52e15d Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 13:14:42 +0200
Subject: [PATCH 055/141] Fix var name
---
.github/actions/docker-image-cache/setup/action.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/setup/action.yml
index 7d9a71c4..e607a06a 100644
--- a/.github/actions/docker-image-cache/setup/action.yml
+++ b/.github/actions/docker-image-cache/setup/action.yml
@@ -39,7 +39,7 @@ runs:
run: |
echo "Deleting preinstalled images" && docker image ls
image_ids_to_delete=$(docker images -a -q)
- echo "Deleting image ids" && echo $image_ids_to_save
+ echo "Deleting image ids" && echo $image_ids_to_delete
# Delete images in background while new images are loaded in
(time docker image rm -f $image_ids_to_delete || true) &
rm_pid=$!
From d93fdd5e2a95a6eb1c8b52a94e7b58548b69fc12 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 13:15:08 +0200
Subject: [PATCH 056/141] Temp disable
---
.github/workflows/check-build.yml | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml
index 3db742c4..1b5af9f7 100644
--- a/.github/workflows/check-build.yml
+++ b/.github/workflows/check-build.yml
@@ -10,14 +10,15 @@ on:
- '.github/**'
- '.idea/**'
- 'assets/**'
- pull_request:
- branches: [ develop ]
- paths-ignore:
- - '**.md'
- - '.config/**'
- - '.github/**'
- - '.idea/**'
- - 'assets/**'
+ # TODO
+ # pull_request:
+ # branches: [ develop ]
+ # paths-ignore:
+ # - '**.md'
+ # - '.config/**'
+ # - '.github/**'
+ # - '.idea/**'
+ # - 'assets/**'
env:
DEMO_MAVEN_MODULE: ${{ github.event.repository.name }}-demo
From 190fb65a268df29359a84f11aaac121eb0280531 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 13:20:40 +0200
Subject: [PATCH 057/141] Update action.yml
---
.github/actions/docker-image-cache/setup/action.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/setup/action.yml
index e607a06a..adf91cdd 100644
--- a/.github/actions/docker-image-cache/setup/action.yml
+++ b/.github/actions/docker-image-cache/setup/action.yml
@@ -37,7 +37,7 @@ runs:
- name: Load docker image cache
shell: bash
run: |
- echo "Deleting preinstalled images" && docker image ls
+ echo "Deleting preinstalled images to not run out of disk space" && docker image ls
image_ids_to_delete=$(docker images -a -q)
echo "Deleting image ids" && echo $image_ids_to_delete
# Delete images in background while new images are loaded in
From c55e91c40e8ead87ad8e115e497a7a4c663cdb8e Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 13:21:21 +0200
Subject: [PATCH 058/141] Update run-integration-tests.yml
---
.github/workflows/run-integration-tests.yml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 92369ca2..61a594ea 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -160,6 +160,10 @@ jobs:
env:
ACTIONS_RUNTIME_TOKEN: ${{ steps.docker-image-cache-setup.outputs.ACTIONS_RUNTIME_TOKEN }}
ACTIONS_RESULTS_URL: ${{ steps.docker-image-cache-setup.outputs.ACTIONS_RESULTS_URL }}
+
+ - name: Display images
+ run: |
+ docker image ls
- name: Docker Image Cache Save
uses: ./.github/actions/docker-image-cache/save
From 8ff10fafa869b56892c87f071dedeb1e75610d4b Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 13:22:40 +0200
Subject: [PATCH 059/141] Update action.yml
---
.github/actions/docker-image-cache/setup/action.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/setup/action.yml
index adf91cdd..66c97c8d 100644
--- a/.github/actions/docker-image-cache/setup/action.yml
+++ b/.github/actions/docker-image-cache/setup/action.yml
@@ -44,6 +44,7 @@ runs:
(time docker image rm -f $image_ids_to_delete || true) &
rm_pid=$!
+ echo "Importing cache"
time docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
wait $rm_pid
From d3dc08438fb6fbcede7f06546a8ad53aa81b78ed Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 13:50:02 +0200
Subject: [PATCH 060/141] Update action.yml
---
.github/actions/docker-image-cache/setup/action.yml | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/setup/action.yml
index 66c97c8d..e502e799 100644
--- a/.github/actions/docker-image-cache/setup/action.yml
+++ b/.github/actions/docker-image-cache/setup/action.yml
@@ -37,18 +37,9 @@ runs:
- name: Load docker image cache
shell: bash
run: |
- echo "Deleting preinstalled images to not run out of disk space" && docker image ls
- image_ids_to_delete=$(docker images -a -q)
- echo "Deleting image ids" && echo $image_ids_to_delete
- # Delete images in background while new images are loaded in
- (time docker image rm -f $image_ids_to_delete || true) &
- rm_pid=$!
-
echo "Importing cache"
time docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
- wait $rm_pid
-
# CONFIGURE BUILDX FOR CACHING
# These are required for caching
From cc9d0a97eea0f5d5d97b807f5be72f46ce764a62 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 14:12:12 +0200
Subject: [PATCH 061/141] Do not cache pre-installed images
---
.github/actions/docker-image-cache/save/action.yml | 7 +++++++
.github/actions/docker-image-cache/setup/action.yml | 7 +++++++
advanced-demo/pom.xml | 2 +-
3 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/.github/actions/docker-image-cache/save/action.yml b/.github/actions/docker-image-cache/save/action.yml
index b2e168a1..adab93eb 100644
--- a/.github/actions/docker-image-cache/save/action.yml
+++ b/.github/actions/docker-image-cache/save/action.yml
@@ -21,10 +21,17 @@ runs:
docker image prune -f
echo "Currently available images" && docker image ls
+ preinstalled_image_ids=()
+ if [ -f ~/.cache/docker-preinstalled/ids.txt ]; then
+ readarray -t preinstalled_image_ids ~/.cache/docker-preinstalled/ids.txt
+ echo "Will NOT cache pre-installed images" && echo $preinstalled_image_ids
+ fi
+
echo "Will NOT cache images" && docker image ls ${CI_CACHE_DOCKER_SAVE_IGNORE_FILTERS:--f="label=org.testcontainers.sessionId"}
mkdir -p ~/.cache/docker
image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q ${CI_CACHE_DOCKER_SAVE_IGNORE_FILTERS:--f="label=org.testcontainers.sessionId"} | sort))
+ image_ids_to_save=$(comm -23 <(echo $image_ids_to_save) <(echo $preinstalled_image_ids))
echo "Saving image ids" && echo $image_ids_to_save
docker save $image_ids_to_save -o ~/.cache/docker/images.tar
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/setup/action.yml
index e502e799..f72b1d78 100644
--- a/.github/actions/docker-image-cache/setup/action.yml
+++ b/.github/actions/docker-image-cache/setup/action.yml
@@ -37,6 +37,13 @@ runs:
- name: Load docker image cache
shell: bash
run: |
+ echo "Getting preinstalled images" && docker image ls
+ preinstalled_image_ids=$(docker images -q)
+
+ echo "Storing preinstalled image ids" && echo $preinstalled_image_ids
+ mkdir -p ~/.cache/docker-preinstalled
+ printf "%s\n" "${preinstalled_image_ids[@]}" > ~/.cache/docker-preinstalled/ids.txt
+
echo "Importing cache"
time docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 2007e98a..2bce894b 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -14,7 +14,7 @@
https://xdev.software
-
+
21
From 2024acde275ed52757e80f1c75f3ff41de4d16f6 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 14:43:15 +0200
Subject: [PATCH 062/141] Try to debug problem
---
.github/actions/docker-image-cache/save/action.yml | 1 +
.github/actions/docker-image-cache/setup/action.yml | 1 +
advanced-demo/pom.xml | 2 +-
3 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/.github/actions/docker-image-cache/save/action.yml b/.github/actions/docker-image-cache/save/action.yml
index adab93eb..e697eaa2 100644
--- a/.github/actions/docker-image-cache/save/action.yml
+++ b/.github/actions/docker-image-cache/save/action.yml
@@ -23,6 +23,7 @@ runs:
preinstalled_image_ids=()
if [ -f ~/.cache/docker-preinstalled/ids.txt ]; then
+ cat ~/.cache/docker-preinstalled/ids.txt
readarray -t preinstalled_image_ids ~/.cache/docker-preinstalled/ids.txt
echo "Will NOT cache pre-installed images" && echo $preinstalled_image_ids
fi
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/setup/action.yml
index f72b1d78..e8445f71 100644
--- a/.github/actions/docker-image-cache/setup/action.yml
+++ b/.github/actions/docker-image-cache/setup/action.yml
@@ -43,6 +43,7 @@ runs:
echo "Storing preinstalled image ids" && echo $preinstalled_image_ids
mkdir -p ~/.cache/docker-preinstalled
printf "%s\n" "${preinstalled_image_ids[@]}" > ~/.cache/docker-preinstalled/ids.txt
+ cat ~/.cache/docker-preinstalled/ids.txt
echo "Importing cache"
time docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 2bce894b..105b4d43 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -14,7 +14,7 @@
https://xdev.software
-
+
21
From 763e88b35d7ac843565c51ab1a7c65548ca8ac75 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 15:09:57 +0200
Subject: [PATCH 063/141] Fix readarray
---
.github/actions/docker-image-cache/save/action.yml | 3 +--
.github/actions/docker-image-cache/setup/action.yml | 1 -
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/.github/actions/docker-image-cache/save/action.yml b/.github/actions/docker-image-cache/save/action.yml
index e697eaa2..073e3888 100644
--- a/.github/actions/docker-image-cache/save/action.yml
+++ b/.github/actions/docker-image-cache/save/action.yml
@@ -23,8 +23,7 @@ runs:
preinstalled_image_ids=()
if [ -f ~/.cache/docker-preinstalled/ids.txt ]; then
- cat ~/.cache/docker-preinstalled/ids.txt
- readarray -t preinstalled_image_ids ~/.cache/docker-preinstalled/ids.txt
+ readarray -t preinstalled_image_ids < ~/.cache/docker-preinstalled/ids.txt
echo "Will NOT cache pre-installed images" && echo $preinstalled_image_ids
fi
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/setup/action.yml
index e8445f71..f72b1d78 100644
--- a/.github/actions/docker-image-cache/setup/action.yml
+++ b/.github/actions/docker-image-cache/setup/action.yml
@@ -43,7 +43,6 @@ runs:
echo "Storing preinstalled image ids" && echo $preinstalled_image_ids
mkdir -p ~/.cache/docker-preinstalled
printf "%s\n" "${preinstalled_image_ids[@]}" > ~/.cache/docker-preinstalled/ids.txt
- cat ~/.cache/docker-preinstalled/ids.txt
echo "Importing cache"
time docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
From 688117a979435d43899b1177c4e1bb2ca79a3e73 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 15:12:06 +0200
Subject: [PATCH 064/141] Update pom.xml
---
advanced-demo/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 105b4d43..696abeae 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -14,7 +14,7 @@
https://xdev.software
-
+
21
From 0b95e1a0002b554b72c871e9374827984450a8f0 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 15:29:24 +0200
Subject: [PATCH 065/141] TEST
---
.github/actions/docker-image-cache/save/action.yml | 6 ++----
.github/actions/docker-image-cache/setup/action.yml | 2 +-
advanced-demo/pom.xml | 2 +-
3 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/.github/actions/docker-image-cache/save/action.yml b/.github/actions/docker-image-cache/save/action.yml
index 073e3888..8c132d2d 100644
--- a/.github/actions/docker-image-cache/save/action.yml
+++ b/.github/actions/docker-image-cache/save/action.yml
@@ -21,17 +21,15 @@ runs:
docker image prune -f
echo "Currently available images" && docker image ls
- preinstalled_image_ids=()
if [ -f ~/.cache/docker-preinstalled/ids.txt ]; then
- readarray -t preinstalled_image_ids < ~/.cache/docker-preinstalled/ids.txt
- echo "Will NOT cache pre-installed images" && echo $preinstalled_image_ids
+ echo "Will NOT cache pre-installed images" && cat ~/.cache/docker-preinstalled/ids.txt
fi
echo "Will NOT cache images" && docker image ls ${CI_CACHE_DOCKER_SAVE_IGNORE_FILTERS:--f="label=org.testcontainers.sessionId"}
mkdir -p ~/.cache/docker
image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q ${CI_CACHE_DOCKER_SAVE_IGNORE_FILTERS:--f="label=org.testcontainers.sessionId"} | sort))
- image_ids_to_save=$(comm -23 <(echo $image_ids_to_save) <(echo $preinstalled_image_ids))
+ image_ids_to_save=$(comm -23 <(echo $image_ids_to_save) <(cat ~/.cache/docker-preinstalled/ids.txt || echo ""))
echo "Saving image ids" && echo $image_ids_to_save
docker save $image_ids_to_save -o ~/.cache/docker/images.tar
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/setup/action.yml
index f72b1d78..d6b50f1e 100644
--- a/.github/actions/docker-image-cache/setup/action.yml
+++ b/.github/actions/docker-image-cache/setup/action.yml
@@ -38,7 +38,7 @@ runs:
shell: bash
run: |
echo "Getting preinstalled images" && docker image ls
- preinstalled_image_ids=$(docker images -q)
+ preinstalled_image_ids=$(docker images -q | sort)
echo "Storing preinstalled image ids" && echo $preinstalled_image_ids
mkdir -p ~/.cache/docker-preinstalled
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 696abeae..080826c2 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -14,7 +14,7 @@
https://xdev.software
-
+
21
From 58c335fad5e36c30cd559454f34752e065b8d777 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 15:37:33 +0200
Subject: [PATCH 066/141] Update action.yml
---
.github/actions/docker-image-cache/save/action.yml | 2 +-
advanced-demo/pom.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/actions/docker-image-cache/save/action.yml b/.github/actions/docker-image-cache/save/action.yml
index 8c132d2d..ce2f9813 100644
--- a/.github/actions/docker-image-cache/save/action.yml
+++ b/.github/actions/docker-image-cache/save/action.yml
@@ -29,7 +29,7 @@ runs:
mkdir -p ~/.cache/docker
image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q ${CI_CACHE_DOCKER_SAVE_IGNORE_FILTERS:--f="label=org.testcontainers.sessionId"} | sort))
- image_ids_to_save=$(comm -23 <(echo $image_ids_to_save) <(cat ~/.cache/docker-preinstalled/ids.txt || echo ""))
+ image_ids_to_save=$(comm -23 <(echo "${image_ids_to_save[@]}") <(cat ~/.cache/docker-preinstalled/ids.txt || echo ""))
echo "Saving image ids" && echo $image_ids_to_save
docker save $image_ids_to_save -o ~/.cache/docker/images.tar
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 080826c2..ba1c94cf 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -14,7 +14,7 @@
https://xdev.software
-
+
21
From 3a6128fe65891c6368d8ea4896b7ff6bac50dd41 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 15:40:46 +0200
Subject: [PATCH 067/141] Images are loaded from cache without tags so they are
always pruned
---
.github/actions/docker-image-cache/save/action.yml | 1 -
advanced-demo/pom.xml | 2 +-
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/.github/actions/docker-image-cache/save/action.yml b/.github/actions/docker-image-cache/save/action.yml
index ce2f9813..1bef6363 100644
--- a/.github/actions/docker-image-cache/save/action.yml
+++ b/.github/actions/docker-image-cache/save/action.yml
@@ -17,7 +17,6 @@ runs:
shell: bash
run: |
echo "Images before pruning" && docker image ls
- docker image prune -af --filter "until=2160h"
docker image prune -f
echo "Currently available images" && docker image ls
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index ba1c94cf..694c2715 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -14,7 +14,7 @@
https://xdev.software
-
+
21
From 563a51fc9744e52964b80952ec4262dd99f7b23e Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 15:53:41 +0200
Subject: [PATCH 068/141] Add new lines
---
.github/actions/docker-image-cache/save/action.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/actions/docker-image-cache/save/action.yml b/.github/actions/docker-image-cache/save/action.yml
index 1bef6363..d9e3b341 100644
--- a/.github/actions/docker-image-cache/save/action.yml
+++ b/.github/actions/docker-image-cache/save/action.yml
@@ -21,15 +21,15 @@ runs:
echo "Currently available images" && docker image ls
if [ -f ~/.cache/docker-preinstalled/ids.txt ]; then
- echo "Will NOT cache pre-installed images" && cat ~/.cache/docker-preinstalled/ids.txt
+ echo && echo "Will NOT cache pre-installed images" && cat ~/.cache/docker-preinstalled/ids.txt
fi
- echo "Will NOT cache images" && docker image ls ${CI_CACHE_DOCKER_SAVE_IGNORE_FILTERS:--f="label=org.testcontainers.sessionId"}
+ echo && echo "Will NOT cache images" && docker image ls ${CI_CACHE_DOCKER_SAVE_IGNORE_FILTERS:--f="label=org.testcontainers.sessionId"}
mkdir -p ~/.cache/docker
image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q ${CI_CACHE_DOCKER_SAVE_IGNORE_FILTERS:--f="label=org.testcontainers.sessionId"} | sort))
image_ids_to_save=$(comm -23 <(echo "${image_ids_to_save[@]}") <(cat ~/.cache/docker-preinstalled/ids.txt || echo ""))
- echo "Saving image ids" && echo $image_ids_to_save
+ echo && echo "Saving image ids" && echo $image_ids_to_save
docker save $image_ids_to_save -o ~/.cache/docker/images.tar
ls -lha ~/.cache/docker
From 8b1cdf314d61e26ed9a78948ebc53a813d6a6afd Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 16:02:45 +0200
Subject: [PATCH 069/141] Improve speed
X
---
.github/actions/docker-image-cache/README.md | 5 ++++
.../{setup => restore}/action.yml | 20 ----------------
.../setup-buildx/action.yml | 24 +++++++++++++++++++
.github/workflows/run-integration-tests.yml | 18 ++++++++------
4 files changed, 40 insertions(+), 27 deletions(-)
create mode 100644 .github/actions/docker-image-cache/README.md
rename .github/actions/docker-image-cache/{setup => restore}/action.yml (69%)
create mode 100644 .github/actions/docker-image-cache/setup-buildx/action.yml
diff --git a/.github/actions/docker-image-cache/README.md b/.github/actions/docker-image-cache/README.md
new file mode 100644
index 00000000..a54612c9
--- /dev/null
+++ b/.github/actions/docker-image-cache/README.md
@@ -0,0 +1,5 @@
+# Composite GitHub Actions
+
+## Notes
+* [Parallel execution](https://github.blog/changelog/2026-06-25-actions-steps-can-now-be-run-in-parallel/) is not yet available for composite actions
+ * Therefore `restore` and `setup-buildx` are currently split as it's recommended that they be implemented in parallel downstream
diff --git a/.github/actions/docker-image-cache/setup/action.yml b/.github/actions/docker-image-cache/restore/action.yml
similarity index 69%
rename from .github/actions/docker-image-cache/setup/action.yml
rename to .github/actions/docker-image-cache/restore/action.yml
index d6b50f1e..2f160056 100644
--- a/.github/actions/docker-image-cache/setup/action.yml
+++ b/.github/actions/docker-image-cache/restore/action.yml
@@ -15,10 +15,6 @@ outputs:
cache-hit:
description: 'A boolean value to indicate an exact match was found for the primary key'
value: ${{ steps.restore-docker-image-cache.outputs.cache-hit }}
- ACTIONS_RUNTIME_TOKEN:
- value: ${{ steps.actions-vars.outputs.ACTIONS_RUNTIME_TOKEN }}
- ACTIONS_RESULTS_URL:
- value: ${{ steps.actions-vars.outputs.ACTIONS_RESULTS_URL }}
runs:
using: "composite"
steps:
@@ -46,19 +42,3 @@ runs:
echo "Importing cache"
time docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
-
- # CONFIGURE BUILDX FOR CACHING
-
- # These are required for caching
- # https://docs.docker.com/build/cache/backends/gha/#authentication
- - name: Get Actions Runtime credentials
- if: runner.os != 'Windows'
- id: actions-vars
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- script: |
- core.setOutput('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN'])
- core.setOutput('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL'])
-
- - uses: docker/setup-buildx-action@v4
- if: runner.os != 'Windows'
diff --git a/.github/actions/docker-image-cache/setup-buildx/action.yml b/.github/actions/docker-image-cache/setup-buildx/action.yml
new file mode 100644
index 00000000..bb986821
--- /dev/null
+++ b/.github/actions/docker-image-cache/setup-buildx/action.yml
@@ -0,0 +1,24 @@
+name: 'Setup buildx'
+description: 'Setup buildx'
+# Based on https://github.com/actions/cache/blob/v6/restore/action.yml
+outputs:
+ ACTIONS_RUNTIME_TOKEN:
+ value: ${{ steps.actions-vars.outputs.ACTIONS_RUNTIME_TOKEN }}
+ ACTIONS_RESULTS_URL:
+ value: ${{ steps.actions-vars.outputs.ACTIONS_RESULTS_URL }}
+runs:
+ using: "composite"
+ steps:
+ # These are required for caching
+ # https://docs.docker.com/build/cache/backends/gha/#authentication
+ - name: Get Actions Runtime credentials
+ if: runner.os != 'Windows'
+ id: actions-vars
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ core.setOutput('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN'])
+ core.setOutput('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL'])
+
+ - uses: docker/setup-buildx-action@v4
+ if: runner.os != 'Windows'
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 61a594ea..db663e6d 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -138,15 +138,19 @@ jobs:
path: ~/.m2/repository
key: ${{ runner.os }}-mvn-it-build-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
- - name: Docker Image Cache Setup
- id: docker-image-cache-setup
- uses: ./.github/actions/docker-image-cache/setup
+ - name: Docker Image Cache Restore
+ id: docker-image-cache-restore
+ uses: ./.github/actions/docker-image-cache/restore
with:
key: it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-
+ - name: Setup Buildx
+ id: docker-image-cache-setup-buildx
+ uses: ./.github/actions/docker-image-cache/setup-buildx
+
- name: Test
run: |
./mvnw -B test \
@@ -158,8 +162,8 @@ jobs:
${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
env:
- ACTIONS_RUNTIME_TOKEN: ${{ steps.docker-image-cache-setup.outputs.ACTIONS_RUNTIME_TOKEN }}
- ACTIONS_RESULTS_URL: ${{ steps.docker-image-cache-setup.outputs.ACTIONS_RESULTS_URL }}
+ ACTIONS_RUNTIME_TOKEN: ${{ steps.docker-image-cache-setup-buildx.outputs.ACTIONS_RUNTIME_TOKEN }}
+ ACTIONS_RESULTS_URL: ${{ steps.docker-image-cache-setup-buildx.outputs.ACTIONS_RESULTS_URL }}
- name: Display images
run: |
@@ -168,8 +172,8 @@ jobs:
- name: Docker Image Cache Save
uses: ./.github/actions/docker-image-cache/save
with:
- key: ${{ steps.docker-image-cache-setup.outputs.key }}
- cache-hit: ${{ steps.docker-image-cache-setup.outputs.cache-hit }}
+ key: ${{ steps.docker-image-cache-restore.outputs.key }}
+ cache-hit: ${{ steps.docker-image-cache-restore.outputs.cache-hit }}
background: true
- name: Upload videos of test failures
From a0e57ee40bf5f2a1e49246c1d9b8458d766aa6ed Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 16:10:46 +0200
Subject: [PATCH 070/141] Update to Java 25
---
advanced-demo/integration-tests/tci-webapp/Dockerfile | 4 ++--
advanced-demo/webapp/Dockerfile | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/advanced-demo/integration-tests/tci-webapp/Dockerfile b/advanced-demo/integration-tests/tci-webapp/Dockerfile
index f48cab54..e8bb371f 100644
--- a/advanced-demo/integration-tests/tci-webapp/Dockerfile
+++ b/advanced-demo/integration-tests/tci-webapp/Dockerfile
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1-labs
# Note: This Dockerfile is used by the integration tests for compiling the app when there was no Image was supplied
-ARG JAVA_VERSION=21
+ARG JAVA_VERSION=25
FROM eclipse-temurin:${JAVA_VERSION}-jre-alpine AS jre-base
@@ -111,7 +111,7 @@ RUN true
COPY --from=builder --chown=${user}:${group} /builder/extracted/application/ ./
ENV JAVA_JACOCO_OPTS=${JACOCO_AGENT_ENABLED:+"-javaagent:/jacoco/agent.jar=destfile=/jacoco/report/jacoco.exec"}
-ENV JAVA_OPTS="-XX:MaxRAMPercentage=75 -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=30 -Djava.awt.headless=true"
+ENV JAVA_OPTS="-XX:MaxRAMPercentage=75 -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=30 -XX:+UseCompactObjectHeaders -Djava.awt.headless=true"
EXPOSE 8080
diff --git a/advanced-demo/webapp/Dockerfile b/advanced-demo/webapp/Dockerfile
index 47f962f1..82581fde 100644
--- a/advanced-demo/webapp/Dockerfile
+++ b/advanced-demo/webapp/Dockerfile
@@ -1,4 +1,4 @@
-FROM eclipse-temurin:21-jre-alpine AS jre-base
+FROM eclipse-temurin:25-jre-alpine AS jre-base
# Build the JRE ourself and exclude stuff from Eclipse-Temurin that we don't need
From e5abdc8500d0d95adb5e694456a81f1f85b313ca Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 16:17:02 +0200
Subject: [PATCH 071/141] Restore commented out code
---
.github/workflows/check-build.yml | 17 +--
.github/workflows/run-integration-tests.yml | 157 +++++++++-----------
advanced-demo/README.md | 1 +
advanced-demo/pom.xml | 2 -
4 files changed, 78 insertions(+), 99 deletions(-)
diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml
index 1b5af9f7..3db742c4 100644
--- a/.github/workflows/check-build.yml
+++ b/.github/workflows/check-build.yml
@@ -10,15 +10,14 @@ on:
- '.github/**'
- '.idea/**'
- 'assets/**'
- # TODO
- # pull_request:
- # branches: [ develop ]
- # paths-ignore:
- # - '**.md'
- # - '.config/**'
- # - '.github/**'
- # - '.idea/**'
- # - 'assets/**'
+ pull_request:
+ branches: [ develop ]
+ paths-ignore:
+ - '**.md'
+ - '.config/**'
+ - '.github/**'
+ - '.idea/**'
+ - 'assets/**'
env:
DEMO_MAVEN_MODULE: ${{ github.event.repository.name }}-demo
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index db663e6d..7680791e 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -24,97 +24,78 @@ concurrency:
cancel-in-progress: true
jobs:
- # persistence:
- # strategy:
- # fail-fast: false
- # matrix:
- # project: [persistence-it-hibernate]
- # parallel: [0, 2]
- # pre-start: [false, true]
- # java: [21]
- # include:
- # - project: persistence-it-eclipselink
- # parallel: 2
- # pre-start: true
- # java: 21
- # name: "persistence (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
- # runs-on: ubuntu-latest
- # if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
- # timeout-minutes: 10
- # permissions:
- # contents: read
- # steps:
- # - uses: actions/checkout@v7
-
- # - name: Set up JDK
- # uses: actions/setup-java@v5
- # with:
- # distribution: temurin
- # java-version: ${{ matrix.java }}
-
- # - name: Cache Maven
- # uses: actions/cache@v6
- # with:
- # path: ~/.m2/repository
- # key: ${{ runner.os }}-mvn-it-build-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
-
- # - name: Restore docker image cache
- # uses: actions/cache/restore@v6
- # id: restore-docker-image-cache
- # with:
- # path: ~/.cache/docker/
- # key: ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
- # restore-keys: |
- # ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
- # ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-
-
- # - name: Load docker image cache
- # run: |
- # echo "Deleting preinstalled images" && docker image ls
- # docker image prune -af
-
- # cat ~/.cache/docker/images.tar | docker load && echo "Restored docker image cache" && docker image ls || true
-
- # - name: Test
- # run: |
- # ./mvnw -B test \
- # -pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
- # -P run-it \
- # ${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
- # ${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
-
- # - name: Create docker image cache
- # # Cache can't be saved when it's already present
- # if: steps.cache.outputs.cache-hit != 'true'
- # run: |
- # echo "Images before pruning" && docker image ls
- # docker image prune -af --filter "until=2160h"
- # docker image prune -f
- # echo "Currently available images" && docker image ls
-
- # echo "Will NOT cache images" && docker image ls -f="label=org.testcontainers.sessionId"
-
- # mkdir -p ~/.cache/docker
- # image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q -f="label=org.testcontainers.sessionId" | sort))
- # echo "Saving image ids" && echo $image_ids_to_save
- # docker save $image_ids_to_save -o ~/.cache/docker/images.tar
-
- # ls -lha ~/.cache/docker
-
- # - name: Save docker image cache
- # if: steps.cache.outputs.cache-hit != 'true'
- # uses: actions/cache/save@v6
- # with:
- # path: ~/.cache/docker/
- # key: ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ persistence:
+ strategy:
+ fail-fast: false
+ matrix:
+ project: [persistence-it-hibernate]
+ parallel: [0, 2]
+ pre-start: [false, true]
+ java: [21]
+ include:
+ - project: persistence-it-eclipselink
+ parallel: 2
+ pre-start: true
+ java: 21
+ name: "persistence (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
+ runs-on: ubuntu-latest
+ if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
+ timeout-minutes: 10
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@v7
+
+ - parallel:
+ - name: Set up JDK
+ uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: ${{ matrix.java }}
+
+ - name: Cache Maven
+ uses: actions/cache@v6
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-mvn-it-build-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+
+ - name: Docker Image Cache Restore
+ id: docker-image-cache-restore
+ uses: ./.github/actions/docker-image-cache/restore
+ with:
+ key: it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
+ ${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-
+
+ - name: Setup Buildx
+ id: docker-image-cache-setup-buildx
+ uses: ./.github/actions/docker-image-cache/setup-buildx
+
+ - name: Test
+ run: |
+ ./mvnw -B test \
+ -pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
+ -P run-it \
+ ${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
+ ${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
+ env:
+ ACTIONS_RUNTIME_TOKEN: ${{ steps.docker-image-cache-setup-buildx.outputs.ACTIONS_RUNTIME_TOKEN }}
+ ACTIONS_RESULTS_URL: ${{ steps.docker-image-cache-setup-buildx.outputs.ACTIONS_RESULTS_URL }}
+
+ - name: Docker Image Cache Save
+ uses: ./.github/actions/docker-image-cache/save
+ with:
+ key: ${{ steps.docker-image-cache-restore.outputs.key }}
+ cache-hit: ${{ steps.docker-image-cache-restore.outputs.cache-hit }}
selenium:
strategy:
fail-fast: false
matrix:
project: [webapp-it]
- parallel: [2] # [0, 2]
- pre-start: [true] # [false, true]
+ parallel: [0, 2]
+ pre-start: [false, true]
java: [21]
name: "selenium (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
runs-on: ubuntu-latest
@@ -137,7 +118,7 @@ jobs:
with:
path: ~/.m2/repository
key: ${{ runner.os }}-mvn-it-build-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
-
+
- name: Docker Image Cache Restore
id: docker-image-cache-restore
uses: ./.github/actions/docker-image-cache/restore
@@ -164,7 +145,7 @@ jobs:
env:
ACTIONS_RUNTIME_TOKEN: ${{ steps.docker-image-cache-setup-buildx.outputs.ACTIONS_RUNTIME_TOKEN }}
ACTIONS_RESULTS_URL: ${{ steps.docker-image-cache-setup-buildx.outputs.ACTIONS_RESULTS_URL }}
-
+
- name: Display images
run: |
docker image ls
@@ -198,6 +179,6 @@ jobs:
name: webapp-jacoco-report-${{ matrix.java }}-${{ matrix.project }}-${{ matrix.parallel }}-${{ matrix.pre-start }}
path: advanced-demo/target/site/jacoco-aggregate
if-no-files-found: ignore
-
+
- name: Wait for async jobs to finish
wait-all:
diff --git a/advanced-demo/README.md b/advanced-demo/README.md
index b2a0d41e..170dd252 100644
--- a/advanced-demo/README.md
+++ b/advanced-demo/README.md
@@ -25,3 +25,4 @@ The most interesting project is probably [webapp-it](./integration-tests/webapp-
* Safe starting of named containers:
Is integrated inside ``TCI``. Parts of it only kick in on unexpected container start errors. Can also be observed during tests (with e.g. ``docker stats``) as it names the containers.
* Container leak detection:
Only kicks in when you forget to stop infrastructure after a test. Can be manually reproduced by commenting out the corresponding ``TCI#stop`` methods.
* Tracing:
Seen at the end of each test inside the logs.
+* Caching (using GitHub actions):
Cache configuration and usage can be found in [`run-integration-tests.yml`](../.github/workflows/run-integration-tests.yml).
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 694c2715..c2d8d72a 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -14,8 +14,6 @@
https://xdev.software
-
-
21
${javaVersion}
From 7a4cd416ce751cf9b348e73303206439b6a5153b Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 16:17:45 +0200
Subject: [PATCH 072/141] Remove incorrect comment
---
.github/actions/docker-image-cache/setup-buildx/action.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.github/actions/docker-image-cache/setup-buildx/action.yml b/.github/actions/docker-image-cache/setup-buildx/action.yml
index bb986821..ac66f285 100644
--- a/.github/actions/docker-image-cache/setup-buildx/action.yml
+++ b/.github/actions/docker-image-cache/setup-buildx/action.yml
@@ -1,6 +1,5 @@
name: 'Setup buildx'
description: 'Setup buildx'
-# Based on https://github.com/actions/cache/blob/v6/restore/action.yml
outputs:
ACTIONS_RUNTIME_TOKEN:
value: ${{ steps.actions-vars.outputs.ACTIONS_RUNTIME_TOKEN }}
From ed8b49001001c73274db58d409849dfb7efae15b Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 16:18:39 +0200
Subject: [PATCH 073/141] Update README.md
---
.github/actions/docker-image-cache/README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/actions/docker-image-cache/README.md b/.github/actions/docker-image-cache/README.md
index a54612c9..2e65f24a 100644
--- a/.github/actions/docker-image-cache/README.md
+++ b/.github/actions/docker-image-cache/README.md
@@ -1,5 +1,7 @@
# Composite GitHub Actions
+Example usage can be found in [`run-integration-tests.yml`](../../workflows/run-integration-tests.yml)
+
## Notes
* [Parallel execution](https://github.blog/changelog/2026-06-25-actions-steps-can-now-be-run-in-parallel/) is not yet available for composite actions
* Therefore `restore` and `setup-buildx` are currently split as it's recommended that they be implemented in parallel downstream
From a4ca388f3375129b8a7ff27c376f645de5aa5c2f Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 16:20:15 +0200
Subject: [PATCH 074/141] Re-enable tests
---
.../java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java | 2 --
.../tci/demo/webapp/cases/LongButLittleResourceUsageTest.java | 2 --
2 files changed, 4 deletions(-)
diff --git a/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java
index a44d709e..73487d3a 100644
--- a/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java
+++ b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java
@@ -1,7 +1,6 @@
package software.xdev.tci.demo.webapp.cases;
import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
@@ -10,7 +9,6 @@
import software.xdev.tci.selenium.TestBrowser;
-@Disabled // TODO
class LoginOIDCTest extends InfraPerCaseTest
{
@DisplayName("Check Login and Logout")
diff --git a/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java
index 6db3e816..29614c07 100644
--- a/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java
+++ b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java
@@ -3,7 +3,6 @@
import java.util.stream.IntStream;
import java.util.stream.Stream;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -14,7 +13,6 @@
// These tests were written to show the advantages of PreStarting
-@Disabled // TODO
class LongButLittleResourceUsageTest extends InfraPerCaseTest
{
static Stream simulatedLongTest()
From 0ba2d117fae62b0ce31f88b37d66bbbef7b51168 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 16:20:23 +0200
Subject: [PATCH 075/141] Remove no longer needed Dockerfile
---
advanced-demo/webapp/.dockerignore | 5 --
advanced-demo/webapp/Dockerfile | 85 ------------------------------
2 files changed, 90 deletions(-)
delete mode 100644 advanced-demo/webapp/.dockerignore
delete mode 100644 advanced-demo/webapp/Dockerfile
diff --git a/advanced-demo/webapp/.dockerignore b/advanced-demo/webapp/.dockerignore
deleted file mode 100644
index c84ae3e6..00000000
--- a/advanced-demo/webapp/.dockerignore
+++ /dev/null
@@ -1,5 +0,0 @@
-# by default ignore everything!
-*
-
-# allow only things that should be used in the context
-!target/webapp.jar
diff --git a/advanced-demo/webapp/Dockerfile b/advanced-demo/webapp/Dockerfile
deleted file mode 100644
index 82581fde..00000000
--- a/advanced-demo/webapp/Dockerfile
+++ /dev/null
@@ -1,85 +0,0 @@
-FROM eclipse-temurin:25-jre-alpine AS jre-base
-
-
-# Build the JRE ourself and exclude stuff from Eclipse-Temurin that we don't need
-#
-# Derived from https://github.com/adoptium/containers/blob/91ea190c462741d2c64ed2f8f0a0efdb3e77c49d/21/jre/alpine/3.21/Dockerfile
-FROM alpine:3 AS jre-minimized
-
-ENV JAVA_HOME=/opt/java/openjdk
-ENV PATH=$JAVA_HOME/bin:$PATH
-
-ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8'
-
-RUN set -eux; \
- # DO NOT INSTALL:
- # gnupg - only required to verify download of jre from eclipse-temurin
- # fontconfig ttf-dejavu - No fonts are needed (we also don't use AWT)
- # everything that works with certificates (ca-certificates p11-kit-trust coreutils openssl) - as we don't do stuff with certificates
- # tzdata - We are using UTC (and everyone should do that)
- apk add --no-cache \
- musl-locales musl-locales-lang
-
-COPY --from=jre-base /opt/java/openjdk /opt/java/openjdk
-
-RUN set -eux; \
- echo "Verifying install ..."; \
- echo "java --version"; java --version; \
- echo "Complete."
-
-# Renamed as cacerts functionality is disabled
-COPY --from=jre-base /__cacert_entrypoint.sh /entrypoint.sh
-RUN chmod 775 /entrypoint.sh
-ENTRYPOINT ["/entrypoint.sh"]
-
-
-# See also https://docs.spring.io/spring-boot/reference/packaging/container-images/dockerfiles.html for further information
-FROM jre-minimized AS builder
-
-WORKDIR /builder
-
-COPY target/webapp.jar app.jar
-
-RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted
-
-
-FROM jre-minimized
-
-ARG JACOCO_AGENT_ENABLED
-ARG JACOCO_AGENT_VERSION="0.8.14"
-
-ARG user=app
-ARG group=app
-ARG uid=1000
-ARG gid=1000
-ARG APP_DIR=/opt/app
-
-# Create user + group + home
-RUN mkdir -p ${APP_DIR} \
- && chown ${uid}:${gid} ${APP_DIR} \
- && addgroup -g ${gid} ${group} \
- && adduser -h "$APP_DIR" -u ${uid} -G ${group} -s /bin/bash -D ${user}; \
- # Download JaCoCoAgent if required
- if [ "$JACOCO_AGENT_ENABLED" = "1" ]; then \
- mkdir -p /jacoco/report \
- && chown $uid:$gid /jacoco/report \
- && wget https://repo.maven.apache.org/maven2/org/jacoco/org.jacoco.agent/$JACOCO_AGENT_VERSION/org.jacoco.agent-$JACOCO_AGENT_VERSION-runtime.jar -O /jacoco/agent.jar; \
- fi
-
-WORKDIR ${APP_DIR}
-
-USER ${user}
-
-COPY --from=builder --chown=${user}:${group} /builder/extracted/dependencies/ ./
-COPY --from=builder --chown=${user}:${group} /builder/extracted/spring-boot-loader/ ./
-COPY --from=builder --chown=${user}:${group} /builder/extracted/snapshot-dependencies/ ./
-COPY --from=builder --chown=${user}:${group} /builder/extracted/application/ ./
-
-# MaxRAMPercentage: Default value is 25% -> we want to use available memory optimal -> increased, but enough is left for other RAM usages like e.g. Metaspace
-# Min/MaxHeapFreeRatio: Default values cause container reserved memory not to shrink properly/waste memory -> decreased
-# https://stackoverflow.com/questions/16058250/what-is-the-purpose-of-xxminheapfreeratio-and-xxmaxheapfreeratio
-ENV JAVA_OPTS="-XX:MaxRAMPercentage=75 -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=30 -Djava.awt.headless=true"
-
-EXPOSE 8080
-
-CMD [ "/bin/sh", "-c", "java $JAVA_OPTS -jar app.jar" ]
From 934e934126ccca5ae837f4c31185663b048e380f Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 16:29:42 +0200
Subject: [PATCH 076/141] Update docs
---
.../actions/docker-image-cache/restore/README.md | 1 +
.github/actions/docker-image-cache/save/README.md | 1 +
.../docker-image-cache/setup-buildx/README.md | 1 +
CHANGELOG.md | 13 +++++++++++++
README.md | 3 +++
5 files changed, 19 insertions(+)
create mode 100644 .github/actions/docker-image-cache/restore/README.md
create mode 100644 .github/actions/docker-image-cache/save/README.md
create mode 100644 .github/actions/docker-image-cache/setup-buildx/README.md
diff --git a/.github/actions/docker-image-cache/restore/README.md b/.github/actions/docker-image-cache/restore/README.md
new file mode 100644
index 00000000..5f6ad4c8
--- /dev/null
+++ b/.github/actions/docker-image-cache/restore/README.md
@@ -0,0 +1 @@
+Restores previously downloaded images so that they don't need to be pulled
diff --git a/.github/actions/docker-image-cache/save/README.md b/.github/actions/docker-image-cache/save/README.md
new file mode 100644
index 00000000..6eef1df3
--- /dev/null
+++ b/.github/actions/docker-image-cache/save/README.md
@@ -0,0 +1 @@
+Saves the pulled images into the cache
diff --git a/.github/actions/docker-image-cache/setup-buildx/README.md b/.github/actions/docker-image-cache/setup-buildx/README.md
new file mode 100644
index 00000000..09e54fd5
--- /dev/null
+++ b/.github/actions/docker-image-cache/setup-buildx/README.md
@@ -0,0 +1 @@
+Set's up the buildx worker and returns [required environment variables](https://docs.docker.com/build/cache/backends/gha/#authentication) for the buildx build.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c407230..09736482 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,16 @@
+# 4.0.0
+* Added `image-build` module
+ * This module utilizes v4 of [testcontainers-advanced-imagebuilder](https://github.com/xdev-software/testcontainers-advanced-imagebuilder)
+ * It contains some shortcuts that help with common configuration e.g. related to caching (see below)
+* Added dedicated composite actions for caching
+ * These can be found in `.github/actions/docker-image-cache`
+ * Currently 3 actions exist:
+ * `restore` - Restores the previously downloaded images
+ * `setup-buildx` - Set's up the buildx worker and returns required environment variables
+ * `save` - Saves the downloaded images
+* Updated demo
+* Fixed a deadlock during service resolution
+
# 3.5.0
* `db-jdbc-spring-orm` & implementations
* No longer use `setPersistenceProviderClassName` because it has no effect
diff --git a/README.md b/README.md
index bef64902..761ce302 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,9 @@ Modules for XDEV's Testcontainer Infrastructure Framework
* Data-generation template
* Improved JDBC Container wait strategy
* Predefined implementations for [Spring-ORM](./db-jdbc-spring-orm/), [Hibernate](./db-jdbc-spring-orm-hibernate/) and [EclipseLink](./db-jdbc-spring-orm-eclipselink/)
+* [image-build](./image-build/)
+ * Contains some shortcuts that help with common build configuration e.g. related to caching
+ * Designed to work together with the [`docker-image-cache` actions](./.github/actions/docker-image-cache/)
* [jacoco](./jacoco/)
* Allows for recording of JaCoCo code coverage files with Java containers
* [jul-to-slf4j](./jul-to-slf4j/)
From 296683ce98911b4e0823990c6bfd5009554bc484 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 16:31:23 +0200
Subject: [PATCH 077/141] Bump and consolidate versions
---
CHANGELOG.md | 6 ++--
advanced-demo/entities-metamodel/pom.xml | 2 +-
advanced-demo/entities/pom.xml | 2 +-
.../persistence-it-eclipselink/pom.xml | 2 +-
.../persistence-it-hibernate/pom.xml | 2 +-
.../integration-tests/persistence-it/pom.xml | 2 +-
advanced-demo/integration-tests/pom.xml | 10 +++----
.../integration-tests/tci-db/pom.xml | 2 +-
.../integration-tests/tci-webapp/pom.xml | 2 +-
.../integration-tests/webapp-it/pom.xml | 2 +-
advanced-demo/persistence/pom.xml | 2 +-
advanced-demo/pom.xml | 8 +++---
advanced-demo/webapp/pom.xml | 2 +-
base-demo/pom.xml | 4 +--
base/pom.xml | 2 +-
bom/pom.xml | 28 +++++++++----------
db-jdbc-spring-orm-eclipselink/pom.xml | 4 +--
db-jdbc-spring-orm-hibernate/pom.xml | 4 +--
db-jdbc-spring-orm/pom.xml | 4 +--
db-jdbc/pom.xml | 4 +--
image-build/pom.xml | 4 +--
jacoco/pom.xml | 4 +--
jul-to-slf4j/pom.xml | 2 +-
junit-jupiter-api-support/pom.xml | 2 +-
mockserver/pom.xml | 4 +--
oidc-server-mock/pom.xml | 4 +--
pom.xml | 2 +-
selenium/pom.xml | 8 +++---
spring-dao-support/pom.xml | 4 +--
29 files changed, 63 insertions(+), 65 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09736482..bd93637a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,14 +8,12 @@
* `restore` - Restores the previously downloaded images
* `setup-buildx` - Set's up the buildx worker and returns required environment variables
* `save` - Saves the downloaded images
-* Updated demo
-* Fixed a deadlock during service resolution
-
-# 3.5.0
* `db-jdbc-spring-orm` & implementations
* No longer use `setPersistenceProviderClassName` because it has no effect
* Performance: Reuse `PersistenceProvider`
+* Fixed a deadlock during service resolution
* Updated dependencies
+* Updated demo
# 3.4.1
* Remove dedicated code for Java 17 (no longer needed because Java 21 is the minimum)
diff --git a/advanced-demo/entities-metamodel/pom.xml b/advanced-demo/entities-metamodel/pom.xml
index 73d24334..d9ffd8cc 100644
--- a/advanced-demo/entities-metamodel/pom.xml
+++ b/advanced-demo/entities-metamodel/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo
advanced-demo
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
entities-metamodel
diff --git a/advanced-demo/entities/pom.xml b/advanced-demo/entities/pom.xml
index c5975091..4dd287e0 100644
--- a/advanced-demo/entities/pom.xml
+++ b/advanced-demo/entities/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo
advanced-demo
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
entities
diff --git a/advanced-demo/integration-tests/persistence-it-eclipselink/pom.xml b/advanced-demo/integration-tests/persistence-it-eclipselink/pom.xml
index 0062e2db..74533ccd 100644
--- a/advanced-demo/integration-tests/persistence-it-eclipselink/pom.xml
+++ b/advanced-demo/integration-tests/persistence-it-eclipselink/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo.it
integration-tests
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
persistence-it-eclipselink
diff --git a/advanced-demo/integration-tests/persistence-it-hibernate/pom.xml b/advanced-demo/integration-tests/persistence-it-hibernate/pom.xml
index 8728d38d..cd86b3ca 100644
--- a/advanced-demo/integration-tests/persistence-it-hibernate/pom.xml
+++ b/advanced-demo/integration-tests/persistence-it-hibernate/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo.it
integration-tests
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
persistence-it-hibernate
diff --git a/advanced-demo/integration-tests/persistence-it/pom.xml b/advanced-demo/integration-tests/persistence-it/pom.xml
index b3b2f7d8..73fd57cd 100644
--- a/advanced-demo/integration-tests/persistence-it/pom.xml
+++ b/advanced-demo/integration-tests/persistence-it/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo.it
integration-tests
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
persistence-it
diff --git a/advanced-demo/integration-tests/pom.xml b/advanced-demo/integration-tests/pom.xml
index f3278c28..07788024 100644
--- a/advanced-demo/integration-tests/pom.xml
+++ b/advanced-demo/integration-tests/pom.xml
@@ -7,12 +7,12 @@
software.xdev.tci.demo
advanced-demo
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci.demo.it
integration-tests
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
pom
@@ -30,7 +30,7 @@
software.xdev.tci.demo.it
persistence-it
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
test-jar
tests
@@ -38,13 +38,13 @@
software.xdev.tci.demo.it
tci-db
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci.demo.it
tci-webapp
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
diff --git a/advanced-demo/integration-tests/tci-db/pom.xml b/advanced-demo/integration-tests/tci-db/pom.xml
index a48ccef7..321c5647 100644
--- a/advanced-demo/integration-tests/tci-db/pom.xml
+++ b/advanced-demo/integration-tests/tci-db/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo.it
integration-tests
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
tci-db
diff --git a/advanced-demo/integration-tests/tci-webapp/pom.xml b/advanced-demo/integration-tests/tci-webapp/pom.xml
index e745257f..2ddfffcd 100644
--- a/advanced-demo/integration-tests/tci-webapp/pom.xml
+++ b/advanced-demo/integration-tests/tci-webapp/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo.it
integration-tests
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
tci-webapp
diff --git a/advanced-demo/integration-tests/webapp-it/pom.xml b/advanced-demo/integration-tests/webapp-it/pom.xml
index 1cfe501e..1e713731 100644
--- a/advanced-demo/integration-tests/webapp-it/pom.xml
+++ b/advanced-demo/integration-tests/webapp-it/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo.it
integration-tests
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
webapp-it
diff --git a/advanced-demo/persistence/pom.xml b/advanced-demo/persistence/pom.xml
index e9fb9f28..363a9c36 100644
--- a/advanced-demo/persistence/pom.xml
+++ b/advanced-demo/persistence/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo
advanced-demo
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
persistence
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index c2d8d72a..7b2e3021 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci.demo
advanced-demo
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
pom
@@ -38,19 +38,19 @@
software.xdev.tci.demo
entities
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci.demo
entities-metamodel
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci.demo
persistence
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
diff --git a/advanced-demo/webapp/pom.xml b/advanced-demo/webapp/pom.xml
index 4a0f2d57..8c853a8c 100644
--- a/advanced-demo/webapp/pom.xml
+++ b/advanced-demo/webapp/pom.xml
@@ -7,7 +7,7 @@
software.xdev.tci.demo
advanced-demo
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
webapp
diff --git a/base-demo/pom.xml b/base-demo/pom.xml
index d755ccd3..4cad8df5 100644
--- a/base-demo/pom.xml
+++ b/base-demo/pom.xml
@@ -7,11 +7,11 @@
software.xdev.tci
root
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
base-demo
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
diff --git a/base/pom.xml b/base/pom.xml
index 51b2e686..42c60a2d 100644
--- a/base/pom.xml
+++ b/base/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
base
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
base
diff --git a/bom/pom.xml b/bom/pom.xml
index fa5a77d4..7f6e1f83 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
bom
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
pom
bom
@@ -51,67 +51,67 @@
software.xdev.tci
base
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
db-jdbc
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
db-jdbc-spring-orm
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
db-jdbc-spring-orm-eclipselink
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
db-jdbc-spring-orm-hibernate
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
image-build
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
jacoco
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
jul-to-slf4j
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
junit-jupiter-api-support
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
mockserver
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
oidc-server-mock
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
selenium
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
spring-dao-support
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
diff --git a/db-jdbc-spring-orm-eclipselink/pom.xml b/db-jdbc-spring-orm-eclipselink/pom.xml
index 61a4205c..a7689a23 100644
--- a/db-jdbc-spring-orm-eclipselink/pom.xml
+++ b/db-jdbc-spring-orm-eclipselink/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
db-jdbc-spring-orm-eclipselink
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
db-jdbc-spring-orm-eclipselink
@@ -53,7 +53,7 @@
software.xdev.tci
db-jdbc-spring-orm
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
diff --git a/db-jdbc-spring-orm-hibernate/pom.xml b/db-jdbc-spring-orm-hibernate/pom.xml
index 050ae61c..fdc1bca8 100644
--- a/db-jdbc-spring-orm-hibernate/pom.xml
+++ b/db-jdbc-spring-orm-hibernate/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
db-jdbc-spring-orm-hibernate
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
db-jdbc-spring-orm-hibernate
@@ -53,7 +53,7 @@
software.xdev.tci
db-jdbc-spring-orm
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
diff --git a/db-jdbc-spring-orm/pom.xml b/db-jdbc-spring-orm/pom.xml
index 2cac28e2..c19b8c88 100644
--- a/db-jdbc-spring-orm/pom.xml
+++ b/db-jdbc-spring-orm/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
db-jdbc-spring-orm
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
db-jdbc-spring-orm
@@ -53,7 +53,7 @@
software.xdev.tci
db-jdbc
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
diff --git a/db-jdbc/pom.xml b/db-jdbc/pom.xml
index e8bcad86..c1fbbc64 100644
--- a/db-jdbc/pom.xml
+++ b/db-jdbc/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
db-jdbc
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
db-jdbc
@@ -53,7 +53,7 @@
software.xdev.tci
base
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
diff --git a/image-build/pom.xml b/image-build/pom.xml
index 8ab64c14..093e6939 100644
--- a/image-build/pom.xml
+++ b/image-build/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
image-build
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
image-build
@@ -53,7 +53,7 @@
software.xdev.tci
base
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev
diff --git a/jacoco/pom.xml b/jacoco/pom.xml
index 3aa36b09..14a24c07 100644
--- a/jacoco/pom.xml
+++ b/jacoco/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
jacoco
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
jacoco
@@ -53,7 +53,7 @@
software.xdev.tci
base
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
diff --git a/jul-to-slf4j/pom.xml b/jul-to-slf4j/pom.xml
index 6e29cfe9..c7018e70 100644
--- a/jul-to-slf4j/pom.xml
+++ b/jul-to-slf4j/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
jul-to-slf4j
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
jul-to-slf4j
diff --git a/junit-jupiter-api-support/pom.xml b/junit-jupiter-api-support/pom.xml
index 3d7058bf..12fb28f2 100644
--- a/junit-jupiter-api-support/pom.xml
+++ b/junit-jupiter-api-support/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
junit-jupiter-api-support
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
junit-jupiter-api-support
diff --git a/mockserver/pom.xml b/mockserver/pom.xml
index ebf8fc6e..f5740aa8 100644
--- a/mockserver/pom.xml
+++ b/mockserver/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
mockserver
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
mockserver
@@ -65,7 +65,7 @@
software.xdev.tci
base
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
diff --git a/oidc-server-mock/pom.xml b/oidc-server-mock/pom.xml
index a1727262..b3dd16a4 100644
--- a/oidc-server-mock/pom.xml
+++ b/oidc-server-mock/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
oidc-server-mock
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
oidc-server-mock
@@ -53,7 +53,7 @@
software.xdev.tci
base
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
diff --git a/pom.xml b/pom.xml
index 7525bc1f..496b07d5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
root
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
pom
diff --git a/selenium/pom.xml b/selenium/pom.xml
index 98e1c7c5..e94800ab 100644
--- a/selenium/pom.xml
+++ b/selenium/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
selenium
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
selenium
@@ -65,18 +65,18 @@
software.xdev.tci
base
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
jul-to-slf4j
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
software.xdev.tci
junit-jupiter-api-support
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
diff --git a/spring-dao-support/pom.xml b/spring-dao-support/pom.xml
index 397faa81..25d18c4a 100644
--- a/spring-dao-support/pom.xml
+++ b/spring-dao-support/pom.xml
@@ -6,7 +6,7 @@
software.xdev.tci
spring-dao-support
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
jar
spring-dao-support
@@ -53,7 +53,7 @@
software.xdev.tci
db-jdbc
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
From 7709d9d0dc2d71a47e4af107260a77fe316dfec7 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 16:36:06 +0200
Subject: [PATCH 078/141] Remove debugging bits
---
.github/actions/docker-image-cache/restore/action.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/actions/docker-image-cache/restore/action.yml b/.github/actions/docker-image-cache/restore/action.yml
index 2f160056..80ae7e7d 100644
--- a/.github/actions/docker-image-cache/restore/action.yml
+++ b/.github/actions/docker-image-cache/restore/action.yml
@@ -41,4 +41,4 @@ runs:
printf "%s\n" "${preinstalled_image_ids[@]}" > ~/.cache/docker-preinstalled/ids.txt
echo "Importing cache"
- time docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
+ docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
From 86c7eded1e15b527f228840bac3e567473bd8263 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 16:39:48 +0200
Subject: [PATCH 079/141] Show images for easier debugging
---
.github/workflows/run-integration-tests.yml | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 7680791e..87a0e99c 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -83,6 +83,9 @@ jobs:
ACTIONS_RUNTIME_TOKEN: ${{ steps.docker-image-cache-setup-buildx.outputs.ACTIONS_RUNTIME_TOKEN }}
ACTIONS_RESULTS_URL: ${{ steps.docker-image-cache-setup-buildx.outputs.ACTIONS_RESULTS_URL }}
+ - name: Show images
+ run: docker image ls
+
- name: Docker Image Cache Save
uses: ./.github/actions/docker-image-cache/save
with:
@@ -146,9 +149,8 @@ jobs:
ACTIONS_RUNTIME_TOKEN: ${{ steps.docker-image-cache-setup-buildx.outputs.ACTIONS_RUNTIME_TOKEN }}
ACTIONS_RESULTS_URL: ${{ steps.docker-image-cache-setup-buildx.outputs.ACTIONS_RESULTS_URL }}
- - name: Display images
- run: |
- docker image ls
+ - name: Show images
+ run: docker image ls
- name: Docker Image Cache Save
uses: ./.github/actions/docker-image-cache/save
From b316500be57b0895615320dc46089826bd64ce0a Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 16:40:20 +0200
Subject: [PATCH 080/141] Speed up test a bit
---
.../tci/demo/webapp/cases/LongButLittleResourceUsageTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java
index 29614c07..e5959b8f 100644
--- a/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java
+++ b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java
@@ -33,7 +33,7 @@ void simulatedLongTest(final TestBrowser browser)
try
{
- Thread.sleep(20_000);
+ Thread.sleep(15_000);
}
catch(final InterruptedException iex)
{
From cbfb7e5e13c7bfe66b142d09e7c9a43001075796 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 17:07:06 +0200
Subject: [PATCH 081/141] Demo: Use AOT
Should improve image startup speed
---
.../integration-tests/tci-webapp/Dockerfile | 45 ++++++-
.../containers/WebAppContainerBuilder.java | 119 +++++++++---------
2 files changed, 103 insertions(+), 61 deletions(-)
diff --git a/advanced-demo/integration-tests/tci-webapp/Dockerfile b/advanced-demo/integration-tests/tci-webapp/Dockerfile
index e8bb371f..f17232a4 100644
--- a/advanced-demo/integration-tests/tci-webapp/Dockerfile
+++ b/advanced-demo/integration-tests/tci-webapp/Dockerfile
@@ -74,6 +74,8 @@ RUN mv webapp/target/webapp.jar app.jar \
FROM jre-minimized
+ARG ENABLE_AOT
+
ARG JACOCO_AGENT_ENABLED
ARG JACOCO_AGENT_VERSION="0.8.14"
@@ -110,9 +112,48 @@ COPY --from=builder --chown=${user}:${group} /builder/extracted/snapshot-depende
RUN true
COPY --from=builder --chown=${user}:${group} /builder/extracted/application/ ./
+# RUN AOT and boot with dummy configuration
+RUN if [[ ! -z "$ENABLE_AOT" ]]; then \
+ java \
+ -XX:+UseG1GC \
+ -XX:+UseCompactObjectHeaders \
+ -XX:+UseStringDeduplication \
+ -XX:AOTCacheOutput=app.aot \
+ -Djava.awt.headless=true \
+ -Dspring.context.exit=onRefresh \
+ # Disable Flyway
+ -Dspring.flyway.enabled=false \
+ # Disabled DB actuator endpoint
+ -Dmanagement.health.db.enabled=false \
+ # Disbale DB instant connection
+ -Dspring.jpa.properties.jakarta.persistence.database-product-name=MariaDB \
+ -Dspring.jpa.properties.jakarta.persistence.database-major-version=11 \
+ -Dspring.jpa.properties.jakarta.persistence.database-minor-version=8 \
+ -Dspring.jpa.properties.hibernate.boot.allow.jdbc.metadata.access=false \
+ # Fake DB
+ -Dspring.datasource.url=jdbc:mariadb://localhost:3306/dummy \
+ -Dspring.datasource.username=dummy \
+ -Dspring.datasource.password=dummy \
+ # Fake Auth
+ -Dspring.security.oauth2.client.registration.local.authorization-grant-type=authorization_code \
+ -Dspring.security.oauth2.client.registration.local.redirect-uri="{baseUrl}/{action}/oauth2/code/{registrationId}" \
+ -Dspring.security.oauth2.client.provider.local.authorization-uri=http://localhost/connect/authorize \
+ -Dspring.security.oauth2.client.provider.local.token-uri=http://localhost/connect/token \
+ -Dspring.security.oauth2.client.provider.local.jwk-set-uri=http://localhost/.well-known/openid-configuration/jwks \
+ -Dspring.security.oauth2.client.provider.local.user-info-uri=http://localhost/connect/userinfo \
+ -Dspring.security.oauth2.client.provider.local.user-info-authentication-method=header \
+ -Dspring.security.oauth2.client.provider.local.user-name-attribute=sub \
+ -Dspring.security.oauth2.client.registration.local.client-name=Local \
+ -Dspring.security.oauth2.client.registration.local.client-id=dummy \
+ -Dspring.security.oauth2.client.registration.local.client-secret=dummy \
+ -Dspring.security.oauth2.client.registration.local.scope="openid,profile,email,offline_access" \
+ -jar app.jar; \
+ fi
+
ENV JAVA_JACOCO_OPTS=${JACOCO_AGENT_ENABLED:+"-javaagent:/jacoco/agent.jar=destfile=/jacoco/report/jacoco.exec"}
-ENV JAVA_OPTS="-XX:MaxRAMPercentage=75 -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=30 -XX:+UseCompactObjectHeaders -Djava.awt.headless=true"
+ENV JAVA_OPTS="-XX:+UseG1GC -XX:MaxRAMPercentage=75 -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=30 -XX:+UseCompactObjectHeaders -XX:+UseStringDeduplication -Djava.awt.headless=true"
+ENV JAVA_AOT_OPTS=${ENABLE_AOT:+"-XX:AOTCache=app.aot"}
EXPOSE 8080
-CMD [ "/bin/sh", "-c", "java $JAVA_OPTS $JAVA_JACOCO_OPTS -jar /opt/app/app.jar" ]
+CMD [ "/bin/sh", "-c", "java $JAVA_OPTS $JAVA_JACOCO_OPTS $JAVA_AOT_OPTS -jar /opt/app/app.jar" ]
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
index 541d5ca8..2e90ddf6 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
@@ -29,66 +29,67 @@ public static String getImageName()
LOG.info("Building WebApp-DockerImage...");
final NativeAdvancedImageFromDockerfile builder = ImageCreator.nativeImage("tci-demo-webapp")
- .withDockerFilePath(Paths.get("../../integration-tests/tci-webapp/Dockerfile"))
- .withBaseDir(Paths.get("../../"))
- .configureFilesToTransferHandler(h -> h
- .withPostGitIgnoreLines(
- // Ignore git-folder, as it will be provided in the Dockerfile
- ".git/**",
- // Ignore other unused folders and extensions
- "*.iml",
- "*.cmd",
- "*.md",
- "_dev_infra/**",
- "_resource_metrics/**",
- // Ignore other Dockerfiles (our required file will always be transferred)
- "Dockerfile",
- // Ignore not required test-modules that may have changed
- // sources only - otherwise the parent pom doesn't find the resources
- "integration-tests/**",
- "**/src/test/**",
- // Ignore resources that are just used for development
- "webapp/src/main/resources-dev/**",
- // Most files from these folders need to be ignored -> Down there for highest prio
- "node_modules",
- "target")
- // File is in root directory - we can't access it
- .withBaseDirRelativeIgnoreFile(null)
- .withDockerFileLinesModifier(new DockerfileCOPYParentsEmulator())
- .withTransferArchiveTARCompressorCustomizer(c -> c
- // Rewrite parent pom to exclude integration tests
- // This way changes in test pom's cause no redownload of dependencies
- .withContentModifier(new FileLinesContentModifier()
+ .withBuildArg("ENABLE_AOT", "1")
+ .withDockerFilePath(Paths.get("../../integration-tests/tci-webapp/Dockerfile"))
+ .withBaseDir(Paths.get("../../"))
+ .configureFilesToTransferHandler(h -> h
+ .withPostGitIgnoreLines(
+ // Ignore git-folder, as it will be provided in the Dockerfile
+ ".git/**",
+ // Ignore other unused folders and extensions
+ "*.iml",
+ "*.cmd",
+ "*.md",
+ "_dev_infra/**",
+ "_resource_metrics/**",
+ // Ignore other Dockerfiles (our required file will always be transferred)
+ "Dockerfile",
+ // Ignore not required test-modules that may have changed
+ // sources only - otherwise the parent pom doesn't find the resources
+ "integration-tests/**",
+ "**/src/test/**",
+ // Ignore resources that are just used for development
+ "webapp/src/main/resources-dev/**",
+ // Most files from these folders need to be ignored -> Down there for highest prio
+ "node_modules",
+ "target")
+ // File is in root directory - we can't access it
+ .withBaseDirRelativeIgnoreFile(null)
+ .withDockerFileLinesModifier(new DockerfileCOPYParentsEmulator())
+ .withTransferArchiveTARCompressorCustomizer(c -> c
+ // Rewrite parent pom to exclude integration tests
+ // This way changes in test pom's cause no redownload of dependencies
+ .withContentModifier(new FileLinesContentModifier()
+ {
+ @Override
+ public boolean shouldApply(
+ final Path sourcePath,
+ final String targetPath,
+ final TarArchiveEntry tarArchiveEntry)
{
- @Override
- public boolean shouldApply(
- final Path sourcePath,
- final String targetPath,
- final TarArchiveEntry tarArchiveEntry)
- {
- return "pom.xml".equals(targetPath);
- }
-
- @Override
- public List modify(
- final List lines,
- final Path sourcePath,
- final String targetPath,
- final TarArchiveEntry tarArchiveEntry)
- {
- return lines.stream()
- // Remove integration tests module
- .filter(s -> !s.contains("integration-tests"))
- .toList();
- }
-
- @Override
- public boolean isIdentical(final List original, final List created)
- {
- return original.size() == created.size();
- }
- }))
- );
+ return "pom.xml".equals(targetPath);
+ }
+
+ @Override
+ public List modify(
+ final List lines,
+ final Path sourcePath,
+ final String targetPath,
+ final TarArchiveEntry tarArchiveEntry)
+ {
+ return lines.stream()
+ // Remove integration tests module
+ .filter(s -> !s.contains("integration-tests"))
+ .toList();
+ }
+
+ @Override
+ public boolean isIdentical(final List original, final List created)
+ {
+ return original.size() == created.size();
+ }
+ }))
+ );
if(JaCoCoConfig.instance().enabled())
{
From 6507b51718c448aebda3c7413f998f7844312413 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 17:13:50 +0200
Subject: [PATCH 082/141] Try fix AOT error
---
advanced-demo/integration-tests/tci-webapp/Dockerfile | 1 +
1 file changed, 1 insertion(+)
diff --git a/advanced-demo/integration-tests/tci-webapp/Dockerfile b/advanced-demo/integration-tests/tci-webapp/Dockerfile
index f17232a4..e309ad3e 100644
--- a/advanced-demo/integration-tests/tci-webapp/Dockerfile
+++ b/advanced-demo/integration-tests/tci-webapp/Dockerfile
@@ -119,6 +119,7 @@ RUN if [[ ! -z "$ENABLE_AOT" ]]; then \
-XX:+UseCompactObjectHeaders \
-XX:+UseStringDeduplication \
-XX:AOTCacheOutput=app.aot \
+ ${JACOCO_AGENT_ENABLED:+"-javaagent:/jacoco/agent.jar=destfile=/jacoco/report/jacoco.exec"} \
-Djava.awt.headless=true \
-Dspring.context.exit=onRefresh \
# Disable Flyway
From 88ede5bf3057ebe341e96d3818c4f05d1d268079 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 17:22:27 +0200
Subject: [PATCH 083/141] Revert "Try fix AOT error"
This reverts commit 6507b51718c448aebda3c7413f998f7844312413.
---
advanced-demo/integration-tests/tci-webapp/Dockerfile | 1 -
1 file changed, 1 deletion(-)
diff --git a/advanced-demo/integration-tests/tci-webapp/Dockerfile b/advanced-demo/integration-tests/tci-webapp/Dockerfile
index e309ad3e..f17232a4 100644
--- a/advanced-demo/integration-tests/tci-webapp/Dockerfile
+++ b/advanced-demo/integration-tests/tci-webapp/Dockerfile
@@ -119,7 +119,6 @@ RUN if [[ ! -z "$ENABLE_AOT" ]]; then \
-XX:+UseCompactObjectHeaders \
-XX:+UseStringDeduplication \
-XX:AOTCacheOutput=app.aot \
- ${JACOCO_AGENT_ENABLED:+"-javaagent:/jacoco/agent.jar=destfile=/jacoco/report/jacoco.exec"} \
-Djava.awt.headless=true \
-Dspring.context.exit=onRefresh \
# Disable Flyway
From a04d4b6fcf22cc0345da6865b94ff5289f340e76 Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 17:30:56 +0200
Subject: [PATCH 084/141] Update run-integration-tests.yml
---
.github/workflows/run-integration-tests.yml | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 87a0e99c..b34b0566 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -100,6 +100,11 @@ jobs:
parallel: [0, 2]
pre-start: [false, true]
java: [21]
+ # Only enable jacoco on the fastest builds because AOT is not fully working with it
+ include:
+ - project: webapp-it
+ parallel: 2
+ jacoco: [true]
name: "selenium (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
runs-on: ubuntu-latest
if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
@@ -139,7 +144,7 @@ jobs:
run: |
./mvnw -B test \
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
- -P run-it,jacoco \
+ -P run-it${{ matrix.jacoco && ',jacoco' || '' }} \
-Dtci.image-build.delete-on-exit=true \
-Dtci.image-build.cache-from="type=gha,scope=§image" \
-Dtci.image-build.cache-to="type=gha,mode=max,scope=§image" \
@@ -166,16 +171,15 @@ jobs:
name: test-fail-videos-${{ matrix.java }}-${{ matrix.project }}-${{ matrix.parallel }}-${{ matrix.pre-start }}
path: advanced-demo/integration-tests/${{ matrix.project }}/target/records
if-no-files-found: ignore
- background: true
- name: Aggregate WebApp JaCoCo report
- if: ${{ success() }}
+ if: ${{ matrix.jacoco && success() }}
# Important: Also run compile (if not already done) so that we have all class files - otherwise the report will be incomplete
run: |
./mvnw -B compile jacoco-aggregator:report-aggregate-all -pl "advanced-demo/webapp" -am -T2C
- name: Upload WebApp JaCoCo report
- if: ${{ success() }}
+ if: ${{ matrix.jacoco && success() }}
uses: actions/upload-artifact@v7
with:
name: webapp-jacoco-report-${{ matrix.java }}-${{ matrix.project }}-${{ matrix.parallel }}-${{ matrix.pre-start }}
From 917078a5f8597df1d27cb264f488033f6279fe7a Mon Sep 17 00:00:00 2001
From: AB
Date: Thu, 9 Jul 2026 17:42:43 +0200
Subject: [PATCH 085/141] Docs
---
.../tci/demo/tci/webapp/containers/WebAppContainerBuilder.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
index 2e90ddf6..5eb7fe74 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
@@ -29,6 +29,7 @@ public static String getImageName()
LOG.info("Building WebApp-DockerImage...");
final NativeAdvancedImageFromDockerfile builder = ImageCreator.nativeImage("tci-demo-webapp")
+ // NOTE: AOT can't be used properly when JaCoCo is active
.withBuildArg("ENABLE_AOT", "1")
.withDockerFilePath(Paths.get("../../integration-tests/tci-webapp/Dockerfile"))
.withBaseDir(Paths.get("../../"))
From d63c00b8a676f0eb05f6180e0c8621f98f539367 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 10 Jul 2026 08:45:16 +0200
Subject: [PATCH 086/141] Improve pre-installed image handling
---
.../docker-image-cache/restore/action.yml | 17 ++++++++++-------
.../actions/docker-image-cache/save/action.yml | 13 ++++++++++---
2 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/.github/actions/docker-image-cache/restore/action.yml b/.github/actions/docker-image-cache/restore/action.yml
index 80ae7e7d..528c714e 100644
--- a/.github/actions/docker-image-cache/restore/action.yml
+++ b/.github/actions/docker-image-cache/restore/action.yml
@@ -8,6 +8,9 @@ inputs:
restore-keys:
description: 'An ordered multiline string listing the prefix-matched keys, that are used for restoring stale cache if no cache hit occurred for key. Note `cache-hit` returns false in this case.'
required: false
+ save-preinstalled-images:
+ description: 'Should pre-installed images be saved (not recommended)'
+ required: false
outputs:
key:
description: 'Identical to the input - can be used to prevent copy pasting'
@@ -18,8 +21,6 @@ outputs:
runs:
using: "composite"
steps:
- # RESTORE "NORMAL" DOCKER IMAGE CACHE
-
# NOTE 2026-07-09 Parallel execution is not yet available for composite actions
# https://github.blog/changelog/2026-06-25-actions-steps-can-now-be-run-in-parallel/
- name: Restore docker image cache
@@ -33,12 +34,14 @@ runs:
- name: Load docker image cache
shell: bash
run: |
- echo "Getting preinstalled images" && docker image ls
- preinstalled_image_ids=$(docker images -q | sort)
+ if [ "${{ inputs.save-preinstalled-images }}" != true ]; then
+ echo "Getting preinstalled images" && docker image ls
+ preinstalled_image_ids=$(docker images -q | sort)
- echo "Storing preinstalled image ids" && echo $preinstalled_image_ids
- mkdir -p ~/.cache/docker-preinstalled
- printf "%s\n" "${preinstalled_image_ids[@]}" > ~/.cache/docker-preinstalled/ids.txt
+ echo "Storing preinstalled image ids" && echo $preinstalled_image_ids
+ mkdir -p ~/.cache/docker-preinstalled
+ printf "%s\n" "${preinstalled_image_ids[@]}" > ~/.cache/docker-preinstalled/ids-${{ github.run_id }}.txt
+ fi
echo "Importing cache"
docker load -i ~/.cache/docker/images.tar && echo "Restored docker image cache" && docker image ls || true
diff --git a/.github/actions/docker-image-cache/save/action.yml b/.github/actions/docker-image-cache/save/action.yml
index d9e3b341..4d21716d 100644
--- a/.github/actions/docker-image-cache/save/action.yml
+++ b/.github/actions/docker-image-cache/save/action.yml
@@ -20,19 +20,26 @@ runs:
docker image prune -f
echo "Currently available images" && docker image ls
- if [ -f ~/.cache/docker-preinstalled/ids.txt ]; then
- echo && echo "Will NOT cache pre-installed images" && cat ~/.cache/docker-preinstalled/ids.txt
+ has_preinstalled_ids=false
+ if [ -f ~/.cache/docker-preinstalled/ids-${{ github.run_id }}.txt ]; then
+ echo && echo "Will NOT cache pre-installed images" && cat ~/.cache/docker-preinstalled/ids-${{ github.run_id }}.txt
+ has_preinstalled_ids=true
fi
echo && echo "Will NOT cache images" && docker image ls ${CI_CACHE_DOCKER_SAVE_IGNORE_FILTERS:--f="label=org.testcontainers.sessionId"}
mkdir -p ~/.cache/docker
image_ids_to_save=$(comm -23 <(docker images -q | sort) <(docker image ls -q ${CI_CACHE_DOCKER_SAVE_IGNORE_FILTERS:--f="label=org.testcontainers.sessionId"} | sort))
- image_ids_to_save=$(comm -23 <(echo "${image_ids_to_save[@]}") <(cat ~/.cache/docker-preinstalled/ids.txt || echo ""))
+ if [ "$has_preinstalled_ids" = true ]; then
+ image_ids_to_save=$(comm -23 <(echo "${image_ids_to_save[@]}") <(cat ~/.cache/docker-preinstalled/ids-${{ github.run_id }}.txt || echo ""))
+ fi
echo && echo "Saving image ids" && echo $image_ids_to_save
docker save $image_ids_to_save -o ~/.cache/docker/images.tar
ls -lha ~/.cache/docker
+ if [ "$has_preinstalled_ids" = true ]; then
+ rm -f ~/.cache/docker-preinstalled/ids-${{ github.run_id }}.txt || true
+ fi
- name: Save docker image cache
if: inputs.cache-hit != 'true'
From aded27f213ea3cac4bcd6713e7deaf215795561d Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 10 Jul 2026 09:17:52 +0200
Subject: [PATCH 087/141] Update JaCoCo agent to latest version
---
advanced-demo/integration-tests/tci-webapp/Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/advanced-demo/integration-tests/tci-webapp/Dockerfile b/advanced-demo/integration-tests/tci-webapp/Dockerfile
index f17232a4..66125eee 100644
--- a/advanced-demo/integration-tests/tci-webapp/Dockerfile
+++ b/advanced-demo/integration-tests/tci-webapp/Dockerfile
@@ -77,7 +77,7 @@ FROM jre-minimized
ARG ENABLE_AOT
ARG JACOCO_AGENT_ENABLED
-ARG JACOCO_AGENT_VERSION="0.8.14"
+ARG JACOCO_AGENT_VERSION="0.8.15"
ARG user=app
ARG group=app
From a53b7bc1dcd9861addc77b317942663953000ec9 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 10 Jul 2026 09:18:00 +0200
Subject: [PATCH 088/141] Docs
---
.github/workflows/run-integration-tests.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index b34b0566..c2aac9ec 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -63,6 +63,9 @@ jobs:
id: docker-image-cache-restore
uses: ./.github/actions/docker-image-cache/restore
with:
+ # This cache could always change however because docker images can be changed remotely if the version is not fixated
+ # As a compromise it utilizes the hash of the pom files because when the dependencies stay the same
+ # the images should also be roughly the same
key: it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-it-docker-images-persistence-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
@@ -131,6 +134,9 @@ jobs:
id: docker-image-cache-restore
uses: ./.github/actions/docker-image-cache/restore
with:
+ # This cache could always change however because docker images can be changed remotely if the version is not fixated
+ # As a compromise it utilizes the hash of the pom files because when the dependencies stay the same
+ # the images should also be roughly the same
key: it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-it-docker-images-selenium-${{ matrix.project }}-${{ hashFiles('**/pom.xml') }}
From bbb0369eadcdba11921bf721bd6205c254eb9903 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 10 Jul 2026 09:22:15 +0200
Subject: [PATCH 089/141] Add dedicated JaCoCo test run
---
.github/workflows/run-integration-tests.yml | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index c2aac9ec..9a02e7ef 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -103,11 +103,14 @@ jobs:
parallel: [0, 2]
pre-start: [false, true]
java: [21]
- # Only enable jacoco on the fastest builds because AOT is not fully working with it
+ jacoco: [false]
+ # Dedicated build with JaCoCo
include:
- project: webapp-it
parallel: 2
- jacoco: [true]
+ pre-start: true
+ java: 21
+ jacoco: true
name: "selenium (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
runs-on: ubuntu-latest
if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
From 7b91e37e0b2f29fdceff37cd22bab9bab6b1793e Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 10 Jul 2026 09:24:44 +0200
Subject: [PATCH 090/141] Update run-integration-tests.yml
---
.github/workflows/run-integration-tests.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 9a02e7ef..bb9179ee 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -111,7 +111,7 @@ jobs:
pre-start: true
java: 21
jacoco: true
- name: "selenium (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
+ name: "selenium (${{matrix.project}}, ${{matrix.parallel }}, ${{matrix.pre-start}}, ${{matrix.java}}${{ matrix.jacoco && ', JaCoCo' }})"
runs-on: ubuntu-latest
if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
timeout-minutes: 30
From 2b35ffb025f66af62977b4895e8f11164a585783 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 10 Jul 2026 09:26:16 +0200
Subject: [PATCH 091/141] Add different build cache scope when jacoco is active
---
.github/workflows/run-integration-tests.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index bb9179ee..3250246c 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -155,8 +155,8 @@ jobs:
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
-P run-it${{ matrix.jacoco && ',jacoco' || '' }} \
-Dtci.image-build.delete-on-exit=true \
- -Dtci.image-build.cache-from="type=gha,scope=§image" \
- -Dtci.image-build.cache-to="type=gha,mode=max,scope=§image" \
+ -Dtci.image-build.cache-from="type=gha,scope=§image${{ matrix.jacoco && '-jacoco' || '' }}" \
+ -Dtci.image-build.cache-to="type=gha,mode=max,scope=§image${{ matrix.jacoco && '-jacoco' || '' }}" \
${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
env:
From 5f70d8f18dfcc5d7bd0522a0befc43606071fa0c Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 10 Jul 2026 09:31:06 +0200
Subject: [PATCH 092/141] Fix display
---
.github/workflows/run-integration-tests.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 3250246c..fd0ea1ce 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -111,7 +111,7 @@ jobs:
pre-start: true
java: 21
jacoco: true
- name: "selenium (${{matrix.project}}, ${{matrix.parallel }}, ${{matrix.pre-start}}, ${{matrix.java}}${{ matrix.jacoco && ', JaCoCo' }})"
+ name: "selenium (${{matrix.project}}, ${{matrix.parallel }}, ${{matrix.pre-start}}, ${{matrix.java}}${{ matrix.jacoco && ', JaCoCo' || '' }})"
runs-on: ubuntu-latest
if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
timeout-minutes: 30
From b31eb4fef83348cce283031ae4fe9262eff9a02d Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Fri, 10 Jul 2026 07:38:32 +0000
Subject: [PATCH 093/141] Update lycheeverse/lychee-action digest to e747777
---
.github/workflows/broken-links.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/broken-links.yml b/.github/workflows/broken-links.yml
index ce623e98..afdffd67 100644
--- a/.github/workflows/broken-links.yml
+++ b/.github/workflows/broken-links.yml
@@ -18,7 +18,7 @@ jobs:
- name: Link Checker
id: lychee
- uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2
+ uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2
with:
args: "--verbose --no-progress './**/*.md'"
fail: false # Don't fail on broken links, create an issue instead
From 3ca0f9a70f878a4b039027e4edfd2292c9f381f2 Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Fri, 10 Jul 2026 07:44:39 +0000
Subject: [PATCH 094/141] Update lycheeverse/lychee-action digest to e747777
---
.github/workflows/broken-links.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/broken-links.yml b/.github/workflows/broken-links.yml
index fbe05e74..8aeed096 100644
--- a/.github/workflows/broken-links.yml
+++ b/.github/workflows/broken-links.yml
@@ -19,7 +19,7 @@ jobs:
- name: Link Checker
id: lychee
- uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2
+ uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2
with:
args: "--verbose --no-progress './**/*.md'"
fail: false # Don't fail on broken links, create an issue instead
From c23918a4562637634aef26c653f57d1c4c9d9dc5 Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Fri, 10 Jul 2026 07:44:42 +0000
Subject: [PATCH 095/141] Update dependency ch.qos.logback:logback-classic to
v1.5.38
---
base-demo/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/base-demo/pom.xml b/base-demo/pom.xml
index d755ccd3..726def11 100644
--- a/base-demo/pom.xml
+++ b/base-demo/pom.xml
@@ -45,7 +45,7 @@
ch.qos.logback
logback-classic
- 1.5.37
+ 1.5.38
test
From 7c8b528ba356fc6a67e6750875139ff303cce852 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 10 Jul 2026 12:10:30 +0200
Subject: [PATCH 096/141] Save cache in background
---
.../tci/db/containers/DBContainerBuilder.java | 38 ++--
.../containers/WebAppContainerBuilder.java | 157 ++++++++---------
image-build/README.md | 2 +
image-build/pom.xml | 2 +-
.../{ImageCreator.java => BuildImage.java} | 25 ++-
...ageHandler.java => BuildImageHandler.java} | 15 +-
.../imagebuild/DefaultBuildImageHandler.java | 164 ++++++++++++++++++
.../imagebuild/DefaultCreateImageHandler.java | 87 ----------
.../cache/async/TCICacheSaveRegistry.java | 50 ++++++
.../cache/async/TCIWaitForCacheSave.java | 69 ++++++++
...nfig.java => BuildImageHandlerConfig.java} | 10 +-
...va => DefaultBuildImageHandlerConfig.java} | 28 ++-
...it.platform.launcher.TestExecutionListener | 1 +
...ware.xdev.tci.imagebuild.BuildImageHandler | 1 +
...are.xdev.tci.imagebuild.CreateImageHandler | 1 -
....imagebuild.config.BuildImageHandlerConfig | 1 +
...imagebuild.config.CreateImageHandlerConfig | 1 -
17 files changed, 441 insertions(+), 211 deletions(-)
rename image-build/src/main/java/software/xdev/tci/imagebuild/{ImageCreator.java => BuildImage.java} (58%)
rename image-build/src/main/java/software/xdev/tci/imagebuild/{CreateImageHandler.java => BuildImageHandler.java} (71%)
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandler.java
delete mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/DefaultCreateImageHandler.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCICacheSaveRegistry.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCIWaitForCacheSave.java
rename image-build/src/main/java/software/xdev/tci/imagebuild/config/{CreateImageHandlerConfig.java => BuildImageHandlerConfig.java} (78%)
rename image-build/src/main/java/software/xdev/tci/imagebuild/config/{DefaultCreateImageHandlerConfig.java => DefaultBuildImageHandlerConfig.java} (69%)
create mode 100644 image-build/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener
create mode 100644 image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.BuildImageHandler
delete mode 100644 image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.CreateImageHandler
create mode 100644 image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.config.BuildImageHandlerConfig
delete mode 100644 image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.config.CreateImageHandlerConfig
diff --git a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java
index e05bce43..de78f3e2 100644
--- a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java
+++ b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java
@@ -3,39 +3,29 @@
import java.nio.file.Paths;
import java.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import software.xdev.tci.imagebuild.ImageCreator;
-import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
+import software.xdev.tci.imagebuild.BuildImage;
public final class DBContainerBuilder
{
- private static final Logger LOG = LoggerFactory.getLogger(DBContainerBuilder.class);
-
private DBContainerBuilder()
{
}
public static String getImageName()
{
- LOG.info("Building Webapp-db-DockerImage...");
-
- final NativeAdvancedImageFromDockerfile builder = ImageCreator.nativeImage("tci-demo-db")
- .withDockerFilePath(Paths.get("../tci-db/Dockerfile"))
- .withBaseDir(Paths.get("../"))
- .configureFilesToTransferHandler(h -> h
- .withPostGitIgnoreLines(
- // Ignore everything
- "**")
- .withBaseDirRelativeIgnoreFile(null)
- );
-
- final String name = builder.build(Duration.ofMinutes(5));
-
- LOG.info("Built Image; Name ='{}'", name);
-
- return name;
+ return BuildImage.nativeImage(
+ "tci-demo-db",
+ Duration.ofMinutes(5),
+ builder -> builder
+ .withDockerFilePath(Paths.get("../tci-db/Dockerfile"))
+ .withBaseDir(Paths.get("../"))
+ .configureFilesToTransferHandler(h -> h
+ .withPostGitIgnoreLines(
+ // Ignore everything
+ "**")
+ .withBaseDirRelativeIgnoreFile(null)
+ )
+ );
}
}
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
index 5eb7fe74..02f6c01f 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java
@@ -6,101 +6,94 @@
import java.util.List;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import software.xdev.tci.imagebuild.ImageCreator;
+import software.xdev.tci.imagebuild.BuildImage;
import software.xdev.tci.jacoco.testbase.config.JaCoCoConfig;
-import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
import software.xdev.testcontainers.imagebuilder.compat.DockerfileCOPYParentsEmulator;
import software.xdev.testcontainers.imagebuilder.transfer.fcm.FileLinesContentModifier;
public final class WebAppContainerBuilder
{
- private static final Logger LOG = LoggerFactory.getLogger(WebAppContainerBuilder.class);
-
private WebAppContainerBuilder()
{
}
public static String getImageName()
{
- LOG.info("Building WebApp-DockerImage...");
-
- final NativeAdvancedImageFromDockerfile builder = ImageCreator.nativeImage("tci-demo-webapp")
- // NOTE: AOT can't be used properly when JaCoCo is active
- .withBuildArg("ENABLE_AOT", "1")
- .withDockerFilePath(Paths.get("../../integration-tests/tci-webapp/Dockerfile"))
- .withBaseDir(Paths.get("../../"))
- .configureFilesToTransferHandler(h -> h
- .withPostGitIgnoreLines(
- // Ignore git-folder, as it will be provided in the Dockerfile
- ".git/**",
- // Ignore other unused folders and extensions
- "*.iml",
- "*.cmd",
- "*.md",
- "_dev_infra/**",
- "_resource_metrics/**",
- // Ignore other Dockerfiles (our required file will always be transferred)
- "Dockerfile",
- // Ignore not required test-modules that may have changed
- // sources only - otherwise the parent pom doesn't find the resources
- "integration-tests/**",
- "**/src/test/**",
- // Ignore resources that are just used for development
- "webapp/src/main/resources-dev/**",
- // Most files from these folders need to be ignored -> Down there for highest prio
- "node_modules",
- "target")
- // File is in root directory - we can't access it
- .withBaseDirRelativeIgnoreFile(null)
- .withDockerFileLinesModifier(new DockerfileCOPYParentsEmulator())
- .withTransferArchiveTARCompressorCustomizer(c -> c
- // Rewrite parent pom to exclude integration tests
- // This way changes in test pom's cause no redownload of dependencies
- .withContentModifier(new FileLinesContentModifier()
- {
- @Override
- public boolean shouldApply(
- final Path sourcePath,
- final String targetPath,
- final TarArchiveEntry tarArchiveEntry)
- {
- return "pom.xml".equals(targetPath);
- }
-
- @Override
- public List modify(
- final List lines,
- final Path sourcePath,
- final String targetPath,
- final TarArchiveEntry tarArchiveEntry)
- {
- return lines.stream()
- // Remove integration tests module
- .filter(s -> !s.contains("integration-tests"))
- .toList();
- }
-
- @Override
- public boolean isIdentical(final List original, final List created)
- {
- return original.size() == created.size();
- }
- }))
- );
-
- if(JaCoCoConfig.instance().enabled())
- {
- builder.withBuildArg("JACOCO_AGENT_ENABLED", "1");
- }
-
- final String imageName = builder.build(Duration.ofMinutes(5));
-
- LOG.info("Built Image; Name ='{}'", imageName);
-
- return imageName;
+ return BuildImage.nativeImage(
+ "tci-demo-webapp",
+ Duration.ofMinutes(5),
+ builder -> {
+ builder
+ // NOTE: AOT can't be used properly when JaCoCo is active
+ .withBuildArg("ENABLE_AOT", "1")
+ .withDockerFilePath(Paths.get("../../integration-tests/tci-webapp/Dockerfile"))
+ .withBaseDir(Paths.get("../../"))
+ .configureFilesToTransferHandler(h -> h
+ .withPostGitIgnoreLines(
+ // Ignore git-folder, as it will be provided in the Dockerfile
+ ".git/**",
+ // Ignore other unused folders and extensions
+ "*.iml",
+ "*.cmd",
+ "*.md",
+ "_dev_infra/**",
+ "_resource_metrics/**",
+ // Ignore other Dockerfiles (our required file will always be transferred)
+ "Dockerfile",
+ // Ignore not required test-modules that may have changed
+ // sources only - otherwise the parent pom doesn't find the resources
+ "integration-tests/**",
+ "**/src/test/**",
+ // Ignore resources that are just used for development
+ "webapp/src/main/resources-dev/**",
+ // Most files from these folders need to be ignored -> Down there for highest prio
+ "node_modules",
+ "target")
+ // File is in root directory - we can't access it
+ .withBaseDirRelativeIgnoreFile(null)
+ .withDockerFileLinesModifier(new DockerfileCOPYParentsEmulator())
+ .withTransferArchiveTARCompressorCustomizer(c -> c
+ // Rewrite parent pom to exclude integration tests
+ // This way changes in test pom's cause no redownload of dependencies
+ .withContentModifier(new FileLinesContentModifier()
+ {
+ @Override
+ public boolean shouldApply(
+ final Path sourcePath,
+ final String targetPath,
+ final TarArchiveEntry tarArchiveEntry)
+ {
+ return "pom.xml".equals(targetPath);
+ }
+
+ @Override
+ public List modify(
+ final List lines,
+ final Path sourcePath,
+ final String targetPath,
+ final TarArchiveEntry tarArchiveEntry)
+ {
+ return lines.stream()
+ // Remove integration tests module
+ .filter(s -> !s.contains("integration-tests"))
+ .toList();
+ }
+
+ @Override
+ public boolean isIdentical(final List original, final List created)
+ {
+ return original.size() == created.size();
+ }
+ })));
+
+ if(JaCoCoConfig.instance().enabled())
+ {
+ builder.withBuildArg("JACOCO_AGENT_ENABLED", "1");
+ }
+
+ return builder;
+ });
}
}
diff --git a/image-build/README.md b/image-build/README.md
index 1d81d786..12c4a9c2 100644
--- a/image-build/README.md
+++ b/image-build/README.md
@@ -25,6 +25,8 @@ _You should know exactly what you're doing when doing modifications._
| `logger-for-build-prefix` | `string` | `container.build.` | Prefix used for the build logger |
| `cache-from` | `string` | - | Only applies to BuildKit (native) build.
See [Docker docs](https://docs.docker.com/build/cache/backends/) for details. |
| `cache-to` | `string` | - | Only applies to BuildKit (native) build.
See [Docker docs](https://docs.docker.com/build/cache/backends/) for details. |
+| `save-cache-in-background` | `bool` | if `cache-to` set `true`
otherwise `false` | Builds the image the first time WITHOUT `cache-to` and the async in the background again WITH `cache-to`. This way saving the cache does not delay test execution. |
+| `wait-for-save-cache-in-background` | `bool` | `true` | Wait until all caches are saved before terminating |
diff --git a/image-build/pom.xml b/image-build/pom.xml
index 093e6939..ba656ab9 100644
--- a/image-build/pom.xml
+++ b/image-build/pom.xml
@@ -58,7 +58,7 @@
software.xdev
testcontainers-advanced-imagebuilder
- 4.0.0
+ 4.1.0-SNAPSHOT
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/ImageCreator.java b/image-build/src/main/java/software/xdev/tci/imagebuild/BuildImage.java
similarity index 58%
rename from image-build/src/main/java/software/xdev/tci/imagebuild/ImageCreator.java
rename to image-build/src/main/java/software/xdev/tci/imagebuild/BuildImage.java
index ef452c0d..eb385204 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/ImageCreator.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/BuildImage.java
@@ -15,29 +15,38 @@
*/
package software.xdev.tci.imagebuild;
+import java.time.Duration;
+import java.util.function.UnaryOperator;
+
import software.xdev.tci.serviceloading.TCIServiceLoaderHolder;
import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
-public final class ImageCreator
+public final class BuildImage
{
- public static NativeAdvancedImageFromDockerfile nativeImage(final String dockerImage)
+ public static String nativeImage(
+ final String dockerImage,
+ final Duration timeout,
+ final UnaryOperator configure)
{
- return impl().nativeImage(dockerImage);
+ return impl().nativeImage(dockerImage, timeout, configure);
}
- public static AdvancedImageFromDockerFile image(final String dockerImage)
+ public static String image(
+ final String dockerImage,
+ final Duration timeout,
+ final UnaryOperator configure)
{
- return impl().image(dockerImage);
+ return impl().image(dockerImage, timeout, configure);
}
- public static CreateImageHandler impl()
+ public static BuildImageHandler impl()
{
- return TCIServiceLoaderHolder.instance().service(CreateImageHandler.class);
+ return TCIServiceLoaderHolder.instance().service(BuildImageHandler.class);
}
- private ImageCreator()
+ private BuildImage()
{
}
}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/CreateImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/BuildImageHandler.java
similarity index 71%
rename from image-build/src/main/java/software/xdev/tci/imagebuild/CreateImageHandler.java
rename to image-build/src/main/java/software/xdev/tci/imagebuild/BuildImageHandler.java
index b447f6c7..cf08dc96 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/CreateImageHandler.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/BuildImageHandler.java
@@ -15,13 +15,22 @@
*/
package software.xdev.tci.imagebuild;
+import java.time.Duration;
+import java.util.function.UnaryOperator;
+
import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
-public interface CreateImageHandler
+public interface BuildImageHandler
{
- NativeAdvancedImageFromDockerfile nativeImage(String dockerImage);
+ String nativeImage(
+ String dockerImage,
+ Duration timeout,
+ UnaryOperator configure);
- AdvancedImageFromDockerFile image(String dockerImage);
+ String image(
+ String dockerImage,
+ Duration timeout,
+ UnaryOperator configure);
}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandler.java
new file mode 100644
index 00000000..589ab297
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandler.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.imagebuild;
+
+import java.time.Duration;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.UnaryOperator;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import software.xdev.tci.concurrent.TCIExecutorServiceHolder;
+import software.xdev.tci.imagebuild.cache.async.TCICacheSaveRegistry;
+import software.xdev.tci.imagebuild.config.BuildImageHandlerConfig;
+import software.xdev.testcontainers.imagebuilder.AbstractImageFromDockerfile;
+import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
+import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
+
+
+public class DefaultBuildImageHandler implements BuildImageHandler
+{
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultBuildImageHandler.class);
+
+ protected static final Pattern DOCKER_IMAGE_SANITIZATION_PATTERN =
+ Pattern.compile("[^A-Za-z0-9-_]");
+
+ protected final BuildImageHandlerConfig config;
+
+ public DefaultBuildImageHandler()
+ {
+ this(BuildImageHandlerConfig.instance());
+ }
+
+ public DefaultBuildImageHandler(final BuildImageHandlerConfig config)
+ {
+ this.config = config;
+ }
+
+ @Override
+ public String nativeImage(
+ final String dockerImage,
+ final Duration timeout,
+ final UnaryOperator configure)
+ {
+ LOG.info("Starting build of (native) image {}", dockerImage);
+
+ final String sanitizeDockerImageName = this.sanitizeDockerImageName(dockerImage);
+ final NativeAdvancedImageFromDockerfile builder =
+ new NativeAdvancedImageFromDockerfile(dockerImage, this.config.deleteOnExit());
+
+ this.configureAbstract(builder, sanitizeDockerImageName);
+
+ this.templateCacheConfigValue(this.config.cacheFrom(), sanitizeDockerImageName)
+ .ifPresent(builder::withCacheFrom);
+
+ if(!this.config.saveCacheInBackground())
+ {
+ this.configureCacheTo(sanitizeDockerImageName, builder);
+ }
+
+ final NativeAdvancedImageFromDockerfile customizedBuilder = configure.apply(builder);
+
+ final String builtImageName = this.buildImage(customizedBuilder, timeout);
+
+ if(this.config.saveCacheInBackground() && this.config.cacheTo().isPresent())
+ {
+ LOG.info("Rebuilding image {} in background to save cache", builtImageName);
+ TCICacheSaveRegistry.instance().add(
+ CompletableFuture.runAsync(
+ () -> {
+ try
+ {
+ this.buildImage(
+ this.configureCacheTo(
+ sanitizeDockerImageName,
+ customizedBuilder.copyForExactRebuild(dockerImage + "-cache")
+ // Don't load it into docker because it's not needed there
+ .withLoad(false)),
+ timeout);
+ }
+ catch(final Exception ex)
+ {
+ LOG.warn("Rebuilding {} to save cache failed", builtImageName, ex);
+ }
+ },
+ TCIExecutorServiceHolder.instance()));
+ }
+
+ return builtImageName;
+ }
+
+ protected NativeAdvancedImageFromDockerfile configureCacheTo(
+ final String sanitizeDockerImageName,
+ final NativeAdvancedImageFromDockerfile builder)
+ {
+ this.templateCacheConfigValue(this.config.cacheTo(), sanitizeDockerImageName)
+ .ifPresent(builder::withCacheTo);
+ return builder;
+ }
+
+ private Optional templateCacheConfigValue(
+ final Optional configValue,
+ final String sanitizeDockerImageName)
+ {
+ return configValue
+ .map(s -> s.replace("$image", sanitizeDockerImageName))
+ .map(s -> s.replace("§image", sanitizeDockerImageName));
+ }
+
+ @Override
+ public String image(
+ final String dockerImage,
+ final Duration timeout,
+ final UnaryOperator configure)
+ {
+ LOG.info("Starting build of image {}", dockerImage);
+
+ final String sanitizeDockerImageName = this.sanitizeDockerImageName(dockerImage);
+ final AdvancedImageFromDockerFile builder =
+ new AdvancedImageFromDockerFile(dockerImage, this.config.deleteOnExit());
+
+ this.configureAbstract(builder, sanitizeDockerImageName);
+
+ return this.buildImage(configure.apply(builder), timeout);
+ }
+
+ protected String buildImage(
+ final AbstractImageFromDockerfile> builder,
+ final Duration timeout)
+ {
+ LOG.info("Building image {}...", builder.getDockerImageName());
+ final String builtImageName = builder.build(timeout);
+ LOG.info("Built image {}", builtImageName);
+
+ return builtImageName;
+ }
+
+ protected > void configureAbstract(
+ final I builder, final String sanitizeDockerImageName)
+ {
+ builder.withLoggerForBuild(
+ LoggerFactory.getLogger(this.config.loggerForBuildPrefix() + sanitizeDockerImageName));
+ }
+
+ protected String sanitizeDockerImageName(final String dockerImage)
+ {
+ return DOCKER_IMAGE_SANITIZATION_PATTERN.matcher(dockerImage).replaceAll("_");
+ }
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultCreateImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultCreateImageHandler.java
deleted file mode 100644
index efc1814d..00000000
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultCreateImageHandler.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright © 2025 XDEV Software (https://xdev.software)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package software.xdev.tci.imagebuild;
-
-import java.util.regex.Pattern;
-
-import org.slf4j.LoggerFactory;
-
-import software.xdev.tci.imagebuild.config.CreateImageHandlerConfig;
-import software.xdev.testcontainers.imagebuilder.AbstractImageFromDockerfile;
-import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
-import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
-
-
-public class DefaultCreateImageHandler implements CreateImageHandler
-{
- protected static final Pattern DOCKER_IMAGE_SANITIZATION_PATTERN =
- Pattern.compile("[^A-Za-z0-9-_]");
-
- protected final CreateImageHandlerConfig config;
-
- public DefaultCreateImageHandler()
- {
- this(CreateImageHandlerConfig.instance());
- }
-
- public DefaultCreateImageHandler(final CreateImageHandlerConfig config)
- {
- this.config = config;
- }
-
- @Override
- public NativeAdvancedImageFromDockerfile nativeImage(final String dockerImage)
- {
- final String sanitizeDockerImageName = this.sanitizeDockerImageName(dockerImage);
- final NativeAdvancedImageFromDockerfile builder =
- new NativeAdvancedImageFromDockerfile(dockerImage, this.config.deleteOnExit());
-
- this.configureAbstract(builder, sanitizeDockerImageName);
-
- this.config.cacheFrom()
- .map(s -> s.replace("$image", sanitizeDockerImageName))
- .map(s -> s.replace("§image", sanitizeDockerImageName))
- .ifPresent(builder::withCacheFrom);
- this.config.cacheTo()
- .map(s -> s.replace("$image", sanitizeDockerImageName))
- .map(s -> s.replace("§image", sanitizeDockerImageName))
- .ifPresent(builder::withCacheTo);
-
- return builder;
- }
-
- @Override
- public AdvancedImageFromDockerFile image(final String dockerImage)
- {
- final String sanitizeDockerImageName = this.sanitizeDockerImageName(dockerImage);
- final AdvancedImageFromDockerFile builder =
- new AdvancedImageFromDockerFile(dockerImage, this.config.deleteOnExit());
-
- return this.configureAbstract(builder, sanitizeDockerImageName);
- }
-
- protected > I configureAbstract(
- final I builder, final String sanitizeDockerImageName)
- {
- return builder.withLoggerForBuild(
- LoggerFactory.getLogger(this.config.loggerForBuildPrefix() + sanitizeDockerImageName));
- }
-
- protected String sanitizeDockerImageName(final String dockerImage)
- {
- return DOCKER_IMAGE_SANITIZATION_PATTERN.matcher(dockerImage).replaceAll("_");
- }
-}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCICacheSaveRegistry.java b/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCICacheSaveRegistry.java
new file mode 100644
index 00000000..fd5868c8
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCICacheSaveRegistry.java
@@ -0,0 +1,50 @@
+package software.xdev.tci.imagebuild.cache.async;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+
+public class TCICacheSaveRegistry
+{
+ private static TCICacheSaveRegistry instance;
+
+ public static void setInstance(final TCICacheSaveRegistry instance)
+ {
+ TCICacheSaveRegistry.instance = instance;
+ }
+
+ public static TCICacheSaveRegistry instance()
+ {
+ if(instance == null)
+ {
+ createInstance();
+ }
+ return instance;
+ }
+
+ private static synchronized void createInstance()
+ {
+ if(instance == null)
+ {
+ instance = new TCICacheSaveRegistry();
+ }
+ }
+
+ protected final List> cfs = Collections.synchronizedList(new ArrayList<>());
+
+ public void add(final CompletableFuture> cf)
+ {
+ this.cfs.add(cf);
+ }
+
+ public List> get()
+ {
+ return this.cfs;
+ }
+
+ protected TCICacheSaveRegistry()
+ {
+ }
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCIWaitForCacheSave.java b/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCIWaitForCacheSave.java
new file mode 100644
index 00000000..02dc49d7
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCIWaitForCacheSave.java
@@ -0,0 +1,69 @@
+package software.xdev.tci.imagebuild.cache.async;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.junit.platform.launcher.TestExecutionListener;
+import org.junit.platform.launcher.TestPlan;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import software.xdev.tci.imagebuild.config.BuildImageHandlerConfig;
+
+
+public class TCIWaitForCacheSave implements TestExecutionListener
+{
+ private static final Logger LOG = LoggerFactory.getLogger(TCIWaitForCacheSave.class);
+
+ private BuildImageHandlerConfig config;
+
+ @Override
+ public void testPlanExecutionStarted(final TestPlan testPlan)
+ {
+ this.config = BuildImageHandlerConfig.instance();
+ }
+
+ @Override
+ public void testPlanExecutionFinished(final TestPlan testPlan)
+ {
+ if(!this.config.waitForSaveCacheInBackground())
+ {
+ return;
+ }
+
+ final List> cfs = TCICacheSaveRegistry.instance().get();
+ if(cfs.isEmpty())
+ {
+ return;
+ }
+
+ LOG.info("Waiting for cache saves to finish...");
+ final long startMs = System.currentTimeMillis();
+ try
+ {
+ CompletableFuture.allOf(cfs.toArray(CompletableFuture[]::new))
+ .get(10, TimeUnit.MINUTES);
+ }
+ catch(final InterruptedException e)
+ {
+ LOG.warn("Got interrupted", e);
+ Thread.currentThread().interrupt();
+ }
+ catch(final ExecutionException e)
+ {
+ LOG.warn("A cache save failed", e);
+ }
+ catch(final TimeoutException e)
+ {
+ LOG.warn("Timed out while waiting for cache save", e);
+ }
+
+ LOG.info(
+ "Finished waiting for {}x cache saves, took {}ms",
+ cfs.size(),
+ System.currentTimeMillis() - startMs);
+ }
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/config/CreateImageHandlerConfig.java b/image-build/src/main/java/software/xdev/tci/imagebuild/config/BuildImageHandlerConfig.java
similarity index 78%
rename from image-build/src/main/java/software/xdev/tci/imagebuild/config/CreateImageHandlerConfig.java
rename to image-build/src/main/java/software/xdev/tci/imagebuild/config/BuildImageHandlerConfig.java
index 80ac8081..1a16d4d4 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/config/CreateImageHandlerConfig.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/config/BuildImageHandlerConfig.java
@@ -20,7 +20,7 @@
import software.xdev.tci.serviceloading.TCIServiceLoaderHolder;
-public interface CreateImageHandlerConfig
+public interface BuildImageHandlerConfig
{
boolean deleteOnExit();
@@ -30,8 +30,12 @@ public interface CreateImageHandlerConfig
Optional cacheTo();
- static CreateImageHandlerConfig instance()
+ boolean saveCacheInBackground();
+
+ boolean waitForSaveCacheInBackground();
+
+ static BuildImageHandlerConfig instance()
{
- return TCIServiceLoaderHolder.instance().service(CreateImageHandlerConfig.class);
+ return TCIServiceLoaderHolder.instance().service(BuildImageHandlerConfig.class);
}
}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultCreateImageHandlerConfig.java b/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultBuildImageHandlerConfig.java
similarity index 69%
rename from image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultCreateImageHandlerConfig.java
rename to image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultBuildImageHandlerConfig.java
index 80d09f16..5d43b7ea 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultCreateImageHandlerConfig.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultBuildImageHandlerConfig.java
@@ -21,12 +21,16 @@
@SuppressWarnings({"java:S2789", "OptionalAssignedToNull", "OptionalUsedAsFieldOrParameterType"})
-public class DefaultCreateImageHandlerConfig extends DefaultConfig implements CreateImageHandlerConfig
+public class DefaultBuildImageHandlerConfig extends DefaultConfig implements BuildImageHandlerConfig
{
+ protected static final String SAVE_CACHE_IN_BACKGROUND = "save-cache-in-background";
+
protected Boolean deleteOnExit;
protected String loggerForBuildPrefix;
protected Optional cacheFrom;
protected Optional cacheTo;
+ protected Boolean saveCacheInBackground;
+ protected Boolean waitForSaveCacheInBackground;
@Override
protected String propertyNamePrefix()
@@ -74,4 +78,26 @@ public Optional cacheTo()
}
return this.cacheTo;
}
+
+ @Override
+ public boolean saveCacheInBackground()
+ {
+ if(this.saveCacheInBackground == null)
+ {
+ this.saveCacheInBackground =
+ this.resolveBool(SAVE_CACHE_IN_BACKGROUND, this.cacheTo().isPresent());
+ }
+ return this.saveCacheInBackground;
+ }
+
+ @Override
+ public boolean waitForSaveCacheInBackground()
+ {
+ if(this.waitForSaveCacheInBackground == null)
+ {
+ this.waitForSaveCacheInBackground =
+ this.resolveBool("wait-for-" + SAVE_CACHE_IN_BACKGROUND, true);
+ }
+ return this.waitForSaveCacheInBackground;
+ }
}
diff --git a/image-build/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/image-build/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener
new file mode 100644
index 00000000..8604e1ab
--- /dev/null
+++ b/image-build/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener
@@ -0,0 +1 @@
+software.xdev.tci.imagebuild.cache.async.TCIWaitForCacheSave
diff --git a/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.BuildImageHandler b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.BuildImageHandler
new file mode 100644
index 00000000..0741e473
--- /dev/null
+++ b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.BuildImageHandler
@@ -0,0 +1 @@
+software.xdev.tci.imagebuild.DefaultBuildImageHandler
diff --git a/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.CreateImageHandler b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.CreateImageHandler
deleted file mode 100644
index f76ede0c..00000000
--- a/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.CreateImageHandler
+++ /dev/null
@@ -1 +0,0 @@
-software.xdev.tci.imagebuild.DefaultCreateImageHandler
diff --git a/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.config.BuildImageHandlerConfig b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.config.BuildImageHandlerConfig
new file mode 100644
index 00000000..b71d6a3e
--- /dev/null
+++ b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.config.BuildImageHandlerConfig
@@ -0,0 +1 @@
+software.xdev.tci.imagebuild.config.DefaultBuildImageHandlerConfig
diff --git a/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.config.CreateImageHandlerConfig b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.config.CreateImageHandlerConfig
deleted file mode 100644
index 9360b4bb..00000000
--- a/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.config.CreateImageHandlerConfig
+++ /dev/null
@@ -1 +0,0 @@
-software.xdev.tci.imagebuild.config.DefaultCreateImageHandlerConfig
From 4f30dff434175ae8d9f1d85dc9380145d070e24f Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 10 Jul 2026 12:11:23 +0200
Subject: [PATCH 097/141] Add misisng headers
---
.../cache/async/TCICacheSaveRegistry.java | 15 +++++++++++++++
.../cache/async/TCIWaitForCacheSave.java | 15 +++++++++++++++
2 files changed, 30 insertions(+)
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCICacheSaveRegistry.java b/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCICacheSaveRegistry.java
index fd5868c8..e4e8ebd3 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCICacheSaveRegistry.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCICacheSaveRegistry.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.imagebuild.cache.async;
import java.util.ArrayList;
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCIWaitForCacheSave.java b/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCIWaitForCacheSave.java
index 02dc49d7..eb81e7d8 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCIWaitForCacheSave.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCIWaitForCacheSave.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.imagebuild.cache.async;
import java.util.List;
From 42563af385da9872c9ac7a9bcc321ad2613bac41 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 10 Jul 2026 12:17:48 +0200
Subject: [PATCH 098/141] Unsnap version
---
image-build/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/image-build/pom.xml b/image-build/pom.xml
index ba656ab9..61dcbd64 100644
--- a/image-build/pom.xml
+++ b/image-build/pom.xml
@@ -58,7 +58,7 @@
software.xdev
testcontainers-advanced-imagebuilder
- 4.1.0-SNAPSHOT
+ 4.1.0
From 35c83930c4840e9ba5ad465ef0bc7d0a40e2e750 Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 10 Jul 2026 13:03:06 +0200
Subject: [PATCH 099/141] Correctly instantiate transfer cache
---
image-build/pom.xml | 2 +-
.../imagebuild/DefaultBuildImageHandler.java | 17 ++++++++++++++++-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/image-build/pom.xml b/image-build/pom.xml
index 61dcbd64..dc1d768d 100644
--- a/image-build/pom.xml
+++ b/image-build/pom.xml
@@ -58,7 +58,7 @@
software.xdev
testcontainers-advanced-imagebuilder
- 4.1.0
+ 4.1.1
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandler.java
index 589ab297..0a1360af 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandler.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandler.java
@@ -68,7 +68,11 @@ public String nativeImage(
this.templateCacheConfigValue(this.config.cacheFrom(), sanitizeDockerImageName)
.ifPresent(builder::withCacheFrom);
- if(!this.config.saveCacheInBackground())
+ if(this.config.saveCacheInBackground())
+ {
+ builder.withCreateTransferFilesCache(true);
+ }
+ else
{
this.configureCacheTo(sanitizeDockerImageName, builder);
}
@@ -97,6 +101,17 @@ public String nativeImage(
{
LOG.warn("Rebuilding {} to save cache failed", builtImageName, ex);
}
+ finally
+ {
+ try
+ {
+ builder.cleanCreatedTransferFilesCache();
+ }
+ catch(final Exception ex)
+ {
+ LOG.warn("Failed to clean created transfer file cache for {}", builtImageName, ex);
+ }
+ }
},
TCIExecutorServiceHolder.instance()));
}
From b692bc7170def1a0f8f59ef6321762a56f42744e Mon Sep 17 00:00:00 2001
From: AB
Date: Fri, 10 Jul 2026 14:38:02 +0200
Subject: [PATCH 100/141] Bump version
---
image-build/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/image-build/pom.xml b/image-build/pom.xml
index dc1d768d..7f51e203 100644
--- a/image-build/pom.xml
+++ b/image-build/pom.xml
@@ -58,7 +58,7 @@
software.xdev
testcontainers-advanced-imagebuilder
- 4.1.1
+ 4.1.2
From d5d7f92b24db48c4ae8cdf68048876a480159b4a Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Sun, 12 Jul 2026 06:26:11 +0000
Subject: [PATCH 101/141] Update dependency com.puppycrawl.tools:checkstyle to
v13.8.0
---
pom.xml | 2 +-
template-placeholder/pom.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/pom.xml b/pom.xml
index 6eb41f92..9642b3fc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,7 +45,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/template-placeholder/pom.xml b/template-placeholder/pom.xml
index 9a861f91..c1cb5518 100644
--- a/template-placeholder/pom.xml
+++ b/template-placeholder/pom.xml
@@ -215,7 +215,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
From 760dc76727f144db900853b8f4321e836ccb8667 Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Sun, 12 Jul 2026 06:26:27 +0000
Subject: [PATCH 102/141] Update org.hibernate.orm to v7.4.5.Final
---
db-jdbc-spring-orm-hibernate/pom.xml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/db-jdbc-spring-orm-hibernate/pom.xml b/db-jdbc-spring-orm-hibernate/pom.xml
index fdc1bca8..943cca58 100644
--- a/db-jdbc-spring-orm-hibernate/pom.xml
+++ b/db-jdbc-spring-orm-hibernate/pom.xml
@@ -59,18 +59,18 @@
org.hibernate.orm
hibernate-core
- 7.4.4.Final
+ 7.4.5.Final
org.hibernate.orm
hibernate-scan-jandex
- 7.4.4.Final
+ 7.4.5.Final
org.hibernate.orm
hibernate-hikaricp
- 7.4.4.Final
+ 7.4.5.Final
From b2e0f35a14631ef65c4c8de69ed0a6e57ea69610 Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Mon, 13 Jul 2026 06:46:59 +0000
Subject: [PATCH 103/141] Update org.junit to v6.1.2
---
advanced-demo/integration-tests/pom.xml | 2 +-
advanced-demo/pom.xml | 2 +-
base-demo/pom.xml | 2 +-
base/pom.xml | 4 ++--
db-jdbc/pom.xml | 2 +-
junit-jupiter-api-support/pom.xml | 2 +-
spring-dao-support/pom.xml | 2 +-
7 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/advanced-demo/integration-tests/pom.xml b/advanced-demo/integration-tests/pom.xml
index 07788024..2d54e5b4 100644
--- a/advanced-demo/integration-tests/pom.xml
+++ b/advanced-demo/integration-tests/pom.xml
@@ -114,7 +114,7 @@
org.junit
junit-bom
- 6.1.1
+ 6.1.2
pom
import
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 7b2e3021..a132c67f 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -70,7 +70,7 @@
org.junit
junit-bom
- 6.1.1
+ 6.1.2
pom
import
diff --git a/base-demo/pom.xml b/base-demo/pom.xml
index acaf7037..ec51c51e 100644
--- a/base-demo/pom.xml
+++ b/base-demo/pom.xml
@@ -38,7 +38,7 @@
org.junit.jupiter
junit-jupiter
- 6.1.1
+ 6.1.2
test
diff --git a/base/pom.xml b/base/pom.xml
index 42c60a2d..819ad072 100644
--- a/base/pom.xml
+++ b/base/pom.xml
@@ -69,14 +69,14 @@
org.junit.platform
junit-platform-launcher
compile
- 6.1.1
+ 6.1.2
org.junit.jupiter
junit-jupiter
- 6.1.1
+ 6.1.2
test
diff --git a/db-jdbc/pom.xml b/db-jdbc/pom.xml
index c1fbbc64..a17b1ff5 100644
--- a/db-jdbc/pom.xml
+++ b/db-jdbc/pom.xml
@@ -72,7 +72,7 @@
org.junit.jupiter
junit-jupiter
- 6.1.1
+ 6.1.2
test
diff --git a/junit-jupiter-api-support/pom.xml b/junit-jupiter-api-support/pom.xml
index 12fb28f2..8a07bbaa 100644
--- a/junit-jupiter-api-support/pom.xml
+++ b/junit-jupiter-api-support/pom.xml
@@ -53,7 +53,7 @@
org.junit.jupiter
junit-jupiter-api
- 6.1.1
+ 6.1.2
compile
diff --git a/spring-dao-support/pom.xml b/spring-dao-support/pom.xml
index 25d18c4a..583eb77d 100644
--- a/spring-dao-support/pom.xml
+++ b/spring-dao-support/pom.xml
@@ -78,7 +78,7 @@
org.junit.jupiter
junit-jupiter
- 6.1.1
+ 6.1.2
test
From 504e727da1159e928f6b1f45210ded2430188ead Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Mon, 13 Jul 2026 06:47:03 +0000
Subject: [PATCH 104/141] Update dependency com.puppycrawl.tools:checkstyle to
v13.8.0
---
advanced-demo/pom.xml | 2 +-
base/pom.xml | 2 +-
db-jdbc-spring-orm-eclipselink/pom.xml | 2 +-
db-jdbc-spring-orm-hibernate/pom.xml | 2 +-
db-jdbc-spring-orm/pom.xml | 2 +-
db-jdbc/pom.xml | 2 +-
image-build/pom.xml | 2 +-
jacoco/pom.xml | 2 +-
jul-to-slf4j/pom.xml | 2 +-
junit-jupiter-api-support/pom.xml | 2 +-
mockserver/pom.xml | 2 +-
oidc-server-mock/pom.xml | 2 +-
pom.xml | 2 +-
selenium/pom.xml | 2 +-
spring-dao-support/pom.xml | 2 +-
15 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/advanced-demo/pom.xml b/advanced-demo/pom.xml
index 7b2e3021..e630bca9 100644
--- a/advanced-demo/pom.xml
+++ b/advanced-demo/pom.xml
@@ -195,7 +195,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/base/pom.xml b/base/pom.xml
index 42c60a2d..31a93f5e 100644
--- a/base/pom.xml
+++ b/base/pom.xml
@@ -284,7 +284,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/db-jdbc-spring-orm-eclipselink/pom.xml b/db-jdbc-spring-orm-eclipselink/pom.xml
index a7689a23..16226b22 100644
--- a/db-jdbc-spring-orm-eclipselink/pom.xml
+++ b/db-jdbc-spring-orm-eclipselink/pom.xml
@@ -248,7 +248,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/db-jdbc-spring-orm-hibernate/pom.xml b/db-jdbc-spring-orm-hibernate/pom.xml
index 943cca58..9e11668f 100644
--- a/db-jdbc-spring-orm-hibernate/pom.xml
+++ b/db-jdbc-spring-orm-hibernate/pom.xml
@@ -259,7 +259,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/db-jdbc-spring-orm/pom.xml b/db-jdbc-spring-orm/pom.xml
index c19b8c88..fc5c488f 100644
--- a/db-jdbc-spring-orm/pom.xml
+++ b/db-jdbc-spring-orm/pom.xml
@@ -258,7 +258,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/db-jdbc/pom.xml b/db-jdbc/pom.xml
index c1fbbc64..8597ca05 100644
--- a/db-jdbc/pom.xml
+++ b/db-jdbc/pom.xml
@@ -268,7 +268,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/image-build/pom.xml b/image-build/pom.xml
index 7f51e203..0efd17d3 100644
--- a/image-build/pom.xml
+++ b/image-build/pom.xml
@@ -247,7 +247,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/jacoco/pom.xml b/jacoco/pom.xml
index 14a24c07..253a09de 100644
--- a/jacoco/pom.xml
+++ b/jacoco/pom.xml
@@ -242,7 +242,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/jul-to-slf4j/pom.xml b/jul-to-slf4j/pom.xml
index c7018e70..f60c13fe 100644
--- a/jul-to-slf4j/pom.xml
+++ b/jul-to-slf4j/pom.xml
@@ -242,7 +242,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/junit-jupiter-api-support/pom.xml b/junit-jupiter-api-support/pom.xml
index 12fb28f2..7397051d 100644
--- a/junit-jupiter-api-support/pom.xml
+++ b/junit-jupiter-api-support/pom.xml
@@ -243,7 +243,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/mockserver/pom.xml b/mockserver/pom.xml
index f5740aa8..2eec4022 100644
--- a/mockserver/pom.xml
+++ b/mockserver/pom.xml
@@ -264,7 +264,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/oidc-server-mock/pom.xml b/oidc-server-mock/pom.xml
index b3dd16a4..fad26177 100644
--- a/oidc-server-mock/pom.xml
+++ b/oidc-server-mock/pom.xml
@@ -248,7 +248,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/pom.xml b/pom.xml
index 496b07d5..fdc2b45a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,7 +78,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/selenium/pom.xml b/selenium/pom.xml
index e94800ab..1d58c858 100644
--- a/selenium/pom.xml
+++ b/selenium/pom.xml
@@ -296,7 +296,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
diff --git a/spring-dao-support/pom.xml b/spring-dao-support/pom.xml
index 25d18c4a..d7995373 100644
--- a/spring-dao-support/pom.xml
+++ b/spring-dao-support/pom.xml
@@ -274,7 +274,7 @@
com.puppycrawl.tools
checkstyle
- 13.7.0
+ 13.8.0
From f8c1cdd1d3761cd7fb8beb77872232704798f1ad Mon Sep 17 00:00:00 2001
From: AB
Date: Mon, 13 Jul 2026 17:22:34 +0200
Subject: [PATCH 105/141] Add HsErrPidCrashReporter
See #688
---
.../webapp/containers/WebAppContainer.java | 19 ++-
.../fatal/HsErrPidStartUpCrashReporter.java | 121 ++++++++++++++++++
.../cache/async/TCICacheSaveRegistry.java | 11 +-
.../tci/jacoco/testbase/JaCoCoRecorder.java | 10 +-
4 files changed, 151 insertions(+), 10 deletions(-)
create mode 100644 base/src/main/java/software/xdev/tci/startup/error/java/fatal/HsErrPidStartUpCrashReporter.java
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java
index 56a6e63c..36aa26db 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java
@@ -6,9 +6,11 @@
import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
+import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.HttpStatus;
import software.xdev.tci.jacoco.containers.JaCoCoAwareContainer;
+import software.xdev.tci.startup.error.java.fatal.HsErrPidStartUpCrashReporter;
@SuppressWarnings("java:S2160")
@@ -17,6 +19,7 @@ public class WebAppContainer extends GenericContainer implement
public static final int DEFAULT_HTTP_PORT = 8080;
protected final boolean connectionlessStart;
+ protected final HsErrPidStartUpCrashReporter hsErrPidStartUpCrashReporter;
public WebAppContainer(final String dockerImageName, final boolean connectionlessStart)
{
@@ -27,6 +30,7 @@ public WebAppContainer(final String dockerImageName, final boolean connectionles
this.withConnectionlessStart();
}
this.addExposedPort(DEFAULT_HTTP_PORT);
+ this.hsErrPidStartUpCrashReporter = new HsErrPidStartUpCrashReporter(this);
}
public WebAppContainer withDB(final String jdbcUrl, final String username, final String password)
@@ -88,7 +92,7 @@ public WebAppContainer withConnectionlessStart()
.withEnv("MANAGEMENT_HEALTH_DB_ENABLED", false)
.withEnv(springJpa + "PROPERTIES_JAKARTA_PERSISTENCE_DATABASE-PRODUCT-NAME", "MariaDB")
.withEnv(springJpa + "PROPERTIES_JAKARTA_PERSISTENCE_DATABASE-MAJOR-VERSION", "11")
- .withEnv(springJpa + "PROPERTIES_JAKARTA_PERSISTENCE_DATABASE-MINOR-VERSION", "4")
+ .withEnv(springJpa + "PROPERTIES_JAKARTA_PERSISTENCE_DATABASE-MINOR-VERSION", "8")
.withEnv(springJpa + "PROPERTIES_HIBERNATE_BOOT_ALLOW_JDBC_METADATA_ACCESS", false);
}
@@ -124,6 +128,19 @@ public WebAppContainer withDefaultWaitStrategy(
.forStatusCode(HttpStatus.SC_OK)
.withReadTimeout(Duration.ofSeconds(10))
));
+
+ @Override
+ protected void containerIsStarted(final InspectContainerResponse containerInfo, final boolean reused)
+ {
+ this.hsErrPidStartUpCrashReporter.containerIsStarted();
+ super.containerIsStarted(containerInfo, reused);
+ }
+
+ @Override
+ protected void containerIsStopping(final InspectContainerResponse containerInfo)
+ {
+ this.hsErrPidStartUpCrashReporter.containerIsStopping(this.logger());
+ super.containerIsStopping(containerInfo);
}
@SuppressWarnings("unused")
diff --git a/base/src/main/java/software/xdev/tci/startup/error/java/fatal/HsErrPidStartUpCrashReporter.java b/base/src/main/java/software/xdev/tci/startup/error/java/fatal/HsErrPidStartUpCrashReporter.java
new file mode 100644
index 00000000..d804a7bf
--- /dev/null
+++ b/base/src/main/java/software/xdev/tci/startup/error/java/fatal/HsErrPidStartUpCrashReporter.java
@@ -0,0 +1,121 @@
+package software.xdev.tci.startup.error.java.fatal;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.testcontainers.containers.GenericContainer;
+
+import software.xdev.tci.concurrent.Suppliers;
+
+
+/**
+ * Utility to log contents of java hs_err_pid files of a container that failed to start.
+ *
+ * Additionally also copies the file into target/
+ *
+ */
+public class HsErrPidStartUpCrashReporter
+{
+ protected static final String HS_ERR_PID = "hs_err_pid";
+ protected static final Supplier DEFAULT_HS_ERR_PID_EXTRACTION_PATTERN_SUPPLIER =
+ Suppliers.memoize(() -> Pattern.compile("^# (\\S{1,999}hs_err_pid\\d{1,9}\\.log)$", Pattern.MULTILINE));
+
+ protected final GenericContainer> container;
+ protected final String hsErrPidSearchKey;
+ protected final Supplier hsErrPidExtractionPattern;
+ protected Supplier hsErrPidPathSupplier = Suppliers.memoize(
+ () -> Paths.get("target/has_err_pid"));
+
+ protected boolean startedWithoutCrashing;
+
+ public HsErrPidStartUpCrashReporter(final GenericContainer> container)
+ {
+ this(container, HS_ERR_PID, DEFAULT_HS_ERR_PID_EXTRACTION_PATTERN_SUPPLIER);
+ }
+
+ protected HsErrPidStartUpCrashReporter(
+ final GenericContainer> container,
+ final String hsErrPidSearchKey,
+ final Supplier hsErrPidExtractionPattern)
+ {
+ this.container = container;
+ this.hsErrPidSearchKey = hsErrPidSearchKey;
+ this.hsErrPidExtractionPattern = hsErrPidExtractionPattern;
+ }
+
+ public void containerIsStarted()
+ {
+ this.startedWithoutCrashing = true;
+ }
+
+ public void containerIsStopping(final Logger logger)
+ {
+ if(this.startedWithoutCrashing || !logger.isWarnEnabled())
+ {
+ return;
+ }
+
+ final String logs = this.container.getLogs();
+ if(!logs.contains(this.hsErrPidSearchKey))
+ {
+ return;
+ }
+
+ final Matcher matcher = this.hsErrPidExtractionPattern.get().matcher(logs);
+ if(!matcher.find())
+ {
+ logger.warn(
+ "Detected {} in log output but was unable to determine the file location - "
+ + "No regex match", this.hsErrPidSearchKey);
+ return;
+ }
+
+ final String filePath = matcher.group(1);
+ if(filePath == null || filePath.isBlank())
+ {
+ logger.warn(
+ "Detected {} in log output but was unable to determine the file location - "
+ + "No regex group match", this.hsErrPidSearchKey);
+ return;
+ }
+
+ try
+ {
+ final String containerId = this.container.getContainerId();
+
+ final Path hsErrPidContainerDirPath = this.hsErrPidPathSupplier.get().resolve(containerId);
+ Files.createDirectories(hsErrPidContainerDirPath);
+ final Path targetHsErrPidFilePath =
+ hsErrPidContainerDirPath.resolve(Path.of(filePath).getFileName().toString());
+
+ this.container.copyFileFromContainer(
+ filePath,
+ is -> Files.copy(
+ is,
+ targetHsErrPidFilePath,
+ StandardCopyOption.REPLACE_EXISTING));
+
+ logger.warn(
+ "Detected {} file[container={}]: {} -> {} \n{}",
+ this.hsErrPidSearchKey,
+ containerId,
+ filePath,
+ targetHsErrPidFilePath,
+ this.container.copyFileFromContainer(
+ filePath,
+ is -> IOUtils.toString(is, StandardCharsets.UTF_8)));
+ }
+ catch(final Exception ex)
+ {
+ logger.warn("Failed to read {} file {}", this.hsErrPidSearchKey, filePath, ex);
+ }
+ }
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCICacheSaveRegistry.java b/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCICacheSaveRegistry.java
index e4e8ebd3..aaae1d0f 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCICacheSaveRegistry.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/cache/async/TCICacheSaveRegistry.java
@@ -23,23 +23,18 @@
public class TCICacheSaveRegistry
{
- private static TCICacheSaveRegistry instance;
-
- public static void setInstance(final TCICacheSaveRegistry instance)
- {
- TCICacheSaveRegistry.instance = instance;
- }
+ protected static TCICacheSaveRegistry instance;
public static TCICacheSaveRegistry instance()
{
if(instance == null)
{
- createInstance();
+ createDefaultInstance();
}
return instance;
}
- private static synchronized void createInstance()
+ protected static synchronized void createDefaultInstance()
{
if(instance == null)
{
diff --git a/jacoco/src/main/java/software/xdev/tci/jacoco/testbase/JaCoCoRecorder.java b/jacoco/src/main/java/software/xdev/tci/jacoco/testbase/JaCoCoRecorder.java
index 428271eb..a8a2b799 100644
--- a/jacoco/src/main/java/software/xdev/tci/jacoco/testbase/JaCoCoRecorder.java
+++ b/jacoco/src/main/java/software/xdev/tci/jacoco/testbase/JaCoCoRecorder.java
@@ -46,11 +46,19 @@ public static JaCoCoRecorder instance()
{
if(instance == null)
{
- instance = new JaCoCoRecorder();
+ createDefaultInstance();
}
return instance;
}
+ protected static synchronized void createDefaultInstance()
+ {
+ if(instance == null)
+ {
+ instance = new JaCoCoRecorder();
+ }
+ }
+
protected JaCoCoConfig config;
protected Map cachedDirForExecutionDataFilesVariantPaths = new ConcurrentHashMap<>();
From 13f438f8317b690483d6abd604f06dae3e8060dc Mon Sep 17 00:00:00 2001
From: AB
Date: Mon, 13 Jul 2026 17:24:46 +0200
Subject: [PATCH 106/141] Add missing header
---
.../java/fatal/HsErrPidStartUpCrashReporter.java | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/base/src/main/java/software/xdev/tci/startup/error/java/fatal/HsErrPidStartUpCrashReporter.java b/base/src/main/java/software/xdev/tci/startup/error/java/fatal/HsErrPidStartUpCrashReporter.java
index d804a7bf..d48d96e3 100644
--- a/base/src/main/java/software/xdev/tci/startup/error/java/fatal/HsErrPidStartUpCrashReporter.java
+++ b/base/src/main/java/software/xdev/tci/startup/error/java/fatal/HsErrPidStartUpCrashReporter.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.startup.error.java.fatal;
import java.nio.charset.StandardCharsets;
From 9c481b19ff5378acb8486a729777272a8f0d324c Mon Sep 17 00:00:00 2001
From: AB
Date: Mon, 13 Jul 2026 17:26:13 +0200
Subject: [PATCH 107/141] Fix typo
---
.../xdev/tci/demo/tci/webapp/containers/WebAppContainer.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java
index 36aa26db..995a8faa 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java
@@ -128,6 +128,7 @@ public WebAppContainer withDefaultWaitStrategy(
.forStatusCode(HttpStatus.SC_OK)
.withReadTimeout(Duration.ofSeconds(10))
));
+ }
@Override
protected void containerIsStarted(final InspectContainerResponse containerInfo, final boolean reused)
From a7e85dae62865945c5c533f69a593062584a7f9d Mon Sep 17 00:00:00 2001
From: XDEV Renovate Bot
Date: Tue, 14 Jul 2026 05:59:31 +0000
Subject: [PATCH 108/141] Update dependency
org.seleniumhq.selenium:selenium-dependencies-bom to v4.46.0
---
advanced-demo/integration-tests/pom.xml | 2 +-
selenium/pom.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/advanced-demo/integration-tests/pom.xml b/advanced-demo/integration-tests/pom.xml
index 2d54e5b4..f40e331b 100644
--- a/advanced-demo/integration-tests/pom.xml
+++ b/advanced-demo/integration-tests/pom.xml
@@ -97,7 +97,7 @@
org.seleniumhq.selenium
selenium-dependencies-bom
- 4.45.0
+ 4.46.0
pom
import
diff --git a/selenium/pom.xml b/selenium/pom.xml
index 1d58c858..137ba8ed 100644
--- a/selenium/pom.xml
+++ b/selenium/pom.xml
@@ -54,7 +54,7 @@
org.seleniumhq.selenium
selenium-dependencies-bom
- 4.45.0
+ 4.46.0
pom
import
From 9b77c3c4d2df4bc423db35745bacd4bbea6c404c Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 11:15:16 +0200
Subject: [PATCH 109/141] Improve container cleanup
---
CHANGELOG.md | 1 +
.../xdev/tci/safestart/SafeNamedContainerStarter.java | 11 +++++++++++
2 files changed, 12 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd93637a..554eeb0a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
* No longer use `setPersistenceProviderClassName` because it has no effect
* Performance: Reuse `PersistenceProvider`
* Fixed a deadlock during service resolution
+* Improve container cleanup
* Updated dependencies
* Updated demo
diff --git a/base/src/main/java/software/xdev/tci/safestart/SafeNamedContainerStarter.java b/base/src/main/java/software/xdev/tci/safestart/SafeNamedContainerStarter.java
index 6f800b68..636f5685 100644
--- a/base/src/main/java/software/xdev/tci/safestart/SafeNamedContainerStarter.java
+++ b/base/src/main/java/software/xdev/tci/safestart/SafeNamedContainerStarter.java
@@ -30,6 +30,8 @@
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.GenericContainer;
+import com.github.dockerjava.api.exception.NotFoundException;
+
import software.xdev.tci.envperf.EnvironmentPerformance;
@@ -144,6 +146,15 @@ protected void tryCleanupContainerAfterStartFail(final List containerNam
}
catch(final Exception ex)
{
+ // Check if container was already cleaned up (e.g. with tryStart->stop)
+ if(ex.getCause() instanceof final NotFoundException nfe
+ && nfe.getMessage() != null
+ && nfe.getMessage().startsWith("{\"message\":\"No such container:"))
+ {
+ LOG.info("Container[name='{}'] was already removed", containerName);
+ return;
+ }
+
LOG.warn("Unable to cleanup container[name='{}']", containerName, ex);
}
}
From 1252f741b10b8216b37b9f34b023f5c778e0f234 Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 11:15:46 +0200
Subject: [PATCH 110/141] Cleanup
---
.../tci/jacoco/testbase/JaCoCoRecorder.java | 31 +++++++++++--------
1 file changed, 18 insertions(+), 13 deletions(-)
diff --git a/jacoco/src/main/java/software/xdev/tci/jacoco/testbase/JaCoCoRecorder.java b/jacoco/src/main/java/software/xdev/tci/jacoco/testbase/JaCoCoRecorder.java
index a8a2b799..455e851f 100644
--- a/jacoco/src/main/java/software/xdev/tci/jacoco/testbase/JaCoCoRecorder.java
+++ b/jacoco/src/main/java/software/xdev/tci/jacoco/testbase/JaCoCoRecorder.java
@@ -87,7 +87,6 @@ public CompletableFuture afterTestAsync(
return this.afterTestAsync(optTCI, null, fileSystemFriendlyNameSupplier, variantName);
}
- @SuppressWarnings("resource")
public CompletableFuture afterTestAsync(
final Optional> optTCI,
final String jaCoCoExecutionDataFilePathInContainer,
@@ -132,18 +131,7 @@ protected void stopContainerAndCopy(
final GenericContainer> container,
final String containerPath)
{
- if(container.isRunning())
- {
- // Shutdown container so that jacoco agent dumps the execution data file
- try
- {
- DockerClientFactory.lazyClient().stopContainerCmd(container.getContainerId()).exec();
- }
- catch(final Exception ex)
- {
- LOG.warn("Failed to stop container", ex);
- }
- }
+ this.stopContainer(container);
try
{
@@ -168,6 +156,23 @@ protected void stopContainerAndCopy(
}
}
+ @SuppressWarnings("resource")
+ protected void stopContainer(final GenericContainer> container)
+ {
+ if(container.isRunning())
+ {
+ // Shutdown container so that jacoco agent dumps the execution data file
+ try
+ {
+ DockerClientFactory.lazyClient().stopContainerCmd(container.getContainerId()).exec();
+ }
+ catch(final Exception ex)
+ {
+ LOG.warn("Failed to stop container", ex);
+ }
+ }
+ }
+
protected Path resolveDirForExecutionDataFiles(final String variantName)
{
return this.cachedDirForExecutionDataFilesVariantPaths.computeIfAbsent(
From d3d6387ad871c5041bba8ca9d1c37faa980fdf51 Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 11:58:50 +0200
Subject: [PATCH 111/141] Fix typo
---
.../software/xdev/tci/safestart/SafeNamedContainerStarter.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/base/src/main/java/software/xdev/tci/safestart/SafeNamedContainerStarter.java b/base/src/main/java/software/xdev/tci/safestart/SafeNamedContainerStarter.java
index 636f5685..36d8d753 100644
--- a/base/src/main/java/software/xdev/tci/safestart/SafeNamedContainerStarter.java
+++ b/base/src/main/java/software/xdev/tci/safestart/SafeNamedContainerStarter.java
@@ -149,7 +149,7 @@ protected void tryCleanupContainerAfterStartFail(final List containerNam
// Check if container was already cleaned up (e.g. with tryStart->stop)
if(ex.getCause() instanceof final NotFoundException nfe
&& nfe.getMessage() != null
- && nfe.getMessage().startsWith("{\"message\":\"No such container:"))
+ && nfe.getMessage().startsWith("Status 404: {\"message\":\"No such container:"))
{
LOG.info("Container[name='{}'] was already removed", containerName);
return;
From 0a8d672c44ad2cfda724eb7699d486a0963da205 Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 14:06:57 +0200
Subject: [PATCH 112/141] Added abortable wait strategies
---
CHANGELOG.md | 5 +
.../webapp/containers/WebAppContainer.java | 20 +-
base/pom.xml | 22 +
.../xdev/tci/startup/wait/AbortMonitor.java | 45 ++
...FastAbortOnContainerDeathWaitStrategy.java | 166 ++++++
.../wait/holder/AbortableStrategyValues.java | 28 +
.../holder/AbortableStrategyValuesHolder.java | 46 ++
.../AbstractWaitAbortableStrategy.java | 75 +++
.../HostPortWaitAbortableStrategy.java | 172 ++++++
.../strategy/HttpWaitAbortableStrategy.java | 524 ++++++++++++++++++
.../LogMessageWaitAbortableStrategy.java | 101 ++++
.../strategy/WaitAllAbortableStrategy.java | 132 +++++
.../holder/AbortableStrategyValuesHolder.java | 21 +
.../db/containers/WaitableJDBCContainer.java | 62 +--
.../containers/TCIMockserverContainer.java | 82 +++
.../factory/MockServerTCIFactory.java | 6 +-
.../PreStartableMockServerTCIFactory.java | 6 +-
.../tci/oidc/factory/BaseOIDCTCIFactory.java | 29 +-
.../selenium/factory/BrowserTCIFactory.java | 17 +-
19 files changed, 1480 insertions(+), 79 deletions(-)
create mode 100644 base/src/main/java/software/xdev/tci/startup/wait/AbortMonitor.java
create mode 100644 base/src/main/java/software/xdev/tci/startup/wait/FastAbortOnContainerDeathWaitStrategy.java
create mode 100644 base/src/main/java/software/xdev/tci/startup/wait/holder/AbortableStrategyValues.java
create mode 100644 base/src/main/java/software/xdev/tci/startup/wait/holder/AbortableStrategyValuesHolder.java
create mode 100644 base/src/main/java/software/xdev/tci/startup/wait/strategy/AbstractWaitAbortableStrategy.java
create mode 100644 base/src/main/java/software/xdev/tci/startup/wait/strategy/HostPortWaitAbortableStrategy.java
create mode 100644 base/src/main/java/software/xdev/tci/startup/wait/strategy/HttpWaitAbortableStrategy.java
create mode 100644 base/src/main/java/software/xdev/tci/startup/wait/strategy/LogMessageWaitAbortableStrategy.java
create mode 100644 base/src/main/java/software/xdev/tci/startup/wait/strategy/WaitAllAbortableStrategy.java
create mode 100644 base/src/main/java25/software/xdev/tci/startup/wait/holder/AbortableStrategyValuesHolder.java
create mode 100644 mockserver/src/main/java/software/xdev/tci/mockserver/containers/TCIMockserverContainer.java
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 554eeb0a..28e2023b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,10 +8,15 @@
* `restore` - Restores the previously downloaded images
* `setup-buildx` - Set's up the buildx worker and returns required environment variables
* `save` - Saves the downloaded images
+* Added abortable wait strategies
+ * primarily designed to be used with `FastAbortOnContainerDeathWaitStrategy`
+ * the strategies can be aborted e.g. when a container died during startup
+ * a dead container therefore no longer has to wait until the corresponding strategy times out
* `db-jdbc-spring-orm` & implementations
* No longer use `setPersistenceProviderClassName` because it has no effect
* Performance: Reuse `PersistenceProvider`
* Fixed a deadlock during service resolution
+* Fixed rate limiting in some places
* Improve container cleanup
* Updated dependencies
* Updated demo
diff --git a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java
index 995a8faa..4b7bf5d0 100644
--- a/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java
+++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java
@@ -3,14 +3,15 @@
import java.time.Duration;
import org.testcontainers.containers.GenericContainer;
-import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
-import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.HttpStatus;
import software.xdev.tci.jacoco.containers.JaCoCoAwareContainer;
import software.xdev.tci.startup.error.java.fatal.HsErrPidStartUpCrashReporter;
+import software.xdev.tci.startup.wait.FastAbortOnContainerDeathWaitStrategy;
+import software.xdev.tci.startup.wait.strategy.HostPortWaitAbortableStrategy;
+import software.xdev.tci.startup.wait.strategy.HttpWaitAbortableStrategy;
@SuppressWarnings("java:S2160")
@@ -111,23 +112,22 @@ public WebAppContainer withDefaultWaitStrategy(
final String actuatorUsername,
final String actuatorPassword)
{
- return this.waitingFor(new WaitAllStrategy()
+ return this.waitingFor(FastAbortOnContainerDeathWaitStrategy.waitAll(s -> s
.withStartupTimeout(startUpTimeout)
- .withStrategy(
- new HttpWaitStrategy()
+ .withStrategy(new HostPortWaitAbortableStrategy())
+ .withStrategy(new HttpWaitAbortableStrategy()
.forPort(WebAppContainer.DEFAULT_HTTP_PORT)
.forPath("/robots.txt")
.forStatusCode(HttpStatus.SC_OK)
- .withReadTimeout(Duration.ofSeconds(10))
- )
- .withStrategy(
- new HttpWaitStrategy()
+ .withReadTimeout(Duration.ofSeconds(10)))
+ .withStrategy(new HttpWaitAbortableStrategy()
.forPort(WebAppContainer.DEFAULT_HTTP_PORT)
.forPath("/actuator/health")
.withBasicCredentials(actuatorUsername, actuatorPassword)
.forStatusCode(HttpStatus.SC_OK)
.withReadTimeout(Duration.ofSeconds(10))
- ));
+ ))
+ );
}
@Override
diff --git a/base/pom.xml b/base/pom.xml
index cb5df55c..09c85580 100644
--- a/base/pom.xml
+++ b/base/pom.xml
@@ -136,6 +136,28 @@
org.apache.maven.plugins
maven-compiler-plugin
3.15.0
+
+
+ compile-java-21
+
+ compile
+
+
+
+ compile-java-25
+ compile
+
+ compile
+
+
+ 25
+
+ ${project.basedir}/src/main/java25
+
+ true
+
+
+
${maven.compiler.release}
diff --git a/base/src/main/java/software/xdev/tci/startup/wait/AbortMonitor.java b/base/src/main/java/software/xdev/tci/startup/wait/AbortMonitor.java
new file mode 100644
index 00000000..08723082
--- /dev/null
+++ b/base/src/main/java/software/xdev/tci/startup/wait/AbortMonitor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.startup.wait;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+
+/**
+ * Monitor that holds the state if a WaitStrategy should be aborted
+ */
+public class AbortMonitor
+{
+ private final AtomicReference abortEx = new AtomicReference<>();
+
+ public void trigger(final RuntimeException rex)
+ {
+ this.abortEx.compareAndSet(null, rex);
+ }
+
+ public void throwIfRequired()
+ {
+ if(this.shouldAbort())
+ {
+ throw this.abortEx.get();
+ }
+ }
+
+ public boolean shouldAbort()
+ {
+ return this.abortEx.get() != null;
+ }
+}
diff --git a/base/src/main/java/software/xdev/tci/startup/wait/FastAbortOnContainerDeathWaitStrategy.java b/base/src/main/java/software/xdev/tci/startup/wait/FastAbortOnContainerDeathWaitStrategy.java
new file mode 100644
index 00000000..694be3e7
--- /dev/null
+++ b/base/src/main/java/software/xdev/tci/startup/wait/FastAbortOnContainerDeathWaitStrategy.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.startup.wait;
+
+import java.time.Duration;
+import java.util.Optional;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+
+import org.rnorth.ducttape.ratelimits.RateLimiter;
+import org.rnorth.ducttape.ratelimits.RateLimiterBuilder;
+import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;
+import org.testcontainers.containers.wait.strategy.WaitStrategy;
+
+import software.xdev.tci.concurrent.ExecutorServiceCreatorHolder;
+import software.xdev.tci.startup.wait.holder.AbortableStrategyValues;
+import software.xdev.tci.startup.wait.holder.AbortableStrategyValuesHolder;
+import software.xdev.tci.startup.wait.strategy.AbstractWaitAbortableStrategy;
+import software.xdev.tci.startup.wait.strategy.WaitAllAbortableStrategy;
+
+
+/**
+ * A "wrapper" strategy that causes a fast-abort when the container dies.
+ *
+ * This is helpful when the container dies during the startup phase as the default wait-strategies
+ * would wait for the timeout to expire which can take up to a few minutes.
+ *
+ */
+public class FastAbortOnContainerDeathWaitStrategy extends AbstractWaitStrategy
+{
+ protected static final ExecutorService DEFAULT_EXECUTOR =
+ ExecutorServiceCreatorHolder.instance().createUnlimited("TCI-testcontainers-wait");
+
+ protected static final RateLimiter DEFAULT_RATE_LIMITER = RateLimiterBuilder
+ .newBuilder()
+ .withRate(10, TimeUnit.MINUTES)
+ .withConstantThroughput()
+ .build();
+
+ protected final WaitStrategy waitStrategy;
+ protected final ExecutorService executor;
+
+ public FastAbortOnContainerDeathWaitStrategy(final AbstractWaitAbortableStrategy> waitStrategy)
+ {
+ this((WaitStrategy)waitStrategy);
+ }
+
+ /**
+ * @apiNote It's recommended to use {@link #FastAbortOnContainerDeathWaitStrategy(AbstractWaitAbortableStrategy)}
+ */
+ public FastAbortOnContainerDeathWaitStrategy(final WaitStrategy waitStrategy)
+ {
+ this(waitStrategy, DEFAULT_EXECUTOR);
+ }
+
+ protected FastAbortOnContainerDeathWaitStrategy(
+ final WaitStrategy waitStrategy,
+ final ExecutorService executor)
+ {
+ this.waitStrategy = waitStrategy;
+ this.executor = executor;
+
+ this.withRateLimiter(DEFAULT_RATE_LIMITER);
+ }
+
+ @Override
+ protected void waitUntilReady()
+ {
+ final AbortMonitor abortMonitor = new AbortMonitor();
+ final CompletableFuture cfWaitStrategy = CompletableFuture.runAsync(
+ () -> AbortableStrategyValuesHolder.executeWith(
+ () -> this.waitStrategy.waitUntilReady(this.waitStrategyTarget),
+ new AbortableStrategyValues(abortMonitor, this.executor)),
+ this.executor
+ );
+
+ final CompletableFuture cfContainerDeathWatchDog = CompletableFuture.runAsync(
+ () -> this.runCheckIfContainerIsDead(abortMonitor, cfWaitStrategy),
+ this.executor
+ );
+
+ try
+ {
+ CompletableFuture.anyOf(cfWaitStrategy, cfContainerDeathWatchDog).join();
+ }
+ finally
+ {
+ abortMonitor.trigger(new CancellationException("Completed"));
+ }
+ }
+
+ @SuppressWarnings("java:S5411") // Irrelevant here!
+ protected void runCheckIfContainerIsDead(
+ final AbortMonitor abortMonitor,
+ final CompletableFuture> cfWaitStrategy)
+ {
+ while(!abortMonitor.shouldAbort() && !cfWaitStrategy.isDone())
+ {
+ if(!this.rateLimiterWhenReadyExceptionless(this::isContainerAlive))
+ {
+ final IllegalStateException abortEx = new IllegalStateException(
+ "Container " + this.waitStrategyTarget.getContainerId() + " is dead - Aborting");
+ abortMonitor.trigger(abortEx);
+ throw abortEx;
+ }
+ }
+ }
+
+ @SuppressWarnings("PMD.PreserveStackTrace")
+ protected T rateLimiterWhenReadyExceptionless(final Supplier supplier)
+ {
+ try
+ {
+ return this.getRateLimiter().getWhenReady(supplier::get);
+ }
+ catch(final Exception ex)
+ {
+ if(ex instanceof final RuntimeException rex)
+ {
+ throw rex;
+ }
+ throw new RuntimeException(ex);
+ }
+ }
+
+ protected boolean isContainerAlive()
+ {
+ return Optional.ofNullable(this.waitStrategyTarget.getCurrentContainerInfo().getState())
+ .filter(s -> Boolean.TRUE.equals(s.getRunning())
+ || Boolean.TRUE.equals(s.getPaused()))
+ .isPresent();
+ }
+
+ @Override
+ public WaitStrategy withStartupTimeout(final Duration startupTimeout)
+ {
+ this.waitStrategy.withStartupTimeout(startupTimeout);
+ return this;
+ }
+
+ /**
+ * Creates a new instance with {@link WaitAllAbortableStrategy} as inner strategy and allows configuring it.
+ */
+ public static FastAbortOnContainerDeathWaitStrategy waitAll(
+ final UnaryOperator configure)
+ {
+ return new FastAbortOnContainerDeathWaitStrategy(configure.apply(new WaitAllAbortableStrategy()));
+ }
+}
diff --git a/base/src/main/java/software/xdev/tci/startup/wait/holder/AbortableStrategyValues.java b/base/src/main/java/software/xdev/tci/startup/wait/holder/AbortableStrategyValues.java
new file mode 100644
index 00000000..97f5f2a3
--- /dev/null
+++ b/base/src/main/java/software/xdev/tci/startup/wait/holder/AbortableStrategyValues.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.startup.wait.holder;
+
+import java.util.concurrent.ExecutorService;
+
+import software.xdev.tci.startup.wait.AbortMonitor;
+
+
+public record AbortableStrategyValues(
+ AbortMonitor abortMonitor,
+ ExecutorService executor
+)
+{
+}
diff --git a/base/src/main/java/software/xdev/tci/startup/wait/holder/AbortableStrategyValuesHolder.java b/base/src/main/java/software/xdev/tci/startup/wait/holder/AbortableStrategyValuesHolder.java
new file mode 100644
index 00000000..92f57a23
--- /dev/null
+++ b/base/src/main/java/software/xdev/tci/startup/wait/holder/AbortableStrategyValuesHolder.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.startup.wait.holder;
+
+/**
+ * @apiNote Please note that this is the Java 21 version. The Java 25 version uses ScopedValues.
+ */
+public final class AbortableStrategyValuesHolder
+{
+ private static final ThreadLocal TL = new ThreadLocal<>();
+
+ public static void executeWith(final Runnable runnable, final AbortableStrategyValues values)
+ {
+ TL.set(values);
+ try
+ {
+ runnable.run();
+ }
+ finally
+ {
+ TL.remove();
+ }
+ }
+
+ public static AbortableStrategyValues get()
+ {
+ return TL.get();
+ }
+
+ private AbortableStrategyValuesHolder()
+ {
+ }
+}
diff --git a/base/src/main/java/software/xdev/tci/startup/wait/strategy/AbstractWaitAbortableStrategy.java b/base/src/main/java/software/xdev/tci/startup/wait/strategy/AbstractWaitAbortableStrategy.java
new file mode 100644
index 00000000..5dd0be68
--- /dev/null
+++ b/base/src/main/java/software/xdev/tci/startup/wait/strategy/AbstractWaitAbortableStrategy.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.startup.wait.strategy;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.rnorth.ducttape.unreliables.Unreliables;
+import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;
+
+import software.xdev.tci.startup.wait.AbortMonitor;
+import software.xdev.tci.startup.wait.holder.AbortableStrategyValuesHolder;
+
+
+public abstract class AbstractWaitAbortableStrategy>
+ extends AbstractWaitStrategy
+{
+ protected T startupRetryUntilSuccess(final Callable callable)
+ {
+ return Unreliables.retryUntilSuccess(
+ (int)this.startupTimeout.getSeconds(),
+ TimeUnit.SECONDS,
+ callable
+ );
+ }
+
+ protected void startupRetryUntilSuccessWithRateLimitWhenNotAborted(
+ final AbortMonitor abortMonitor,
+ final Runnable runnable)
+ {
+ this.startupRetryUntilSuccess(() -> {
+ if(!abortMonitor.shouldAbort())
+ {
+ this.getRateLimiter().doWhenReady(runnable);
+ }
+ return true;
+ });
+ }
+
+ @Override
+ protected void waitUntilReady()
+ {
+ this.waitUntilReady(AbortableStrategyValuesHolder.get().abortMonitor());
+ }
+
+ protected void waitUntilReady(final AbortMonitor abortMonitor)
+ {
+ // NOOP
+ }
+
+ protected ExecutorService getExecutor()
+ {
+ return AbortableStrategyValuesHolder.get().executor();
+ }
+
+ @SuppressWarnings("unchecked")
+ protected S self()
+ {
+ return (S)this;
+ }
+}
diff --git a/base/src/main/java/software/xdev/tci/startup/wait/strategy/HostPortWaitAbortableStrategy.java b/base/src/main/java/software/xdev/tci/startup/wait/strategy/HostPortWaitAbortableStrategy.java
new file mode 100644
index 00000000..8a048f64
--- /dev/null
+++ b/base/src/main/java/software/xdev/tci/startup/wait/strategy/HostPortWaitAbortableStrategy.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.startup.wait.strategy;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.ContainerLaunchException;
+import org.testcontainers.containers.wait.internal.ExternalPortListeningCheck;
+import org.testcontainers.containers.wait.internal.InternalCommandPortListeningCheck;
+import org.testcontainers.shaded.org.awaitility.Awaitility;
+
+import software.xdev.tci.startup.wait.AbortMonitor;
+
+
+/**
+ * Based on {@link org.testcontainers.containers.wait.strategy.HostPortWaitStrategy}
+ */
+public class HostPortWaitAbortableStrategy extends AbstractWaitAbortableStrategy
+{
+ private static final Logger LOG = LoggerFactory.getLogger(HostPortWaitAbortableStrategy.class);
+
+ protected int[] ports;
+
+ @Override
+ protected void waitUntilReady(final AbortMonitor abortMonitor)
+ {
+ final Set externalLivenessCheckPorts;
+ if(this.ports == null || this.ports.length == 0)
+ {
+ externalLivenessCheckPorts = this.getLivenessCheckPorts();
+ if(externalLivenessCheckPorts.isEmpty())
+ {
+ if(LOG.isDebugEnabled())
+ {
+ LOG.debug(
+ "Liveness check ports of {} is empty. Not waiting.",
+ this.waitStrategyTarget.getContainerInfo().getName()
+ );
+ }
+ return;
+ }
+ }
+ else
+ {
+ externalLivenessCheckPorts =
+ Arrays
+ .stream(this.ports)
+ .mapToObj(port -> this.waitStrategyTarget.getMappedPort(port))
+ .collect(Collectors.toSet());
+ }
+
+ abortMonitor.throwIfRequired();
+
+ final List exposedPorts = this.waitStrategyTarget.getExposedPorts();
+
+ final Set internalPorts = this.getInternalPorts(externalLivenessCheckPorts, exposedPorts);
+
+ final Callable internalCheck =
+ new InternalCommandPortListeningCheck(this.waitStrategyTarget, internalPorts);
+
+ final Callable externalCheck = new ExternalPortListeningCheck(
+ this.waitStrategyTarget,
+ externalLivenessCheckPorts
+ );
+
+ try
+ {
+ final List> futures = this.getExecutor().invokeAll(
+ Arrays.asList(
+ // Blocking
+ () -> {
+ final long startMs = System.currentTimeMillis();
+ final Boolean result = internalCheck.call();
+ LOG.debug(
+ "Internal port check {} for {} took {}ms",
+ Boolean.TRUE.equals(result) ? "passed" : "failed",
+ internalPorts,
+ System.currentTimeMillis() - startMs
+ );
+ return result;
+ },
+ // Polling
+ () -> {
+ final long startMs = System.currentTimeMillis();
+ Awaitility.await()
+ .pollInSameThread()
+ .pollInterval(Duration.ofMillis(100))
+ .pollDelay(Duration.ZERO)
+ .failFast(
+ "container is no longer running",
+ () -> !this.waitStrategyTarget.isRunning() || abortMonitor.shouldAbort())
+ .ignoreExceptions()
+ .forever()
+ .until(externalCheck);
+
+ LOG.debug(
+ "External port check passed for {} mapped as {} took {}ms",
+ internalPorts,
+ externalLivenessCheckPorts,
+ System.currentTimeMillis() - startMs
+ );
+ return true;
+ }
+ ),
+ this.startupTimeout.getSeconds(),
+ TimeUnit.SECONDS
+ );
+
+ for(final Future future : futures)
+ {
+ future.get(0, TimeUnit.SECONDS);
+ }
+ }
+ catch(final InterruptedException iex)
+ {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("Got interrupted", iex);
+ }
+ catch(final CancellationException | ExecutionException | TimeoutException e)
+ {
+ throw new ContainerLaunchException(
+ "Timed out waiting for container port to open ("
+ + this.waitStrategyTarget.getHost()
+ + " ports: "
+ + externalLivenessCheckPorts
+ + " should be listening)",
+ e
+ );
+ }
+ }
+
+ protected Set getInternalPorts(
+ final Set externalLivenessCheckPorts,
+ final List exposedPorts)
+ {
+ return exposedPorts
+ .stream()
+ .filter(it -> externalLivenessCheckPorts.contains(this.waitStrategyTarget.getMappedPort(it)))
+ .collect(Collectors.toSet());
+ }
+
+ public HostPortWaitAbortableStrategy forPorts(final int... ports)
+ {
+ this.ports = ports;
+ return this.self();
+ }
+}
diff --git a/base/src/main/java/software/xdev/tci/startup/wait/strategy/HttpWaitAbortableStrategy.java b/base/src/main/java/software/xdev/tci/startup/wait/strategy/HttpWaitAbortableStrategy.java
new file mode 100644
index 00000000..42e951ad
--- /dev/null
+++ b/base/src/main/java/software/xdev/tci/startup/wait/strategy/HttpWaitAbortableStrategy.java
@@ -0,0 +1,524 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.startup.wait.strategy;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UncheckedIOException;
+import java.net.HttpURLConnection;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URL;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509ExtendedTrustManager;
+
+import org.rnorth.ducttape.TimeoutException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.ContainerLaunchException;
+import org.testcontainers.shaded.com.google.common.io.BaseEncoding;
+
+import software.xdev.tci.startup.wait.AbortMonitor;
+
+
+/**
+ * Based on {@link org.testcontainers.containers.wait.strategy.HttpWaitStrategy}
+ */
+@SuppressWarnings("PMD.GodClass")
+public class HttpWaitAbortableStrategy extends AbstractWaitAbortableStrategy
+{
+ private static final Logger LOG = LoggerFactory.getLogger(HttpWaitAbortableStrategy.class);
+
+ /**
+ * Authorization HTTP header.
+ */
+ protected static final String HEADER_AUTHORIZATION = "Authorization";
+
+ /**
+ * Basic Authorization scheme prefix.
+ */
+ protected static final String AUTH_BASIC = "Basic ";
+
+ protected String path = "/";
+
+ protected String method = "GET";
+
+ protected final Set statusCodes = new HashSet<>();
+
+ protected boolean tlsEnabled;
+
+ protected String username;
+
+ protected String password;
+
+ protected final Map headers = new HashMap<>();
+
+ protected Predicate responsePredicate;
+
+ protected Predicate statusCodePredicate;
+
+ protected Optional livenessPort = Optional.empty();
+
+ protected Duration readTimeout = Duration.ofSeconds(1);
+
+ protected boolean allowInsecure;
+
+ // region Configure
+
+ /**
+ * Waits for the given status code.
+ *
+ * @param statusCode the expected status code
+ * @return this
+ */
+ public HttpWaitAbortableStrategy forStatusCode(final int statusCode)
+ {
+ this.statusCodes.add(statusCode);
+ return this.self();
+ }
+
+ /**
+ * Waits for the status code to pass the given predicate
+ *
+ * @param statusCodePredicate The predicate to test the response against
+ * @return this
+ */
+ public HttpWaitAbortableStrategy forStatusCodeMatching(final Predicate statusCodePredicate)
+ {
+ this.statusCodePredicate = statusCodePredicate;
+ return this;
+ }
+
+ /**
+ * Waits for the given path.
+ *
+ * @param path the path to check
+ * @return this
+ */
+ public HttpWaitAbortableStrategy forPath(final String path)
+ {
+ this.path = path;
+ return this.self();
+ }
+
+ /**
+ * Wait for the given port.
+ *
+ * @param port the given port
+ * @return this
+ */
+ public HttpWaitAbortableStrategy forPort(final int port)
+ {
+ this.livenessPort = Optional.of(port);
+ return this.self();
+ }
+
+ /**
+ * Indicates that the status check should use HTTPS.
+ *
+ * @return this
+ */
+ public HttpWaitAbortableStrategy usingTls()
+ {
+ this.tlsEnabled = true;
+ return this.self();
+ }
+
+ /**
+ * Indicates the HTTP method to use (GET by default).
+ *
+ * @param method the HTTP method.
+ * @return this
+ */
+ public HttpWaitAbortableStrategy withMethod(final String method)
+ {
+ this.method = method;
+ return this.self();
+ }
+
+ /**
+ * Indicates that HTTPS connection could use untrusted (self signed) certificate chains.
+ *
+ * @return this
+ */
+ public HttpWaitAbortableStrategy allowInsecure()
+ {
+ this.allowInsecure = true;
+ return this.self();
+ }
+
+ /**
+ * Authenticate with HTTP Basic Authorization credentials.
+ *
+ * @param username the username
+ * @param password the password
+ * @return this
+ */
+ public HttpWaitAbortableStrategy withBasicCredentials(
+ final String username,
+ final String password)
+ {
+ this.username = username;
+ this.password = password;
+ return this.self();
+ }
+
+ /**
+ * Add a custom HTTP Header to the call.
+ *
+ * @param name The HTTP Header name
+ * @param value The HTTP Header value
+ * @return this
+ */
+ public HttpWaitAbortableStrategy withHeader(
+ final String name,
+ final String value)
+ {
+ this.headers.put(name, value);
+ return this.self();
+ }
+
+ /**
+ * Add multiple custom HTTP Headers to the call.
+ *
+ * @param headers Headers map of name/value
+ * @return this
+ */
+ public HttpWaitAbortableStrategy withHeaders(final Map headers)
+ {
+ this.headers.putAll(headers);
+ return this.self();
+ }
+
+ /**
+ * Set the HTTP connections read timeout.
+ *
+ * @param timeout the timeout (minimum 1 millisecond)
+ * @return this
+ */
+ public HttpWaitAbortableStrategy withReadTimeout(final Duration timeout)
+ {
+ if(timeout.toMillis() < 1)
+ {
+ throw new IllegalArgumentException("you cannot specify a value smaller than 1 ms");
+ }
+ this.readTimeout = timeout;
+ return this.self();
+ }
+
+ /**
+ * Waits for the response to pass the given predicate
+ *
+ * @param responsePredicate The predicate to test the response against
+ * @return this
+ */
+ public HttpWaitAbortableStrategy forResponsePredicate(final Predicate responsePredicate)
+ {
+ this.responsePredicate = responsePredicate;
+ return this.self();
+ }
+
+ // endregion
+
+ @Override
+ protected void waitUntilReady(final AbortMonitor abortMonitor)
+ {
+ final String containerName = this.waitStrategyTarget.getContainerInfo().getName();
+
+ final Integer livenessCheckPort = this.livenessPort
+ .map(this.waitStrategyTarget::getMappedPort)
+ .orElseGet(() -> {
+ final Set livenessCheckPorts = this.getLivenessCheckPorts();
+ if(livenessCheckPorts == null || livenessCheckPorts.isEmpty())
+ {
+ LOG.warn("{}: No exposed ports or mapped ports - cannot wait for status", containerName);
+ return -1;
+ }
+ return livenessCheckPorts.iterator().next();
+ });
+
+ abortMonitor.throwIfRequired();
+
+ if(null == livenessCheckPort || -1 == livenessCheckPort)
+ {
+ return;
+ }
+ final URI rawUri = this.buildLivenessUri(livenessCheckPort);
+ final String uri = rawUri.toString();
+
+ try
+ {
+ // Un-map the port for logging
+ final int originalPort = this.waitStrategyTarget
+ .getExposedPorts()
+ .stream()
+ .filter(exposedPort -> rawUri.getPort() == this.waitStrategyTarget.getMappedPort(exposedPort))
+ .findFirst()
+ .orElseThrow(() -> new IllegalStateException("Target port " + rawUri.getPort() + " is not exposed"));
+ LOG.info(
+ "{}: Waiting for {} seconds for URL: {} (where port {} maps to container port {})",
+ containerName,
+ this.startupTimeout.getSeconds(),
+ uri,
+ rawUri.getPort(),
+ originalPort
+ );
+ }
+ catch(final RuntimeException e)
+ {
+ // do not allow a failure in logging to prevent progress, but log for diagnosis
+ LOG.warn("Unexpected error occurred - will proceed to try to wait anyway", e);
+ }
+
+ abortMonitor.throwIfRequired();
+
+ // try to connect to the URL
+ try
+ {
+ this.startupRetryUntilSuccessWithRateLimitWhenNotAborted(
+ abortMonitor,
+ () -> this.connectAndCheck(uri));
+ abortMonitor.throwIfRequired();
+ }
+ catch(final TimeoutException e)
+ {
+ throw new ContainerLaunchException(
+ String.format(
+ "Timed out waiting for URL to be accessible (%s should return HTTP %s)",
+ uri,
+ this.statusCodes.isEmpty() ? HttpURLConnection.HTTP_OK : this.statusCodes
+ ),
+ e
+ );
+ }
+ }
+
+ protected void connectAndCheck(final String uri)
+ {
+ try
+ {
+ final HttpURLConnection connection = this.openConnection(uri);
+ connection.setReadTimeout(Math.toIntExact(this.readTimeout.toMillis()));
+
+ // authenticate
+ if(this.username != null && !this.username.isEmpty())
+ {
+ connection.setRequestProperty(
+ HEADER_AUTHORIZATION,
+ this.buildAuthString(this.username, this.password)
+ );
+ connection.setUseCaches(false);
+ }
+
+ // Add user configured headers
+ this.headers.forEach(connection::setRequestProperty);
+ connection.setRequestMethod(this.method);
+ connection.connect();
+
+ LOG.trace("Get response code {}", connection.getResponseCode());
+
+ // Choose the statusCodePredicate strategy depending on what we defined.
+ if(!this.determineStatusCodePredicate().test(connection.getResponseCode()))
+ {
+ throw new IllegalStateException("HTTP response code was: " + connection.getResponseCode());
+ }
+
+ if(this.responsePredicate != null)
+ {
+ final String responseBody = this.getResponseBody(connection);
+
+ LOG.trace("Got response {}", responseBody);
+
+ if(!this.responsePredicate.test(responseBody))
+ {
+ throw new IllegalStateException("Response: " + responseBody + " did not match predicate");
+ }
+ }
+ }
+ catch(final IOException e)
+ {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private Predicate determineStatusCodePredicate()
+ {
+ if(this.statusCodes.isEmpty() && this.statusCodePredicate == null)
+ {
+ // We have no status code and no predicate so we expect a 200 OK response code
+ return responseCode -> HttpURLConnection.HTTP_OK == responseCode;
+ }
+ else if(!this.statusCodes.isEmpty() && this.statusCodePredicate == null)
+ {
+ // We use the default status predicate checker when we only have status codes
+ return this.statusCodes::contains;
+ }
+ else if(this.statusCodes.isEmpty())
+ {
+ // We only have a predicate
+ return this.statusCodePredicate;
+ }
+
+ // We have both predicate and status code
+ return this.statusCodePredicate.or(this.statusCodes::contains);
+ }
+
+ protected HttpURLConnection openConnection(final String uri) throws IOException
+ {
+ if(!this.tlsEnabled)
+ {
+ return (HttpURLConnection)new URL(uri).openConnection();
+ }
+
+ final HttpsURLConnection connection = (HttpsURLConnection)new URL(uri).openConnection();
+ if(this.allowInsecure)
+ {
+ // Create a trust manager that does not validate certificate chains
+ // and trust all certificates
+ final TrustManager[] trustAllCerts = new TrustManager[]{
+ new X509ExtendedTrustManager()
+ {
+ @Override
+ public X509Certificate[] getAcceptedIssuers()
+ {
+ return new X509Certificate[0];
+ }
+
+ @Override
+ public void checkClientTrusted(final X509Certificate[] certs, final String authType)
+ {
+ }
+
+ @Override
+ public void checkServerTrusted(final X509Certificate[] certs, final String authType)
+ {
+ }
+
+ @Override
+ public void checkClientTrusted(
+ final X509Certificate[] chain,
+ final String authType,
+ final Socket socket)
+ {
+ }
+
+ @Override
+ public void checkServerTrusted(
+ final X509Certificate[] chain,
+ final String authType,
+ final Socket socket)
+ {
+ }
+
+ @Override
+ public void checkClientTrusted(
+ final X509Certificate[] chain,
+ final String authType,
+ final SSLEngine engine)
+ {
+ }
+
+ @Override
+ public void checkServerTrusted(
+ final X509Certificate[] chain,
+ final String authType,
+ final SSLEngine engine)
+ {
+ }
+ },
+ };
+
+ try
+ {
+ // Create custom SSL context and set the "trust all certificates" trust manager
+ final SSLContext sc = SSLContext.getInstance("SSL");
+ sc.init(new KeyManager[0], trustAllCerts, new SecureRandom());
+ connection.setSSLSocketFactory(sc.getSocketFactory());
+ }
+ catch(final NoSuchAlgorithmException | KeyManagementException ex)
+ {
+ throw new IOException("Unable to create custom SSL factory instance", ex);
+ }
+ }
+
+ return connection;
+ }
+
+ /**
+ * Build the URI on which to check if the container is ready.
+ *
+ * @param livenessCheckPort the liveness port
+ * @return the liveness URI
+ */
+ @SuppressWarnings("checkstyle:MagicNumber")
+ protected URI buildLivenessUri(final int livenessCheckPort)
+ {
+ final String scheme = (this.tlsEnabled ? "https" : "http") + "://";
+ final String host = this.waitStrategyTarget.getHost();
+
+ final String portSuffix =
+ this.tlsEnabled && livenessCheckPort == 443
+ || !this.tlsEnabled && livenessCheckPort == 80
+ ? ""
+ : ":" + livenessCheckPort;
+
+ return URI.create(scheme + host + portSuffix + this.path);
+ }
+
+ /**
+ * @param username the username
+ * @param password the password
+ * @return a basic authentication string for the given credentials
+ */
+ protected String buildAuthString(final String username, final String password)
+ {
+ return AUTH_BASIC + BaseEncoding.base64().encode((username + ":" + password).getBytes());
+ }
+
+ @SuppressWarnings({"PMD.AvoidStringBuilderOrBuffer", "checkstyle:MagicNumber"})
+ protected String getResponseBody(final HttpURLConnection connection) throws IOException
+ {
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(
+ connection.getResponseCode() >= 200 && connection.getResponseCode() < 300
+ ? connection.getInputStream()
+ : connection.getErrorStream()));
+
+ final StringBuilder builder = new StringBuilder();
+ String line;
+ while((line = reader.readLine()) != null)
+ {
+ builder.append(line);
+ }
+ return builder.toString();
+ }
+}
diff --git a/base/src/main/java/software/xdev/tci/startup/wait/strategy/LogMessageWaitAbortableStrategy.java b/base/src/main/java/software/xdev/tci/startup/wait/strategy/LogMessageWaitAbortableStrategy.java
new file mode 100644
index 00000000..651d6ee9
--- /dev/null
+++ b/base/src/main/java/software/xdev/tci/startup/wait/strategy/LogMessageWaitAbortableStrategy.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.startup.wait.strategy;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Predicate;
+
+import org.testcontainers.containers.ContainerLaunchException;
+import org.testcontainers.containers.output.FrameConsumerResultCallback;
+import org.testcontainers.containers.output.OutputFrame;
+import org.testcontainers.containers.output.WaitingConsumer;
+
+import com.github.dockerjava.api.command.LogContainerCmd;
+
+import software.xdev.tci.startup.wait.AbortMonitor;
+
+
+/**
+ * Based on {@link org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy}
+ */
+public class LogMessageWaitAbortableStrategy extends AbstractWaitAbortableStrategy
+{
+ protected String regEx;
+
+ protected int times = 1;
+
+ @Override
+ protected void waitUntilReady(final AbortMonitor abortMonitor)
+ {
+ try(final LogContainerCmd cmd = this.waitStrategyTarget
+ .getDockerClient()
+ .logContainerCmd(this.waitStrategyTarget.getContainerId())
+ .withFollowStream(true)
+ .withSince(0)
+ .withStdOut(true)
+ .withStdErr(true))
+ {
+ try(final FrameConsumerResultCallback callback = new FrameConsumerResultCallback())
+ {
+ final WaitingConsumer waitingConsumer = new WaitingConsumer();
+ callback.addConsumer(OutputFrame.OutputType.STDOUT, waitingConsumer);
+ callback.addConsumer(OutputFrame.OutputType.STDERR, waitingConsumer);
+
+ cmd.exec(callback);
+
+ final Predicate waitPredicate = outputFrame -> {
+ abortMonitor.throwIfRequired();
+
+ // (?s) enables line terminator matching (equivalent to Pattern.DOTALL)
+ return outputFrame.getUtf8String().matches("(?s)" + this.regEx);
+ };
+ try
+ {
+ waitingConsumer.waitUntil(
+ waitPredicate,
+ this.startupTimeout.getSeconds(),
+ TimeUnit.SECONDS,
+ this.times);
+ }
+ catch(final TimeoutException e)
+ {
+ throw new ContainerLaunchException(
+ "Timed out waiting for log output matching '" + this.regEx + "'",
+ e);
+ }
+ }
+ catch(final IOException ioe)
+ {
+ throw new UncheckedIOException(ioe);
+ }
+ }
+ }
+
+ public LogMessageWaitAbortableStrategy withRegEx(final String regEx)
+ {
+ this.regEx = regEx;
+ return this.self();
+ }
+
+ public LogMessageWaitAbortableStrategy withTimes(final int times)
+ {
+ this.times = times;
+ return this.self();
+ }
+}
diff --git a/base/src/main/java/software/xdev/tci/startup/wait/strategy/WaitAllAbortableStrategy.java b/base/src/main/java/software/xdev/tci/startup/wait/strategy/WaitAllAbortableStrategy.java
new file mode 100644
index 00000000..eb69606b
--- /dev/null
+++ b/base/src/main/java/software/xdev/tci/startup/wait/strategy/WaitAllAbortableStrategy.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.startup.wait.strategy;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.rnorth.ducttape.timeouts.Timeouts;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
+import org.testcontainers.containers.wait.strategy.WaitStrategy;
+
+import software.xdev.tci.startup.wait.AbortMonitor;
+import software.xdev.tci.startup.wait.holder.AbortableStrategyValues;
+import software.xdev.tci.startup.wait.holder.AbortableStrategyValuesHolder;
+
+
+/**
+ * Based on {@link WaitAllStrategy}
+ */
+public class WaitAllAbortableStrategy extends AbstractWaitAbortableStrategy
+{
+ private static final Logger LOG = LoggerFactory.getLogger(WaitAllAbortableStrategy.class);
+
+ protected final WaitAllStrategy.Mode mode;
+
+ protected final List strategies = new ArrayList<>();
+
+ public WaitAllAbortableStrategy()
+ {
+ this(WaitAllStrategy.Mode.WITH_OUTER_TIMEOUT);
+ }
+
+ public WaitAllAbortableStrategy(final WaitAllStrategy.Mode mode)
+ {
+ this.mode = mode;
+ }
+
+ @Override
+ protected void waitUntilReady(final AbortMonitor abortMonitor)
+ {
+ if(this.mode == WaitAllStrategy.Mode.WITH_INDIVIDUAL_TIMEOUTS_ONLY)
+ {
+ this.waitUntilNestedStrategiesAreReady(abortMonitor);
+ }
+ else
+ {
+ final AbortableStrategyValues valuesToPropagate = AbortableStrategyValuesHolder.get();
+ Timeouts.doWithTimeout(
+ (int)this.startupTimeout.toMillis(),
+ TimeUnit.MILLISECONDS,
+ () -> AbortableStrategyValuesHolder.executeWith(
+ () -> this.waitUntilNestedStrategiesAreReady(abortMonitor),
+ valuesToPropagate)
+ );
+ }
+ }
+
+ private void waitUntilNestedStrategiesAreReady(final AbortMonitor abortMonitor)
+ {
+ for(final WaitStrategy strategy : this.strategies)
+ {
+ abortMonitor.throwIfRequired();
+
+ final long startMs = System.currentTimeMillis();
+ LOG.debug("Waiting for {}", strategy);
+
+ strategy.waitUntilReady(this.waitStrategyTarget);
+
+ LOG.debug("Finished waiting for {}, took {}ms", strategy, System.currentTimeMillis() - startMs);
+ }
+ }
+
+ public WaitAllAbortableStrategy withStrategy(final AbstractWaitAbortableStrategy> strategy)
+ {
+ return this.withWaitStrategy(strategy);
+ }
+
+ /**
+ * @apiNote It's recommended to use {@link #withStrategy(AbstractWaitAbortableStrategy)}
+ */
+ public WaitAllAbortableStrategy withWaitStrategy(final WaitStrategy strategy)
+ {
+ if(this.mode == WaitAllStrategy.Mode.WITH_OUTER_TIMEOUT)
+ {
+ this.applyStartupTimeout(strategy);
+ }
+
+ this.strategies.add(strategy);
+ return this.self();
+ }
+
+ @Override
+ public WaitAllAbortableStrategy withStartupTimeout(final Duration startupTimeout)
+ {
+ if(this.mode == WaitAllStrategy.Mode.WITH_INDIVIDUAL_TIMEOUTS_ONLY)
+ {
+ throw new IllegalStateException(
+ String.format(
+ "Changing startup timeout is not supported with mode %s",
+ WaitAllStrategy.Mode.WITH_INDIVIDUAL_TIMEOUTS_ONLY
+ )
+ );
+ }
+
+ super.withStartupTimeout(startupTimeout);
+
+ this.strategies.forEach(this::applyStartupTimeout);
+ return this.self();
+ }
+
+ private void applyStartupTimeout(final WaitStrategy childStrategy)
+ {
+ childStrategy.withStartupTimeout(this.startupTimeout);
+ }
+}
diff --git a/base/src/main/java25/software/xdev/tci/startup/wait/holder/AbortableStrategyValuesHolder.java b/base/src/main/java25/software/xdev/tci/startup/wait/holder/AbortableStrategyValuesHolder.java
new file mode 100644
index 00000000..d0c2b249
--- /dev/null
+++ b/base/src/main/java25/software/xdev/tci/startup/wait/holder/AbortableStrategyValuesHolder.java
@@ -0,0 +1,21 @@
+package software.xdev.tci.startup.wait.holder;
+
+
+public final class AbortableStrategyValuesHolder
+{
+ private static final ScopedValue SV = ScopedValue.newInstance();
+
+ public static void executeWith(final Runnable runnable, final AbortableStrategyValues values)
+ {
+ ScopedValue.where(SV, values).run(runnable);
+ }
+
+ public static AbortableStrategyValues get()
+ {
+ return SV.get();
+ }
+
+ private AbortableStrategyValuesHolder()
+ {
+ }
+}
diff --git a/db-jdbc/src/main/java/software/xdev/tci/db/containers/WaitableJDBCContainer.java b/db-jdbc/src/main/java/software/xdev/tci/db/containers/WaitableJDBCContainer.java
index 2decd1cf..62ca1a55 100644
--- a/db-jdbc/src/main/java/software/xdev/tci/db/containers/WaitableJDBCContainer.java
+++ b/db-jdbc/src/main/java/software/xdev/tci/db/containers/WaitableJDBCContainer.java
@@ -16,22 +16,22 @@
package software.xdev.tci.db.containers;
import java.sql.Connection;
+import java.sql.SQLException;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
-import org.rnorth.ducttape.TimeoutException;
import org.rnorth.ducttape.ratelimits.RateLimiter;
import org.rnorth.ducttape.ratelimits.RateLimiterBuilder;
import org.rnorth.ducttape.unreliables.Unreliables;
-import org.testcontainers.containers.ContainerLaunchException;
import org.testcontainers.containers.JdbcDatabaseContainer;
-import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;
-import org.testcontainers.containers.wait.strategy.Wait;
-import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.containers.wait.strategy.WaitStrategyTarget;
import software.xdev.tci.envperf.EnvironmentPerformance;
+import software.xdev.tci.startup.wait.AbortMonitor;
+import software.xdev.tci.startup.wait.FastAbortOnContainerDeathWaitStrategy;
+import software.xdev.tci.startup.wait.strategy.AbstractWaitAbortableStrategy;
+import software.xdev.tci.startup.wait.strategy.HostPortWaitAbortableStrategy;
public interface WaitableJDBCContainer extends WaitStrategyTarget
@@ -39,10 +39,11 @@ public interface WaitableJDBCContainer extends WaitStrategyTarget
@SuppressWarnings("checkstyle:MagicNumber")
default WaitStrategy completeJDBCWaitStrategy()
{
- return new WaitAllStrategy()
- .withStrategy(Wait.defaultWaitStrategy())
+ return FastAbortOnContainerDeathWaitStrategy.waitAll(s -> s
+ .withStartupTimeout(Duration.ofSeconds(40L + EnvironmentPerformance.cpuSlownessFactor() * 20L))
+ .withStrategy(new HostPortWaitAbortableStrategy())
.withStrategy(new JDBCWaitStrategy())
- .withStartupTimeout(Duration.ofSeconds(40L + EnvironmentPerformance.cpuSlownessFactor() * 20L));
+ );
}
WaitStrategy getWaitStrategy();
@@ -59,20 +60,19 @@ default void waitUntilContainerStarted()
/**
* @apiNote Assumes that the container is already started
*/
- class JDBCWaitStrategy extends AbstractWaitStrategy
+ class JDBCWaitStrategy extends AbstractWaitAbortableStrategy
{
@SuppressWarnings("checkstyle:MagicNumber")
public JDBCWaitStrategy()
{
this.withRateLimiter(RateLimiterBuilder.newBuilder()
- .withRate(200, TimeUnit.MILLISECONDS)
+ .withRate(5, TimeUnit.SECONDS)
.withConstantThroughput()
.build());
}
- @SuppressWarnings("PMD.PreserveStackTrace")
@Override
- protected void waitUntilReady()
+ protected void waitUntilReady(final AbortMonitor abortMonitor)
{
if(!(this.waitStrategyTarget instanceof final JdbcDatabaseContainer> container))
{
@@ -80,45 +80,33 @@ protected void waitUntilReady()
"Container must implement JdbcDatabaseContainer and WaitableJDBCContainer");
}
- try
- {
- this.waitUntilJDBCValid(container);
- }
- catch(final TimeoutException e)
- {
- throw new ContainerLaunchException(
- "JDBCContainer cannot be accessed by (JDBC URL: "
- + container.getJdbcUrl()
- + "), please check container logs");
- }
- }
-
- protected void waitUntilJDBCValid(final JdbcDatabaseContainer> container)
- {
- Unreliables.retryUntilTrue(
- (int)this.startupTimeout.getSeconds(),
- TimeUnit.SECONDS,
- // Rate limit creation of connections as this is quite an expensive operation
- () -> this.getRateLimiter().getWhenReady(() -> {
+ // Rate limit creation of connections as this is quite an expensive operation
+ this.startupRetryUntilSuccessWithRateLimitWhenNotAborted(
+ abortMonitor,
+ () -> {
try(final Connection connection = container.createConnection(""))
{
- return this.waitUntilJDBCConnectionValidated(container, connection);
+ this.waitUntilJDBCConnectionValidated(container, connection);
+ }
+ catch(final SQLException sqlEx)
+ {
+ throw new IllegalStateException("SQL failed", sqlEx);
}
- })
+ }
);
}
@SuppressWarnings({"unused", "checkstyle:MagicNumber"}) // Parameter might be used by extension
- protected boolean waitUntilJDBCConnectionValidated(
+ protected void waitUntilJDBCConnectionValidated(
final JdbcDatabaseContainer> container,
final Connection connection)
{
final RateLimiter validateJDBCRateLimiter = RateLimiterBuilder.newBuilder()
- .withRate(50, TimeUnit.MILLISECONDS)
+ .withRate(20, TimeUnit.SECONDS)
.withConstantThroughput()
.build();
- return Unreliables.retryUntilSuccess(
+ Unreliables.retryUntilSuccess(
// If this fails after startupTimeout / 2 (min=3s, max=30s)
// it might be possible that the connection is somehow corrupted or broken
// -> Build a new connection after that (see waitUntilJDBCValid)
diff --git a/mockserver/src/main/java/software/xdev/tci/mockserver/containers/TCIMockserverContainer.java b/mockserver/src/main/java/software/xdev/tci/mockserver/containers/TCIMockserverContainer.java
new file mode 100644
index 00000000..50963957
--- /dev/null
+++ b/mockserver/src/main/java/software/xdev/tci/mockserver/containers/TCIMockserverContainer.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.mockserver.containers;
+
+import org.testcontainers.images.RemoteDockerImage;
+import org.testcontainers.utility.DockerImageName;
+
+import com.github.dockerjava.api.command.InspectContainerResponse;
+
+import software.xdev.tci.misc.ContainerMemory;
+import software.xdev.tci.startup.error.java.fatal.HsErrPidStartUpCrashReporter;
+import software.xdev.tci.startup.wait.FastAbortOnContainerDeathWaitStrategy;
+import software.xdev.tci.startup.wait.strategy.LogMessageWaitAbortableStrategy;
+import software.xdev.testcontainers.mockserver.containers.MockServerContainer;
+
+
+@SuppressWarnings("java:S2160")
+public class TCIMockserverContainer extends MockServerContainer
+{
+ private final HsErrPidStartUpCrashReporter hsErrPidStartUpCrashReporter;
+
+ public TCIMockserverContainer(final RemoteDockerImage image)
+ {
+ super(image);
+ this.hsErrPidStartUpCrashReporter = new HsErrPidStartUpCrashReporter(this);
+ }
+
+ public TCIMockserverContainer(final DockerImageName dockerImageName)
+ {
+ super(dockerImageName);
+ this.hsErrPidStartUpCrashReporter = new HsErrPidStartUpCrashReporter(this);
+ }
+
+ public TCIMockserverContainer(final String tag)
+ {
+ super(tag);
+ this.hsErrPidStartUpCrashReporter = new HsErrPidStartUpCrashReporter(this);
+ }
+
+ public TCIMockserverContainer()
+ {
+ this.hsErrPidStartUpCrashReporter = new HsErrPidStartUpCrashReporter(this);
+ }
+
+ @Override
+ protected void containerIsStarted(final InspectContainerResponse containerInfo, final boolean reused)
+ {
+ this.hsErrPidStartUpCrashReporter.containerIsStarted();
+ super.containerIsStarted(containerInfo, reused);
+ }
+
+ @Override
+ protected void containerIsStopping(final InspectContainerResponse containerInfo)
+ {
+ this.hsErrPidStartUpCrashReporter.containerIsStopping(this.logger());
+ super.containerIsStopping(containerInfo);
+ }
+
+ public static TCIMockserverContainer createDefaultForFactory()
+ {
+ final TCIMockserverContainer container = new TCIMockserverContainer();
+ container
+ .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M))
+ .waitingFor(new FastAbortOnContainerDeathWaitStrategy(new LogMessageWaitAbortableStrategy()
+ .withRegEx(".*started on port: " + PORT + ".*")
+ ));
+ return container;
+ }
+}
diff --git a/mockserver/src/main/java/software/xdev/tci/mockserver/factory/MockServerTCIFactory.java b/mockserver/src/main/java/software/xdev/tci/mockserver/factory/MockServerTCIFactory.java
index d3d8b25e..5cb0bf03 100644
--- a/mockserver/src/main/java/software/xdev/tci/mockserver/factory/MockServerTCIFactory.java
+++ b/mockserver/src/main/java/software/xdev/tci/mockserver/factory/MockServerTCIFactory.java
@@ -22,8 +22,8 @@
import org.testcontainers.containers.Network;
import software.xdev.tci.factory.ondemand.OnDemandTCIFactory;
-import software.xdev.tci.misc.ContainerMemory;
import software.xdev.tci.mockserver.MockServerTCI;
+import software.xdev.tci.mockserver.containers.TCIMockserverContainer;
import software.xdev.testcontainers.mockserver.containers.MockServerContainer;
@@ -39,7 +39,6 @@ protected MockServerTCIFactory(
super(infraBuilder, containerBuilder, containerBaseName, containerLoggerName);
}
- @SuppressWarnings("resource")
protected MockServerTCIFactory(
final BiFunction infraBuilder,
final String additionalContainerBaseName,
@@ -47,8 +46,7 @@ protected MockServerTCIFactory(
{
super(
infraBuilder,
- () -> new MockServerContainer()
- .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M)),
+ TCIMockserverContainer::createDefaultForFactory,
"mockserver-" + additionalContainerBaseName,
"container.mockserver." + additionalLoggerName);
}
diff --git a/mockserver/src/main/java/software/xdev/tci/mockserver/factory/PreStartableMockServerTCIFactory.java b/mockserver/src/main/java/software/xdev/tci/mockserver/factory/PreStartableMockServerTCIFactory.java
index 14d0346b..465ff0ee 100644
--- a/mockserver/src/main/java/software/xdev/tci/mockserver/factory/PreStartableMockServerTCIFactory.java
+++ b/mockserver/src/main/java/software/xdev/tci/mockserver/factory/PreStartableMockServerTCIFactory.java
@@ -18,15 +18,14 @@
import java.util.function.BiFunction;
import software.xdev.tci.factory.prestart.PreStartableTCIFactory;
-import software.xdev.tci.misc.ContainerMemory;
import software.xdev.tci.mockserver.MockServerTCI;
+import software.xdev.tci.mockserver.containers.TCIMockserverContainer;
import software.xdev.testcontainers.mockserver.containers.MockServerContainer;
public abstract class PreStartableMockServerTCIFactory
extends PreStartableTCIFactory
{
- @SuppressWarnings("resource")
protected PreStartableMockServerTCIFactory(
final BiFunction infraBuilder,
final String additionalContainerBaseName,
@@ -35,8 +34,7 @@ protected PreStartableMockServerTCIFactory(
{
super(
infraBuilder,
- () -> new MockServerContainer()
- .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M)),
+ TCIMockserverContainer::createDefaultForFactory,
"mockserver-" + additionalContainerBaseName,
"container.mockserver." + additionalLoggerName,
prestartName);
diff --git a/oidc-server-mock/src/main/java/software/xdev/tci/oidc/factory/BaseOIDCTCIFactory.java b/oidc-server-mock/src/main/java/software/xdev/tci/oidc/factory/BaseOIDCTCIFactory.java
index 62a55a75..37a1e49a 100644
--- a/oidc-server-mock/src/main/java/software/xdev/tci/oidc/factory/BaseOIDCTCIFactory.java
+++ b/oidc-server-mock/src/main/java/software/xdev/tci/oidc/factory/BaseOIDCTCIFactory.java
@@ -21,9 +21,6 @@
import java.util.function.Supplier;
import org.apache.hc.core5.http.HttpStatus;
-import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;
-import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
-import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
import software.xdev.tci.envperf.EnvironmentPerformance;
import software.xdev.tci.factory.prestart.PreStartableTCIFactory;
@@ -32,6 +29,9 @@
import software.xdev.tci.oidc.BaseOIDCTCI;
import software.xdev.tci.oidc.containers.BaseOIDCServerContainer;
import software.xdev.tci.oidc.containers.OIDCServerContainer;
+import software.xdev.tci.startup.wait.FastAbortOnContainerDeathWaitStrategy;
+import software.xdev.tci.startup.wait.strategy.HostPortWaitAbortableStrategy;
+import software.xdev.tci.startup.wait.strategy.HttpWaitAbortableStrategy;
@SuppressWarnings("java:S119")
@@ -92,22 +92,21 @@ public static OIDCServerContainer createDefaultContainer()
return createDefaultContainer(null);
}
- @SuppressWarnings({"resource", "checkstyle:MagicNumber"})
+ @SuppressWarnings({"checkstyle:MagicNumber"})
public static OIDCServerContainer createDefaultContainer(final Consumer customizer)
{
final OIDCServerContainer oidcServerContainer = new OIDCServerContainer()
.withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M))
- .waitingFor(
- new WaitAllStrategy()
- .withStartupTimeout(Duration.ofSeconds(40L + 20L * EnvironmentPerformance.cpuSlownessFactor()))
- .withStrategy(new HostPortWaitStrategy())
- .withStrategy(
- new HttpWaitStrategy()
- .forPort(BaseOIDCServerContainer.PORT)
- .forPath("/")
- .forStatusCode(HttpStatus.SC_OK)
- .withReadTimeout(Duration.ofSeconds(10))
- )
+ .waitingFor(FastAbortOnContainerDeathWaitStrategy.waitAll(s -> s
+ .withStartupTimeout(Duration.ofSeconds(40L + 20L * EnvironmentPerformance.cpuSlownessFactor()))
+ .withStrategy(new HostPortWaitAbortableStrategy())
+ .withStrategy(
+ new HttpWaitAbortableStrategy()
+ .forPort(BaseOIDCServerContainer.PORT)
+ .forPath("/")
+ .forStatusCode(HttpStatus.SC_OK)
+ .withReadTimeout(Duration.ofSeconds(10))
+ ))
);
if(customizer != null)
diff --git a/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java b/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java
index 055484ae..c98840e4 100644
--- a/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java
+++ b/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java
@@ -31,9 +31,6 @@
import org.rnorth.ducttape.unreliables.Unreliables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;
-import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
-import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
import org.testcontainers.images.RemoteDockerImage;
import software.xdev.tci.concurrent.TCIExecutorServiceHolder;
@@ -45,6 +42,9 @@
import software.xdev.tci.selenium.containers.SeleniumBrowserWebDriverContainer;
import software.xdev.tci.selenium.factory.config.BrowserTCIFactoryConfig;
import software.xdev.tci.serviceloading.TCIServiceLoaderHolder;
+import software.xdev.tci.startup.wait.FastAbortOnContainerDeathWaitStrategy;
+import software.xdev.tci.startup.wait.strategy.HostPortWaitAbortableStrategy;
+import software.xdev.tci.startup.wait.strategy.LogMessageWaitAbortableStrategy;
import software.xdev.testcontainers.selenium.containers.browser.BrowserWebDriverContainer;
import software.xdev.testcontainers.selenium.containers.recorder.SeleniumRecordingContainer;
@@ -119,12 +119,11 @@ public static SeleniumBrowserWebDriverContainer createDefaultContainer(
// https://github.com/SeleniumHQ/docker-selenium/issues/2355
.withEnv("SE_ENABLE_TRACING", "false")
// Some (AWS) CPUs are completely overloaded with the default 15s timeout -> increase it
- .waitingFor(new WaitAllStrategy()
- .withStrategy(new LogMessageWaitStrategy()
- .withRegEx(".*(Started Selenium Standalone).*\n")
- .withStartupTimeout(Duration.ofSeconds(30 + 20L * cpuSlownessFactor())))
- .withStrategy(new HostPortWaitStrategy())
- .withStartupTimeout(Duration.ofSeconds(30 + 20L * cpuSlownessFactor())));
+ .waitingFor(FastAbortOnContainerDeathWaitStrategy.waitAll(s -> s
+ .withStartupTimeout(Duration.ofSeconds(30 + 20L * cpuSlownessFactor()))
+ .withStrategy(new LogMessageWaitAbortableStrategy().withRegEx(".*(Started Selenium Standalone).*\n"))
+ .withStrategy(new HostPortWaitAbortableStrategy())
+ ));
}
public static SeleniumRecordingContainer createDefaultRecordingContainer(
From 826463d5ec4e88996c920bc8c8a478ab680bb83a Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 14:11:36 +0200
Subject: [PATCH 113/141] [Selenium] Re-use existing constants
---
selenium/pom.xml | 2 +-
.../xdev/tci/selenium/factory/BrowserTCIFactory.java | 9 +++++++--
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/selenium/pom.xml b/selenium/pom.xml
index 137ba8ed..1119fe4a 100644
--- a/selenium/pom.xml
+++ b/selenium/pom.xml
@@ -82,7 +82,7 @@
software.xdev
testcontainers-selenium
- 2.0.1
+ 2.0.2
diff --git a/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java b/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java
index c98840e4..65235342 100644
--- a/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java
+++ b/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java
@@ -121,7 +121,8 @@ public static SeleniumBrowserWebDriverContainer createDefaultContainer(
// Some (AWS) CPUs are completely overloaded with the default 15s timeout -> increase it
.waitingFor(FastAbortOnContainerDeathWaitStrategy.waitAll(s -> s
.withStartupTimeout(Duration.ofSeconds(30 + 20L * cpuSlownessFactor()))
- .withStrategy(new LogMessageWaitAbortableStrategy().withRegEx(".*(Started Selenium Standalone).*\n"))
+ .withStrategy(new LogMessageWaitAbortableStrategy()
+ .withRegEx(BrowserWebDriverContainer.LOG_MSG_WAIT_STRATEGY_REGEX))
.withStrategy(new HostPortWaitAbortableStrategy())
));
}
@@ -132,7 +133,11 @@ public static SeleniumRecordingContainer createDefaultRecordingContainer(
{
return new SeleniumRecordingContainer(browserContainer)
.withLogConsumer(getLogConsumer("container.browserrecorder." + capabilities.getBrowserName()))
- .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M));
+ .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M))
+ .waitingFor(new FastAbortOnContainerDeathWaitStrategy(
+ new LogMessageWaitAbortableStrategy()
+ .withRegEx(SeleniumRecordingContainer.LOG_MSG_WAIT_STRATEGY_REGEX)
+ ));
}
public BrowserTCIFactory(
From fcc6bb8a7de05dcc708934602c5d57db6bc08897 Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 14:20:08 +0200
Subject: [PATCH 114/141] Update build to use Java 25
---
.github/workflows/check-build.yml | 6 +++---
.github/workflows/release.yml | 8 ++++----
.github/workflows/run-integration-tests.yml | 8 ++++----
.github/workflows/test-deploy.yml | 4 ++--
jul-to-slf4j/README.md | 2 +-
5 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml
index 3db742c4..127dc248 100644
--- a/.github/workflows/check-build.yml
+++ b/.github/workflows/check-build.yml
@@ -28,7 +28,7 @@ jobs:
timeout-minutes: 30
strategy:
matrix:
- java: [21, 25]
+ java: [25]
distribution: [temurin]
steps:
- uses: actions/checkout@v7
@@ -74,7 +74,7 @@ jobs:
timeout-minutes: 15
strategy:
matrix:
- java: [21]
+ java: [25]
distribution: [temurin]
steps:
- uses: actions/checkout@v7
@@ -110,7 +110,7 @@ jobs:
timeout-minutes: 15
strategy:
matrix:
- java: [21]
+ java: [25]
distribution: [temurin]
steps:
- uses: actions/checkout@v7
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9968f3d5..f1bf26d9 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -20,7 +20,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v5
with:
- java-version: '21'
+ java-version: '25'
distribution: 'temurin'
# Try to reuse existing cache from check-build
@@ -125,7 +125,7 @@ jobs:
uses: actions/setup-java@v5
with: # running setup-java overwrites the settings.xml
distribution: 'temurin'
- java-version: '21'
+ java-version: '25'
server-id: github-central
server-password: PACKAGES_CENTRAL_TOKEN
gpg-passphrase: MAVEN_GPG_PASSPHRASE
@@ -147,7 +147,7 @@ jobs:
uses: actions/setup-java@v5
with: # running setup-java again overwrites the settings.xml
distribution: 'temurin'
- java-version: '21'
+ java-version: '25'
server-id: sonatype-central-portal
server-username: MAVEN_CENTRAL_USERNAME
server-password: MAVEN_CENTRAL_TOKEN
@@ -182,7 +182,7 @@ jobs:
- name: Setup - Java
uses: actions/setup-java@v5
with:
- java-version: '21'
+ java-version: '25'
distribution: 'temurin'
# Try to reuse existing cache from check-build
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index fd0ea1ce..ec93a1cf 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -31,12 +31,12 @@ jobs:
project: [persistence-it-hibernate]
parallel: [0, 2]
pre-start: [false, true]
- java: [21]
+ java: [25]
include:
- project: persistence-it-eclipselink
parallel: 2
pre-start: true
- java: 21
+ java: 25
name: "persistence (${{matrix.project}}, ${{matrix.parallel}}, ${{matrix.pre-start}}, ${{matrix.java}})"
runs-on: ubuntu-latest
if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
@@ -102,14 +102,14 @@ jobs:
project: [webapp-it]
parallel: [0, 2]
pre-start: [false, true]
- java: [21]
+ java: [25]
jacoco: [false]
# Dedicated build with JaCoCo
include:
- project: webapp-it
parallel: 2
pre-start: true
- java: 21
+ java: 25
jacoco: true
name: "selenium (${{matrix.project}}, ${{matrix.parallel }}, ${{matrix.pre-start}}, ${{matrix.java}}${{ matrix.jacoco && ', JaCoCo' || '' }})"
runs-on: ubuntu-latest
diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml
index 2042eec9..570e1a33 100644
--- a/.github/workflows/test-deploy.yml
+++ b/.github/workflows/test-deploy.yml
@@ -14,7 +14,7 @@ jobs:
uses: actions/setup-java@v5
with: # running setup-java overwrites the settings.xml
distribution: 'temurin'
- java-version: '21'
+ java-version: '25'
server-id: github-central
server-password: PACKAGES_CENTRAL_TOKEN
gpg-passphrase: MAVEN_GPG_PASSPHRASE
@@ -36,7 +36,7 @@ jobs:
uses: actions/setup-java@v5
with: # running setup-java again overwrites the settings.xml
distribution: 'temurin'
- java-version: '21'
+ java-version: '25'
server-id: sonatype-central-portal
server-username: MAVEN_CENTRAL_USERNAME
server-password: MAVEN_CENTRAL_TOKEN
diff --git a/jul-to-slf4j/README.md b/jul-to-slf4j/README.md
index d6074722..3959daa0 100644
--- a/jul-to-slf4j/README.md
+++ b/jul-to-slf4j/README.md
@@ -1,3 +1,3 @@
# JUL to SLF4J
-Logging Adapter to redirect [JUL](https://docs.oracle.com/en/java/javase/21/docs/api/java.logging/java/util/logging/package-summary.html) to [SLF4J](https://github.com/qos-ch/slf4j)
+Logging Adapter to redirect [JUL](https://docs.oracle.com/en/java/javase/25/docs/api/java.logging/java/util/logging/package-summary.html) to [SLF4J](https://github.com/qos-ch/slf4j)
From 49751b3815445dc8c67e9da01d524b311b323c61 Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 14:35:41 +0200
Subject: [PATCH 115/141] Improve docs
---
base/README.md | 10 ++++++----
.../error/java/fatal/HsErrPidStartUpCrashReporter.java | 2 +-
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/base/README.md b/base/README.md
index 866eeab8..78ad3563 100644
--- a/base/README.md
+++ b/base/README.md
@@ -6,13 +6,15 @@ Base for other modules and core components.
| Feature | Why? | Demo |
| --- | --- | --- |
-| Easily create infrastructure using TCI[JD](https://javadoc.io/doc/software.xdev/base/latest/software/xdev/tci/TCI.html) (TestContainer Infrastructure) templating + Factories for that | Makes writing and designing tests easier | [here](../base-demo/src/test/java/software/xdev/tci/dummyinfra/) |
-| [PreStarting mechanism](./src/main/java/software/xdev/tci/factory/prestart/)[JD](https://javadoc.io/doc/software.xdev/base/latest/software/xdev/tci/factory/prestart/PreStartableTCIFactory.html) for [additional performance](../PERFORMANCE.md) | Tries to run tests as fast as possible - with a few trade-offs | [here](../base-demo/src/test/java/software/xdev/tci/factory/prestart/) |
+| Easily create infrastructure using TCI[JD](https://javadoc.io/doc/software.xdev.tci/base/latest/software/xdev/tci/TCI.html) (TestContainer Infrastructure) templating + Factories for that | Makes writing and designing tests easier | [here](../base-demo/src/test/java/software/xdev/tci/dummyinfra/) |
+| [PreStarting mechanism](./src/main/java/software/xdev/tci/factory/prestart/)[JD](https://javadoc.io/doc/software.xdev.tci/base/latest/software/xdev/tci/factory/prestart/PreStartableTCIFactory.html) for [additional performance](../PERFORMANCE.md) | Tries to run tests as fast as possible - with a few trade-offs | [here](../base-demo/src/test/java/software/xdev/tci/factory/prestart/) |
| All started containers have a unique human-readable name | Easier identification when tracing or debugging | [here](../base-demo/src/test/java/software/xdev/tci/safestart/) |
-| An optimized [implementation of Network](./src/main/java/software/xdev/tci/network/)[JD](https://javadoc.io/doc/software.xdev/base/latest/software/xdev/tci/network/LazyNetwork.html) | Addresses various problems of the original implementation to speed up tests | [here](../base-demo/src/test/java/software/xdev/tci/network/) |
-| [Safe starting of named containers](./src/main/java/software/xdev/tci/safestart/)[JD](https://javadoc.io/doc/software.xdev/base/latest/software/xdev/tci/safestart/SafeNamedContainerStarter.html) | Ensures that a container doesn't enter a crash loop during retried startups | [here](../base-demo/src/test/java/software/xdev/tci/safestart/) |
+| An optimized [implementation of Network](./src/main/java/software/xdev/tci/network/)[JD](https://javadoc.io/doc/software.xdev.tci/base/latest/software/xdev/tci/network/LazyNetwork.html) | Addresses various problems of the original implementation to speed up tests | [here](../base-demo/src/test/java/software/xdev/tci/network/) |
+| [Safe starting of named containers](./src/main/java/software/xdev/tci/safestart/)[JD](https://javadoc.io/doc/software.xdev.tci/base/latest/software/xdev/tci/safestart/SafeNamedContainerStarter.html) | Ensures that a container doesn't enter a crash loop during retried startups | [here](../base-demo/src/test/java/software/xdev/tci/safestart/) |
| [Container Leak detection](./src/main/java/software/xdev/tci/leakdetection/)¹ | Prevents you from running out of resources | [here](../base-demo/src/test/java/software/xdev/tci/leak/) |
| [Tracing](./src/main/java/software/xdev/tci/tracing/)¹ | Makes finding bottlenecks and similar problems easier | |
+| [Reporting of fatal (Java) crashes during container startup](./src/main/java/software/xdev/tci/startup/error/java/fatal/) | Easier error diagnosis | [here](../advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java) |
+| [Fail-fast/Abortable wait strategies](./src/main/java/software/xdev/tci/startup/wait/) | Fail fast when a container crashes - don't wait until the timeout | [here](../advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java) |
¹ = Active by default due to service loading
diff --git a/base/src/main/java/software/xdev/tci/startup/error/java/fatal/HsErrPidStartUpCrashReporter.java b/base/src/main/java/software/xdev/tci/startup/error/java/fatal/HsErrPidStartUpCrashReporter.java
index d48d96e3..89fca3c7 100644
--- a/base/src/main/java/software/xdev/tci/startup/error/java/fatal/HsErrPidStartUpCrashReporter.java
+++ b/base/src/main/java/software/xdev/tci/startup/error/java/fatal/HsErrPidStartUpCrashReporter.java
@@ -34,7 +34,7 @@
/**
* Utility to log contents of java hs_err_pid files of a container that failed to start.
*
- * Additionally also copies the file into target/
+ * Additionally also copies the file into target/has_err_pid/<containerId>/<hs_err_pid-file>
*
*/
public class HsErrPidStartUpCrashReporter
From 39172ca2e081b009c65145b568b6a03fdfcb881f Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 14:38:03 +0200
Subject: [PATCH 116/141] Use constant instead of hardcoded string
---
mockserver/pom.xml | 2 +-
.../xdev/tci/mockserver/containers/TCIMockserverContainer.java | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/mockserver/pom.xml b/mockserver/pom.xml
index 2eec4022..c63e30e7 100644
--- a/mockserver/pom.xml
+++ b/mockserver/pom.xml
@@ -54,7 +54,7 @@
software.xdev.mockserver
bom
- 2.51.0
+ 2.51.1
pom
import
diff --git a/mockserver/src/main/java/software/xdev/tci/mockserver/containers/TCIMockserverContainer.java b/mockserver/src/main/java/software/xdev/tci/mockserver/containers/TCIMockserverContainer.java
index 50963957..44f0eaef 100644
--- a/mockserver/src/main/java/software/xdev/tci/mockserver/containers/TCIMockserverContainer.java
+++ b/mockserver/src/main/java/software/xdev/tci/mockserver/containers/TCIMockserverContainer.java
@@ -75,7 +75,7 @@ public static TCIMockserverContainer createDefaultForFactory()
container
.withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M))
.waitingFor(new FastAbortOnContainerDeathWaitStrategy(new LogMessageWaitAbortableStrategy()
- .withRegEx(".*started on port: " + PORT + ".*")
+ .withRegEx(MockServerContainer.LOG_MSG_WAIT_STRATEGY_REGEX)
));
return container;
}
From 4334d355b133510bcf8d7cdaa0bc66ea5f28850e Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 15:02:29 +0200
Subject: [PATCH 117/141] Code cleanup
---
.../HostPortWaitAbortableStrategy.java | 74 +++++++++++--------
1 file changed, 43 insertions(+), 31 deletions(-)
diff --git a/base/src/main/java/software/xdev/tci/startup/wait/strategy/HostPortWaitAbortableStrategy.java b/base/src/main/java/software/xdev/tci/startup/wait/strategy/HostPortWaitAbortableStrategy.java
index 8a048f64..49b79e61 100644
--- a/base/src/main/java/software/xdev/tci/startup/wait/strategy/HostPortWaitAbortableStrategy.java
+++ b/base/src/main/java/software/xdev/tci/startup/wait/strategy/HostPortWaitAbortableStrategy.java
@@ -49,44 +49,27 @@ public class HostPortWaitAbortableStrategy extends AbstractWaitAbortableStrategy
@Override
protected void waitUntilReady(final AbortMonitor abortMonitor)
{
- final Set externalLivenessCheckPorts;
- if(this.ports == null || this.ports.length == 0)
+ final Set externalLivenessCheckPorts = this.determineExternalLivenessCheckPorts();
+ if(externalLivenessCheckPorts == null || externalLivenessCheckPorts.isEmpty())
{
- externalLivenessCheckPorts = this.getLivenessCheckPorts();
- if(externalLivenessCheckPorts.isEmpty())
+ if(LOG.isDebugEnabled())
{
- if(LOG.isDebugEnabled())
- {
- LOG.debug(
- "Liveness check ports of {} is empty. Not waiting.",
- this.waitStrategyTarget.getContainerInfo().getName()
- );
- }
- return;
+ LOG.debug(
+ "Liveness check ports of {} is empty. Not waiting.",
+ this.waitStrategyTarget.getContainerInfo().getName()
+ );
}
- }
- else
- {
- externalLivenessCheckPorts =
- Arrays
- .stream(this.ports)
- .mapToObj(port -> this.waitStrategyTarget.getMappedPort(port))
- .collect(Collectors.toSet());
+ return;
}
abortMonitor.throwIfRequired();
- final List exposedPorts = this.waitStrategyTarget.getExposedPorts();
-
- final Set internalPorts = this.getInternalPorts(externalLivenessCheckPorts, exposedPorts);
+ final Set internalPorts = this.getInternalPorts(
+ externalLivenessCheckPorts,
+ this.waitStrategyTarget.getExposedPorts());
- final Callable internalCheck =
- new InternalCommandPortListeningCheck(this.waitStrategyTarget, internalPorts);
-
- final Callable externalCheck = new ExternalPortListeningCheck(
- this.waitStrategyTarget,
- externalLivenessCheckPorts
- );
+ final Callable internalCheck = this.createInternalCheck(internalPorts);
+ final Callable externalCheck = this.createExternalCheck(externalLivenessCheckPorts);
try
{
@@ -154,13 +137,42 @@ protected void waitUntilReady(final AbortMonitor abortMonitor)
}
}
+ private ExternalPortListeningCheck createExternalCheck(final Set externalLivenessCheckPorts)
+ {
+ return new ExternalPortListeningCheck(
+ this.waitStrategyTarget,
+ externalLivenessCheckPorts
+ );
+ }
+
+ private InternalCommandPortListeningCheck createInternalCheck(final Set internalPorts)
+ {
+ return new InternalCommandPortListeningCheck(this.waitStrategyTarget, internalPorts);
+ }
+
+ /**
+ * @apiNote Can return null
+ */
+ protected Set determineExternalLivenessCheckPorts()
+ {
+ if(this.ports == null || this.ports.length == 0)
+ {
+ return this.getLivenessCheckPorts();
+ }
+
+ return Arrays
+ .stream(this.ports)
+ .mapToObj(this.waitStrategyTarget::getMappedPort)
+ .collect(Collectors.toSet());
+ }
+
protected Set getInternalPorts(
final Set externalLivenessCheckPorts,
final List exposedPorts)
{
return exposedPorts
.stream()
- .filter(it -> externalLivenessCheckPorts.contains(this.waitStrategyTarget.getMappedPort(it)))
+ .filter(port -> externalLivenessCheckPorts.contains(this.waitStrategyTarget.getMappedPort(port)))
.collect(Collectors.toSet());
}
From 0247e60e06dbb231796db2ffafa2262ae21d661a Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 15:16:14 +0200
Subject: [PATCH 118/141] Differ cache based on available AVX level
See #688
---
.github/workflows/run-integration-tests.yml | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index ec93a1cf..edf8af06 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -151,12 +151,18 @@ jobs:
- name: Test
run: |
+ // Report CPU
+ cat /proc/cpuinfo | grep 'name' | uniq
+
+ // Differentiate cache between AVX microcode used by Java (until https://github.com/xdev-software/tci/issues/688 is fixed)
+ java_useavx=$(java -XX:+PrintFlagsFinal -version 2> /dev/null | awk '/UseAVX/ {print $4}')
+
./mvnw -B test \
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
-P run-it${{ matrix.jacoco && ',jacoco' || '' }} \
-Dtci.image-build.delete-on-exit=true \
- -Dtci.image-build.cache-from="type=gha,scope=§image${{ matrix.jacoco && '-jacoco' || '' }}" \
- -Dtci.image-build.cache-to="type=gha,mode=max,scope=§image${{ matrix.jacoco && '-jacoco' || '' }}" \
+ -Dtci.image-build.cache-from="type=gha,scope=§image-j_avx${java_useavx}${{ matrix.jacoco && '-jacoco' || '' }}" \
+ -Dtci.image-build.cache-to="type=gha,mode=max,scope=§image-j_avx${java_useavx}${{ matrix.jacoco && '-jacoco' || '' }}" \
${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
env:
From 578dab83c8a01a9cebc0f6f10f7824e0dcfc683f Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 16:40:05 +0200
Subject: [PATCH 119/141] Make it possible to configure image build based on
name of built image
This is helpful when certain image builds require custom settings.
Example: The dummy webapp requires custom images based on presence of AVX and jacoco
---
.github/workflows/run-integration-tests.yml | 6 +-
image-build/README.md | 12 +-
.../xdev/tci/imagebuild/BuildImage.java | 4 +-
...er.java => BuildImageHandlerProvider.java} | 2 +-
.../imagebuild/DefaultBuildImageHandler.java | 179 ------------------
.../DefaultBuildImageHandlerProvider.java | 67 +++++++
.../AbstractBuildImageHandlerConfig.java | 39 ++++
.../DefaultBuildImageHandlerConfig.java | 25 +--
.../OverlayBuildImageHandlerConfig.java | 77 ++++++++
.../handler/AbstractBuildImageHandler.java | 74 ++++++++
.../handler/AdvancedBuildImageHandler.java | 29 +++
.../imagebuild/handler/BuildImageHandler.java | 16 ++
.../NativeAdvancedBuildImageHandler.java | 107 +++++++++++
...ware.xdev.tci.imagebuild.BuildImageHandler | 1 -
...v.tci.imagebuild.BuildImageHandlerProvider | 1 +
15 files changed, 433 insertions(+), 206 deletions(-)
rename image-build/src/main/java/software/xdev/tci/imagebuild/{BuildImageHandler.java => BuildImageHandlerProvider.java} (96%)
delete mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandler.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandlerProvider.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/config/AbstractBuildImageHandlerConfig.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/config/OverlayBuildImageHandlerConfig.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/handler/AbstractBuildImageHandler.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/handler/AdvancedBuildImageHandler.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/handler/BuildImageHandler.java
create mode 100644 image-build/src/main/java/software/xdev/tci/imagebuild/handler/NativeAdvancedBuildImageHandler.java
delete mode 100644 image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.BuildImageHandler
create mode 100644 image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.BuildImageHandlerProvider
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index edf8af06..ca70a02d 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -161,8 +161,10 @@ jobs:
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
-P run-it${{ matrix.jacoco && ',jacoco' || '' }} \
-Dtci.image-build.delete-on-exit=true \
- -Dtci.image-build.cache-from="type=gha,scope=§image-j_avx${java_useavx}${{ matrix.jacoco && '-jacoco' || '' }}" \
- -Dtci.image-build.cache-to="type=gha,mode=max,scope=§image-j_avx${java_useavx}${{ matrix.jacoco && '-jacoco' || '' }}" \
+ -Dtci.image-build.cache-from="type=gha,scope=§image" \
+ -Dtci.image-build.cache-to="type=gha,mode=max,scope=§image" \
+ -Dtci.image-build.tci-demo-webapp.cache-from="type=gha,scope=§image-j_avx${java_useavx}${{ matrix.jacoco && '-jacoco' || '' }}" \
+ -Dtci.image-build.tci-demo-webapp.cache-to="type=gha,mode=max,scope=§image-j_avx${java_useavx}${{ matrix.jacoco && '-jacoco' || '' }}" \
${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
env:
diff --git a/image-build/README.md b/image-build/README.md
index 12c4a9c2..ba834dd3 100644
--- a/image-build/README.md
+++ b/image-build/README.md
@@ -6,11 +6,17 @@ Provides common utility and configuration to build images.
The configuration is dynamically loaded from (sorted by highest priority)
-* Environment variables
- * prefixed with ``TCI_IMAGE-BUILD_``
+* Environment variables
+ * prefixed with `TCI_IMAGE-BUILD__`
+ * where `imageName` is the (sanitized) name of the image to build
+ * prefixed with `TCI_IMAGE-BUILD_`
* all properties are in UPPERCASE and use `_` instead of `.` or `-`
* System properties
- * prefixed with ``tci.image-build.``
+ * prefixed with `tci.image-build..`
+ * where `imageName` is the (sanitized) name of the image to build
+ * prefixed with `tci.image-build.`
+
+_NOTE: Sanitized image names only include alphanumeric characters, `_` or `-`. All other characters are replaced by `_`_
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/BuildImage.java b/image-build/src/main/java/software/xdev/tci/imagebuild/BuildImage.java
index eb385204..aa2c0017 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/BuildImage.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/BuildImage.java
@@ -41,9 +41,9 @@ public static String image(
return impl().image(dockerImage, timeout, configure);
}
- public static BuildImageHandler impl()
+ public static BuildImageHandlerProvider impl()
{
- return TCIServiceLoaderHolder.instance().service(BuildImageHandler.class);
+ return TCIServiceLoaderHolder.instance().service(BuildImageHandlerProvider.class);
}
private BuildImage()
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/BuildImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/BuildImageHandlerProvider.java
similarity index 96%
rename from image-build/src/main/java/software/xdev/tci/imagebuild/BuildImageHandler.java
rename to image-build/src/main/java/software/xdev/tci/imagebuild/BuildImageHandlerProvider.java
index cf08dc96..ed57af9e 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/BuildImageHandler.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/BuildImageHandlerProvider.java
@@ -22,7 +22,7 @@
import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
-public interface BuildImageHandler
+public interface BuildImageHandlerProvider
{
String nativeImage(
String dockerImage,
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandler.java
deleted file mode 100644
index 0a1360af..00000000
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandler.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright © 2025 XDEV Software (https://xdev.software)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package software.xdev.tci.imagebuild;
-
-import java.time.Duration;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.UnaryOperator;
-import java.util.regex.Pattern;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import software.xdev.tci.concurrent.TCIExecutorServiceHolder;
-import software.xdev.tci.imagebuild.cache.async.TCICacheSaveRegistry;
-import software.xdev.tci.imagebuild.config.BuildImageHandlerConfig;
-import software.xdev.testcontainers.imagebuilder.AbstractImageFromDockerfile;
-import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
-import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
-
-
-public class DefaultBuildImageHandler implements BuildImageHandler
-{
- private static final Logger LOG = LoggerFactory.getLogger(DefaultBuildImageHandler.class);
-
- protected static final Pattern DOCKER_IMAGE_SANITIZATION_PATTERN =
- Pattern.compile("[^A-Za-z0-9-_]");
-
- protected final BuildImageHandlerConfig config;
-
- public DefaultBuildImageHandler()
- {
- this(BuildImageHandlerConfig.instance());
- }
-
- public DefaultBuildImageHandler(final BuildImageHandlerConfig config)
- {
- this.config = config;
- }
-
- @Override
- public String nativeImage(
- final String dockerImage,
- final Duration timeout,
- final UnaryOperator configure)
- {
- LOG.info("Starting build of (native) image {}", dockerImage);
-
- final String sanitizeDockerImageName = this.sanitizeDockerImageName(dockerImage);
- final NativeAdvancedImageFromDockerfile builder =
- new NativeAdvancedImageFromDockerfile(dockerImage, this.config.deleteOnExit());
-
- this.configureAbstract(builder, sanitizeDockerImageName);
-
- this.templateCacheConfigValue(this.config.cacheFrom(), sanitizeDockerImageName)
- .ifPresent(builder::withCacheFrom);
-
- if(this.config.saveCacheInBackground())
- {
- builder.withCreateTransferFilesCache(true);
- }
- else
- {
- this.configureCacheTo(sanitizeDockerImageName, builder);
- }
-
- final NativeAdvancedImageFromDockerfile customizedBuilder = configure.apply(builder);
-
- final String builtImageName = this.buildImage(customizedBuilder, timeout);
-
- if(this.config.saveCacheInBackground() && this.config.cacheTo().isPresent())
- {
- LOG.info("Rebuilding image {} in background to save cache", builtImageName);
- TCICacheSaveRegistry.instance().add(
- CompletableFuture.runAsync(
- () -> {
- try
- {
- this.buildImage(
- this.configureCacheTo(
- sanitizeDockerImageName,
- customizedBuilder.copyForExactRebuild(dockerImage + "-cache")
- // Don't load it into docker because it's not needed there
- .withLoad(false)),
- timeout);
- }
- catch(final Exception ex)
- {
- LOG.warn("Rebuilding {} to save cache failed", builtImageName, ex);
- }
- finally
- {
- try
- {
- builder.cleanCreatedTransferFilesCache();
- }
- catch(final Exception ex)
- {
- LOG.warn("Failed to clean created transfer file cache for {}", builtImageName, ex);
- }
- }
- },
- TCIExecutorServiceHolder.instance()));
- }
-
- return builtImageName;
- }
-
- protected NativeAdvancedImageFromDockerfile configureCacheTo(
- final String sanitizeDockerImageName,
- final NativeAdvancedImageFromDockerfile builder)
- {
- this.templateCacheConfigValue(this.config.cacheTo(), sanitizeDockerImageName)
- .ifPresent(builder::withCacheTo);
- return builder;
- }
-
- private Optional templateCacheConfigValue(
- final Optional configValue,
- final String sanitizeDockerImageName)
- {
- return configValue
- .map(s -> s.replace("$image", sanitizeDockerImageName))
- .map(s -> s.replace("§image", sanitizeDockerImageName));
- }
-
- @Override
- public String image(
- final String dockerImage,
- final Duration timeout,
- final UnaryOperator configure)
- {
- LOG.info("Starting build of image {}", dockerImage);
-
- final String sanitizeDockerImageName = this.sanitizeDockerImageName(dockerImage);
- final AdvancedImageFromDockerFile builder =
- new AdvancedImageFromDockerFile(dockerImage, this.config.deleteOnExit());
-
- this.configureAbstract(builder, sanitizeDockerImageName);
-
- return this.buildImage(configure.apply(builder), timeout);
- }
-
- protected String buildImage(
- final AbstractImageFromDockerfile> builder,
- final Duration timeout)
- {
- LOG.info("Building image {}...", builder.getDockerImageName());
- final String builtImageName = builder.build(timeout);
- LOG.info("Built image {}", builtImageName);
-
- return builtImageName;
- }
-
- protected > void configureAbstract(
- final I builder, final String sanitizeDockerImageName)
- {
- builder.withLoggerForBuild(
- LoggerFactory.getLogger(this.config.loggerForBuildPrefix() + sanitizeDockerImageName));
- }
-
- protected String sanitizeDockerImageName(final String dockerImage)
- {
- return DOCKER_IMAGE_SANITIZATION_PATTERN.matcher(dockerImage).replaceAll("_");
- }
-}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandlerProvider.java b/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandlerProvider.java
new file mode 100644
index 00000000..a2aa4df0
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/DefaultBuildImageHandlerProvider.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.imagebuild;
+
+import java.time.Duration;
+import java.util.function.UnaryOperator;
+
+import software.xdev.tci.imagebuild.config.BuildImageHandlerConfig;
+import software.xdev.tci.imagebuild.handler.AdvancedBuildImageHandler;
+import software.xdev.tci.imagebuild.handler.NativeAdvancedBuildImageHandler;
+import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
+import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
+
+
+public class DefaultBuildImageHandlerProvider implements BuildImageHandlerProvider
+{
+ protected final BuildImageHandlerConfig config;
+
+ public DefaultBuildImageHandlerProvider()
+ {
+ this(BuildImageHandlerConfig.instance());
+ }
+
+ public DefaultBuildImageHandlerProvider(final BuildImageHandlerConfig config)
+ {
+ this.config = config;
+ }
+
+ @Override
+ public String nativeImage(
+ final String dockerImage,
+ final Duration timeout,
+ final UnaryOperator configure)
+ {
+ return new NativeAdvancedBuildImageHandler().build(
+ dockerImage,
+ this.config,
+ timeout,
+ configure);
+ }
+
+ @Override
+ public String image(
+ final String dockerImage,
+ final Duration timeout,
+ final UnaryOperator configure)
+ {
+ return new AdvancedBuildImageHandler().build(
+ dockerImage,
+ this.config,
+ timeout,
+ configure);
+ }
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/config/AbstractBuildImageHandlerConfig.java b/image-build/src/main/java/software/xdev/tci/imagebuild/config/AbstractBuildImageHandlerConfig.java
new file mode 100644
index 00000000..1f7ef873
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/config/AbstractBuildImageHandlerConfig.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.imagebuild.config;
+
+import java.util.Optional;
+
+import software.xdev.tci.config.DefaultConfig;
+
+
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+public abstract class AbstractBuildImageHandlerConfig extends DefaultConfig implements BuildImageHandlerConfig
+{
+ protected static final String DELETE_ON_EXIT = "delete-on-exit";
+ protected static final String LOGGER_FOR_BUILD_PREFIX = "logger-for-build-prefix";
+ protected static final String CACHE_FROM = "cache-from";
+ protected static final String CACHE_TO = "cache-to";
+ protected static final String SAVE_CACHE_IN_BACKGROUND = "save-cache-in-background";
+ protected static final String WAIT_FOR_SAVE_CACHE_IN_BACKGROUND = "wait-for-" + SAVE_CACHE_IN_BACKGROUND;
+
+ protected Boolean deleteOnExit;
+ protected String loggerForBuildPrefix;
+ protected Optional cacheFrom;
+ protected Optional cacheTo;
+ protected Boolean saveCacheInBackground;
+ protected Boolean waitForSaveCacheInBackground;
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultBuildImageHandlerConfig.java b/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultBuildImageHandlerConfig.java
index 5d43b7ea..5cdf6bc9 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultBuildImageHandlerConfig.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/config/DefaultBuildImageHandlerConfig.java
@@ -17,21 +17,10 @@
import java.util.Optional;
-import software.xdev.tci.config.DefaultConfig;
-
-@SuppressWarnings({"java:S2789", "OptionalAssignedToNull", "OptionalUsedAsFieldOrParameterType"})
-public class DefaultBuildImageHandlerConfig extends DefaultConfig implements BuildImageHandlerConfig
+@SuppressWarnings({"java:S2789", "OptionalAssignedToNull"})
+public class DefaultBuildImageHandlerConfig extends AbstractBuildImageHandlerConfig
{
- protected static final String SAVE_CACHE_IN_BACKGROUND = "save-cache-in-background";
-
- protected Boolean deleteOnExit;
- protected String loggerForBuildPrefix;
- protected Optional cacheFrom;
- protected Optional cacheTo;
- protected Boolean saveCacheInBackground;
- protected Boolean waitForSaveCacheInBackground;
-
@Override
protected String propertyNamePrefix()
{
@@ -43,7 +32,7 @@ public boolean deleteOnExit()
{
if(this.deleteOnExit == null)
{
- this.deleteOnExit = this.resolveBool("delete-on-exit", false);
+ this.deleteOnExit = this.resolveBool(DELETE_ON_EXIT, false);
}
return this.deleteOnExit;
}
@@ -53,7 +42,7 @@ public String loggerForBuildPrefix()
{
if(this.loggerForBuildPrefix == null)
{
- this.loggerForBuildPrefix = this.resolve("logger-for-build-prefix")
+ this.loggerForBuildPrefix = this.resolve(LOGGER_FOR_BUILD_PREFIX)
.orElse("container.build.");
}
return this.loggerForBuildPrefix;
@@ -64,7 +53,7 @@ public Optional cacheFrom()
{
if(this.cacheFrom == null)
{
- this.cacheFrom = this.resolve("cache-from");
+ this.cacheFrom = this.resolve(CACHE_FROM);
}
return this.cacheFrom;
}
@@ -74,7 +63,7 @@ public Optional cacheTo()
{
if(this.cacheTo == null)
{
- this.cacheTo = this.resolve("cache-to");
+ this.cacheTo = this.resolve(CACHE_TO);
}
return this.cacheTo;
}
@@ -96,7 +85,7 @@ public boolean waitForSaveCacheInBackground()
if(this.waitForSaveCacheInBackground == null)
{
this.waitForSaveCacheInBackground =
- this.resolveBool("wait-for-" + SAVE_CACHE_IN_BACKGROUND, true);
+ this.resolveBool(WAIT_FOR_SAVE_CACHE_IN_BACKGROUND, true);
}
return this.waitForSaveCacheInBackground;
}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/config/OverlayBuildImageHandlerConfig.java b/image-build/src/main/java/software/xdev/tci/imagebuild/config/OverlayBuildImageHandlerConfig.java
new file mode 100644
index 00000000..c300456c
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/config/OverlayBuildImageHandlerConfig.java
@@ -0,0 +1,77 @@
+package software.xdev.tci.imagebuild.config;
+
+import java.util.Optional;
+
+
+@SuppressWarnings({"java:S2789", "OptionalAssignedToNull"})
+public class OverlayBuildImageHandlerConfig extends DefaultBuildImageHandlerConfig
+{
+ private final BuildImageHandlerConfig parentConfig;
+ private final String prefixName;
+
+ public OverlayBuildImageHandlerConfig(final BuildImageHandlerConfig parentConfig, final String prefixName)
+ {
+ this.parentConfig = parentConfig;
+ this.prefixName = prefixName;
+ }
+
+ @Override
+ protected String propertyNamePrefix()
+ {
+ return super.propertyNamePrefix() + "." + this.prefixName;
+ }
+
+ @Override
+ public boolean deleteOnExit()
+ {
+ if(this.deleteOnExit == null)
+ {
+ this.deleteOnExit = this.resolveBool(DELETE_ON_EXIT, this.parentConfig.deleteOnExit());
+ }
+ return this.deleteOnExit;
+ }
+
+ @Override
+ public String loggerForBuildPrefix()
+ {
+ if(this.loggerForBuildPrefix == null)
+ {
+ this.loggerForBuildPrefix = this.resolve(LOGGER_FOR_BUILD_PREFIX)
+ .orElseGet(this.parentConfig::loggerForBuildPrefix);
+ }
+ return this.loggerForBuildPrefix;
+ }
+
+ @Override
+ public Optional cacheFrom()
+ {
+ if(this.cacheFrom == null)
+ {
+ this.cacheFrom = this.resolve(CACHE_FROM)
+ .or(this.parentConfig::cacheFrom);
+ }
+ return this.cacheFrom;
+ }
+
+ @Override
+ public Optional cacheTo()
+ {
+ if(this.cacheTo == null)
+ {
+ this.cacheTo = this.resolve(CACHE_TO)
+ .or(this.parentConfig::cacheTo);
+ }
+ return this.cacheTo;
+ }
+
+ @Override
+ public boolean waitForSaveCacheInBackground()
+ {
+ if(this.waitForSaveCacheInBackground == null)
+ {
+ this.waitForSaveCacheInBackground =
+ this.resolveBool(WAIT_FOR_SAVE_CACHE_IN_BACKGROUND, this.parentConfig.waitForSaveCacheInBackground());
+ }
+ return this.waitForSaveCacheInBackground;
+ }
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AbstractBuildImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AbstractBuildImageHandler.java
new file mode 100644
index 00000000..2e29fb26
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AbstractBuildImageHandler.java
@@ -0,0 +1,74 @@
+package software.xdev.tci.imagebuild.handler;
+
+import java.time.Duration;
+import java.util.function.UnaryOperator;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import software.xdev.tci.imagebuild.config.BuildImageHandlerConfig;
+import software.xdev.tci.imagebuild.config.OverlayBuildImageHandlerConfig;
+import software.xdev.testcontainers.imagebuilder.AbstractImageFromDockerfile;
+
+
+public abstract class AbstractBuildImageHandler>
+ implements BuildImageHandler
+{
+ protected static final Pattern DOCKER_IMAGE_SANITIZATION_PATTERN = Pattern.compile("[^A-Za-z0-9-_]");
+
+ protected Logger logger;
+
+ protected AbstractBuildImageHandler()
+ {
+ this.logger = LoggerFactory.getLogger(this.getClass());
+ }
+
+ @Override
+ public String build(
+ final String dockerImage,
+ final BuildImageHandlerConfig parentConfig,
+ final Duration timeout,
+ final UnaryOperator configure)
+ {
+ final String sanitizedDockerImageName = this.sanitizeDockerImageName(dockerImage);
+ return this.build(
+ dockerImage,
+ sanitizedDockerImageName,
+ new OverlayBuildImageHandlerConfig(parentConfig, sanitizedDockerImageName),
+ timeout,
+ configure);
+ }
+
+ protected abstract String build(
+ final String dockerImage,
+ final String sanitizedDockerImageName,
+ final BuildImageHandlerConfig parentConfig,
+ final Duration timeout,
+ final UnaryOperator configure);
+
+ protected void configureAbstract(
+ final I builder,
+ final BuildImageHandlerConfig config,
+ final String sanitizeDockerImageName)
+ {
+ builder.withLoggerForBuild(
+ LoggerFactory.getLogger(config.loggerForBuildPrefix() + sanitizeDockerImageName));
+ }
+
+ protected String buildImage(
+ final AbstractImageFromDockerfile> builder,
+ final Duration timeout)
+ {
+ this.logger.info("Building image {}...", builder.getDockerImageName());
+ final String builtImageName = builder.build(timeout);
+ this.logger.info("Built image {}", builtImageName);
+
+ return builtImageName;
+ }
+
+ protected String sanitizeDockerImageName(final String dockerImage)
+ {
+ return DOCKER_IMAGE_SANITIZATION_PATTERN.matcher(dockerImage).replaceAll("_");
+ }
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AdvancedBuildImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AdvancedBuildImageHandler.java
new file mode 100644
index 00000000..2c2d92c7
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AdvancedBuildImageHandler.java
@@ -0,0 +1,29 @@
+package software.xdev.tci.imagebuild.handler;
+
+import java.time.Duration;
+import java.util.function.UnaryOperator;
+
+import software.xdev.tci.imagebuild.config.BuildImageHandlerConfig;
+import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile;
+
+
+public class AdvancedBuildImageHandler extends AbstractBuildImageHandler
+{
+ @Override
+ protected String build(
+ final String dockerImage,
+ final String sanitizedDockerImageName,
+ final BuildImageHandlerConfig config,
+ final Duration timeout,
+ final UnaryOperator configure)
+ {
+ this.logger.info("Starting build of image {}", dockerImage);
+
+ final AdvancedImageFromDockerFile builder =
+ new AdvancedImageFromDockerFile(dockerImage, config.deleteOnExit());
+
+ this.configureAbstract(builder, config, sanitizedDockerImageName);
+
+ return this.buildImage(configure.apply(builder), timeout);
+ }
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/handler/BuildImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/BuildImageHandler.java
new file mode 100644
index 00000000..c8bf53f1
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/BuildImageHandler.java
@@ -0,0 +1,16 @@
+package software.xdev.tci.imagebuild.handler;
+
+import java.time.Duration;
+import java.util.function.UnaryOperator;
+
+import software.xdev.tci.imagebuild.config.BuildImageHandlerConfig;
+
+
+public interface BuildImageHandler
+{
+ String build(
+ String dockerImage,
+ BuildImageHandlerConfig parentConfig,
+ Duration timeout,
+ UnaryOperator configure);
+}
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/handler/NativeAdvancedBuildImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/NativeAdvancedBuildImageHandler.java
new file mode 100644
index 00000000..6a33a3c4
--- /dev/null
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/NativeAdvancedBuildImageHandler.java
@@ -0,0 +1,107 @@
+package software.xdev.tci.imagebuild.handler;
+
+import java.time.Duration;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.UnaryOperator;
+
+import software.xdev.tci.concurrent.TCIExecutorServiceHolder;
+import software.xdev.tci.imagebuild.cache.async.TCICacheSaveRegistry;
+import software.xdev.tci.imagebuild.config.BuildImageHandlerConfig;
+import software.xdev.testcontainers.imagebuilder.buildxnative.NativeAdvancedImageFromDockerfile;
+
+
+public class NativeAdvancedBuildImageHandler extends AbstractBuildImageHandler
+{
+ @Override
+ protected String build(
+ final String dockerImage,
+ final String sanitizedDockerImageName,
+ final BuildImageHandlerConfig config,
+ final Duration timeout,
+ final UnaryOperator configure)
+ {
+ this.logger.info("Starting build of (native) image {}", dockerImage);
+
+ final NativeAdvancedImageFromDockerfile builder =
+ new NativeAdvancedImageFromDockerfile(dockerImage, config.deleteOnExit());
+
+ this.configureAbstract(builder, config, sanitizedDockerImageName);
+
+ this.templateCacheConfigValue(config.cacheFrom(), sanitizedDockerImageName)
+ .ifPresent(builder::withCacheFrom);
+
+ if(config.saveCacheInBackground())
+ {
+ builder.withCreateTransferFilesCache(true);
+ }
+ else
+ {
+ this.configureCacheTo(sanitizedDockerImageName, config, builder);
+ }
+
+ final NativeAdvancedImageFromDockerfile customizedBuilder = configure.apply(builder);
+
+ final String builtImageName = this.buildImage(customizedBuilder, timeout);
+
+ if(config.saveCacheInBackground() && config.cacheTo().isPresent())
+ {
+ this.logger.info("Rebuilding image {} in background to save cache", builtImageName);
+ TCICacheSaveRegistry.instance().add(
+ CompletableFuture.runAsync(
+ () -> {
+ try
+ {
+ this.buildImage(
+ this.configureCacheTo(
+ sanitizedDockerImageName,
+ config,
+ customizedBuilder.copyForExactRebuild(dockerImage + "-cache")
+ // Don't load it into docker because it's not needed there
+ .withLoad(false)),
+ timeout);
+ }
+ catch(final Exception ex)
+ {
+ this.logger.warn("Rebuilding {} to save cache failed", builtImageName, ex);
+ }
+ finally
+ {
+ try
+ {
+ builder.cleanCreatedTransferFilesCache();
+ }
+ catch(final Exception ex)
+ {
+ this.logger.warn(
+ "Failed to clean created transfer file cache for {}",
+ builtImageName,
+ ex);
+ }
+ }
+ },
+ TCIExecutorServiceHolder.instance()));
+ }
+
+ return builtImageName;
+ }
+
+ protected NativeAdvancedImageFromDockerfile configureCacheTo(
+ final String sanitizeDockerImageName,
+ final BuildImageHandlerConfig config,
+ final NativeAdvancedImageFromDockerfile builder)
+ {
+ this.templateCacheConfigValue(config.cacheTo(), sanitizeDockerImageName)
+ .ifPresent(builder::withCacheTo);
+ return builder;
+ }
+
+ protected Optional templateCacheConfigValue(
+ final Optional configValue,
+ final String sanitizeDockerImageName)
+ {
+ return configValue
+ .map(s -> s.replace("$image", sanitizeDockerImageName))
+ .map(s -> s.replace("§image", sanitizeDockerImageName));
+ }
+}
diff --git a/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.BuildImageHandler b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.BuildImageHandler
deleted file mode 100644
index 0741e473..00000000
--- a/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.BuildImageHandler
+++ /dev/null
@@ -1 +0,0 @@
-software.xdev.tci.imagebuild.DefaultBuildImageHandler
diff --git a/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.BuildImageHandlerProvider b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.BuildImageHandlerProvider
new file mode 100644
index 00000000..bae2b157
--- /dev/null
+++ b/image-build/src/main/resources/META-INF/services/software.xdev.tci.imagebuild.BuildImageHandlerProvider
@@ -0,0 +1 @@
+software.xdev.tci.imagebuild.DefaultBuildImageHandlerProvider
From e7ba0db941a61b50ac8a74b877f008b36306bdac Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 16:40:59 +0200
Subject: [PATCH 120/141] Java comments != Shell comments
---
.github/workflows/run-integration-tests.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index ca70a02d..228097ad 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -151,10 +151,10 @@ jobs:
- name: Test
run: |
- // Report CPU
+ # Report CPU
cat /proc/cpuinfo | grep 'name' | uniq
- // Differentiate cache between AVX microcode used by Java (until https://github.com/xdev-software/tci/issues/688 is fixed)
+ # Differentiate cache between AVX microcode used by Java (until https://github.com/xdev-software/tci/issues/688 is fixed)
java_useavx=$(java -XX:+PrintFlagsFinal -version 2> /dev/null | awk '/UseAVX/ {print $4}')
./mvnw -B test \
From b8e471534a48ea5970b4c801d0ff23ebaae5d5b1 Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 16:42:13 +0200
Subject: [PATCH 121/141] Add missing headers
---
.../config/OverlayBuildImageHandlerConfig.java | 15 +++++++++++++++
.../handler/AbstractBuildImageHandler.java | 15 +++++++++++++++
.../handler/AdvancedBuildImageHandler.java | 15 +++++++++++++++
.../tci/imagebuild/handler/BuildImageHandler.java | 15 +++++++++++++++
.../handler/NativeAdvancedBuildImageHandler.java | 15 +++++++++++++++
5 files changed, 75 insertions(+)
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/config/OverlayBuildImageHandlerConfig.java b/image-build/src/main/java/software/xdev/tci/imagebuild/config/OverlayBuildImageHandlerConfig.java
index c300456c..65ffafdc 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/config/OverlayBuildImageHandlerConfig.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/config/OverlayBuildImageHandlerConfig.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.imagebuild.config;
import java.util.Optional;
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AbstractBuildImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AbstractBuildImageHandler.java
index 2e29fb26..a2412174 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AbstractBuildImageHandler.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AbstractBuildImageHandler.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.imagebuild.handler;
import java.time.Duration;
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AdvancedBuildImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AdvancedBuildImageHandler.java
index 2c2d92c7..cd013563 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AdvancedBuildImageHandler.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/AdvancedBuildImageHandler.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.imagebuild.handler;
import java.time.Duration;
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/handler/BuildImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/BuildImageHandler.java
index c8bf53f1..399c9b82 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/handler/BuildImageHandler.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/BuildImageHandler.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.imagebuild.handler;
import java.time.Duration;
diff --git a/image-build/src/main/java/software/xdev/tci/imagebuild/handler/NativeAdvancedBuildImageHandler.java b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/NativeAdvancedBuildImageHandler.java
index 6a33a3c4..112c9b8f 100644
--- a/image-build/src/main/java/software/xdev/tci/imagebuild/handler/NativeAdvancedBuildImageHandler.java
+++ b/image-build/src/main/java/software/xdev/tci/imagebuild/handler/NativeAdvancedBuildImageHandler.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.tci.imagebuild.handler;
import java.time.Duration;
From d818db8abaa1de9684000f0bcbecbddbb4adda4e Mon Sep 17 00:00:00 2001
From: AB
Date: Tue, 14 Jul 2026 16:44:35 +0200
Subject: [PATCH 122/141] Update CHANGELOG.md
---
CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28e2023b..56e02952 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
* Added `image-build` module
* This module utilizes v4 of [testcontainers-advanced-imagebuilder](https://github.com/xdev-software/testcontainers-advanced-imagebuilder)
* It contains some shortcuts that help with common configuration e.g. related to caching (see below)
+ * Can be configured using environment variables or system properties (see corresponding configuration section for details)
* Added dedicated composite actions for caching
* These can be found in `.github/actions/docker-image-cache`
* Currently 3 actions exist:
@@ -12,6 +13,7 @@
* primarily designed to be used with `FastAbortOnContainerDeathWaitStrategy`
* the strategies can be aborted e.g. when a container died during startup
* a dead container therefore no longer has to wait until the corresponding strategy times out
+ * Implemented in all applicable modules
* `db-jdbc-spring-orm` & implementations
* No longer use `setPersistenceProviderClassName` because it has no effect
* Performance: Reuse `PersistenceProvider`
From ebdb60b0a3f799e83803fc1850f685f470bd09a7 Mon Sep 17 00:00:00 2001
From: "Alex \"Blex\" B" <45384811+AB-xdev@users.noreply.github.com>
Date: Tue, 14 Jul 2026 17:01:06 +0200
Subject: [PATCH 123/141] Update CONTRIBUTING.md
Update recommended setup
---
CONTRIBUTING.md | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2c7b0250..c77eab35 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,14 +23,15 @@ You should have the following things installed:
* Maven (Note that the [Maven Wrapper](https://maven.apache.org/wrapper/) is shipped with the repo)
### Recommended setup
-* Install ``IntelliJ`` (Community Edition is sufficient)
- * Install the following plugins:
- * [Save Actions](https://plugins.jetbrains.com/plugin/22113) - Provides save actions, like running the formatter or adding ``final`` to fields
- * [SonarLint](https://plugins.jetbrains.com/plugin/7973-sonarlint) - CodeStyle/CodeAnalysis
- * You may consider disabling telemetry in the settings under ``Tools > Sonarlint -> About``
- * [Checkstyle-IDEA](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea) - CodeStyle/CodeAnalysis
+* Install `IntelliJ`
+ * Recommended setup actions
+ * Disable not needed plugins
+ * Disable [telemetry](https://www.jetbrains.com/help/idea/settings-usage-statistics.html)
+ * Consider [disabling the integrated webserver for security reasons](https://intellij-support.jetbrains.com/hc/en-us/community/posts/8125278285586-Internal-Local-ports-63342-6942?page=1#community_comment_8141506876562) using `-Didea.builtin.server.disabled=true`
+ * Configure the available memory
* Import the project
- * Ensure that everything is encoded in ``UTF-8``
+ * You will get prompted to install the required plugins
+ * Ensure that everything is encoded in `UTF-8`
* Ensure that the JDK/Java-Version is correct
From 4dc1db8168698f69b147261da2ee21a478a190f4 Mon Sep 17 00:00:00 2001
From: "Alex \"Blex\" B" <45384811+AB-xdev@users.noreply.github.com>
Date: Tue, 14 Jul 2026 17:18:14 +0200
Subject: [PATCH 124/141] Update CONTRIBUTING.md
This options crashes the build
---
CONTRIBUTING.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c77eab35..6e9dde56 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -27,7 +27,6 @@ You should have the following things installed:
* Recommended setup actions
* Disable not needed plugins
* Disable [telemetry](https://www.jetbrains.com/help/idea/settings-usage-statistics.html)
- * Consider [disabling the integrated webserver for security reasons](https://intellij-support.jetbrains.com/hc/en-us/community/posts/8125278285586-Internal-Local-ports-63342-6942?page=1#community_comment_8141506876562) using `-Didea.builtin.server.disabled=true`
* Configure the available memory
* Import the project
* You will get prompted to install the required plugins
From dd842234927835ebb0342e0b61d5c0d26d41e175 Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 08:58:36 +0200
Subject: [PATCH 125/141] Update and standardize browser config
---
CHANGELOG.md | 1 +
.../xdev/tci/config/DefaultConfig.java | 9 +-
selenium/README.md | 26 ++++++
.../xdev/tci/selenium/BrowserTCI.java | 2 +-
.../selenium/factory/BrowserTCIFactory.java | 4 +-
.../config/BrowserTCIFactoryConfig.java | 2 +-
.../DefaultBrowserTCIFactoryConfig.java | 87 ++++++++++++++++---
7 files changed, 113 insertions(+), 18 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56e02952..142feea7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@
* Improve container cleanup
* Updated dependencies
* Updated demo
+* Updated docs
# 3.4.1
* Remove dedicated code for Java 17 (no longer needed because Java 21 is the minimum)
diff --git a/base/src/main/java/software/xdev/tci/config/DefaultConfig.java b/base/src/main/java/software/xdev/tci/config/DefaultConfig.java
index e8d3dcd0..4c5d06e9 100644
--- a/base/src/main/java/software/xdev/tci/config/DefaultConfig.java
+++ b/base/src/main/java/software/xdev/tci/config/DefaultConfig.java
@@ -36,11 +36,16 @@ protected Optional resolve(final String propertyName)
protected boolean resolveBool(final String propertyName, final boolean defaultVal)
{
- return this.resolve(propertyName)
- .map(s -> "1".equals(s) || Boolean.parseBoolean(s))
+ return this.resolveBool(propertyName)
.orElse(defaultVal);
}
+ protected Optional resolveBool(final String propertyName)
+ {
+ return this.resolve(propertyName)
+ .map(s -> "1".equals(s) || Boolean.parseBoolean(s));
+ }
+
protected int resolveInt(final String propertyName, final int defaultVal)
{
return this.resolveInt(propertyName, () -> defaultVal);
diff --git a/selenium/README.md b/selenium/README.md
index 75811157..de4650c8 100644
--- a/selenium/README.md
+++ b/selenium/README.md
@@ -9,3 +9,29 @@ TCI for [Selenium](https://github.com/SeleniumHQ/selenium).
* NoVNC support
* Full scale support for recording videos
* Browser Logs can be enabled if required
+
+## Config
+
+The configuration is dynamically loaded from (sorted by highest priority)
+
+* Environment variables
+ * prefixed with ``TCI_SELENIUM_``
+ * all properties are in UPPERCASE and use `_` instead of `.` or `-`
+* System properties
+ * prefixed with ``tci.selenium.``
+
+
+
+Full list of configuration options
+
+_Please note that the preconfigured values usually work out of the box.
_
+_You should know exactly what you're doing when doing modifications._
+
+| Property | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `record-mode` | `Enum` | `RECORD_FAILING` | Recording mode.
Available:- SKIP - Do not record any videos
- RECORD_ALL - Record all tests
- RECORD_FAILING - Record failing tests only
|
+| `dir-for-records` | `String` | `target/records` | Directory for storing the recorded videos |
+| `vnc-enabled` | `bool` | `false` | Enable [VNC](https://en.wikipedia.org/wiki/VNC) and [NoVNC](https://github.com/novnc/novnc). This is usually only needed during debugging. |
+| `bidi-enabled` | `bool` | `true` | Use [Selenium BiDirectional functionality](https://www.selenium.dev/documentation/webdriver/bidi/) instead of legacy Chrome DevTools Protocol (CDP).
Disabling this will make certain operations unavailable e.g. listening for browser logs. |
+| `deactivate-cdp-if-possible` | `bool` | `true` | Disable Chrome DevTools Protocol (CDP) if possible.
CDP requires additional maven dependencies (e.g. `selenium-devtools-v137)` that are not present by default and will result in a warning. |
+| `min-browser-console-log-level` | `Enum` | `ERROR` | Prints out browser console logs. Configures the MINIMUM log level.
Available options:
diff --git a/selenium/src/main/java/software/xdev/tci/selenium/BrowserTCI.java b/selenium/src/main/java/software/xdev/tci/selenium/BrowserTCI.java
index c88cdc11..7009160d 100644
--- a/selenium/src/main/java/software/xdev/tci/selenium/BrowserTCI.java
+++ b/selenium/src/main/java/software/xdev/tci/selenium/BrowserTCI.java
@@ -62,7 +62,7 @@ public class BrowserTCI extends TCI
// https://www.selenium.dev/documentation/webdriver/bidi
protected boolean bidiEnabled = true;
- // Disables the (not standardized) Chrome Dev Tools (CDP) protocol (when bidi is enabled).
+ // Disables the (not standardized) Chrome DevTools protocol (CDP) when bidi is enabled.
// CDP requires additional maven dependencies (e.g. selenium-devtools-v137) that are
// NOT present and result in a warning.
protected boolean deactivateCDPIfPossible = true;
diff --git a/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java b/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java
index 65235342..f153a52d 100644
--- a/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java
+++ b/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java
@@ -88,8 +88,8 @@ public static BrowserTCI createDefaultBrowserTCI(
.withWebDriverRetryCount(Math.max(Math.min(cpuSlownessFactor(), 5), 1))
.withWebDriverRetrySec(25 + cpuSlownessFactor() * 5)
.withBrowserConsoleLog(
- logBrowserConsoleConsumer(config.browserConsoleLogLevel()),
- config.browserConsoleLogLevel().logLevels());
+ logBrowserConsoleConsumer(config.minBrowserConsoleLogLevel()),
+ config.minBrowserConsoleLogLevel().logLevels());
}
@SuppressWarnings({"resource", "checkstyle:MagicNumber"})
diff --git a/selenium/src/main/java/software/xdev/tci/selenium/factory/config/BrowserTCIFactoryConfig.java b/selenium/src/main/java/software/xdev/tci/selenium/factory/config/BrowserTCIFactoryConfig.java
index f83f64f0..883b7d77 100644
--- a/selenium/src/main/java/software/xdev/tci/selenium/factory/config/BrowserTCIFactoryConfig.java
+++ b/selenium/src/main/java/software/xdev/tci/selenium/factory/config/BrowserTCIFactoryConfig.java
@@ -33,5 +33,5 @@ public interface BrowserTCIFactoryConfig
boolean deactivateCdpIfPossible();
- BrowserTCIFactory.BrowserConsoleLogLevel browserConsoleLogLevel();
+ BrowserTCIFactory.BrowserConsoleLogLevel minBrowserConsoleLogLevel();
}
diff --git a/selenium/src/main/java/software/xdev/tci/selenium/factory/config/DefaultBrowserTCIFactoryConfig.java b/selenium/src/main/java/software/xdev/tci/selenium/factory/config/DefaultBrowserTCIFactoryConfig.java
index fa1ac71e..22119602 100644
--- a/selenium/src/main/java/software/xdev/tci/selenium/factory/config/DefaultBrowserTCIFactoryConfig.java
+++ b/selenium/src/main/java/software/xdev/tci/selenium/factory/config/DefaultBrowserTCIFactoryConfig.java
@@ -30,12 +30,43 @@ public class DefaultBrowserTCIFactoryConfig extends DefaultConfig implements Bro
{
private static final Logger LOG = LoggerFactory.getLogger(DefaultBrowserTCIFactoryConfig.class);
- public static final String RECORD_MODE = "recordMode";
- public static final String RECORD_DIR = "recordDir";
- public static final String VNC_ENABLED = "vncEnabled";
- public static final String BIDI_ENABLED = "bidiEnabled";
- public static final String DEACTIVATE_CDP_IF_POSSIBLE = "deactivateCdpIfPossible";
- public static final String BROWSER_CONSOLE_LOG_LEVEL = "browserConsoleLogLevel";
+ /**
+ * @deprecated Use non legacy option instead
+ */
+ @Deprecated(since = "4.0.0")
+ public static final String LEGACY_RECORD_MODE = "recordMode";
+ /**
+ * @deprecated Use non legacy option instead
+ */
+ @Deprecated(since = "4.0.0")
+ public static final String LEGACY_RECORD_DIR = "recordDir";
+ /**
+ * @deprecated Use non legacy option instead
+ */
+ @Deprecated(since = "4.0.0")
+ public static final String LEGACY_VNC_ENABLED = "vncEnabled";
+ /**
+ * @deprecated Use non legacy option instead
+ */
+ @Deprecated(since = "4.0.0")
+ public static final String LEGACY_BIDI_ENABLED = "bidiEnabled";
+ /**
+ * @deprecated Use non legacy option instead
+ */
+ @Deprecated(since = "4.0.0")
+ public static final String LEGACY_DEACTIVATE_CDP_IF_POSSIBLE = "deactivateCdpIfPossible";
+ /**
+ * @deprecated Use non legacy option instead
+ */
+ @Deprecated(since = "4.0.0")
+ public static final String LEGACY_BROWSER_CONSOLE_LOG_LEVEL = "browserConsoleLogLevel";
+
+ public static final String RECORD_MODE = "record-mode";
+ public static final String RECORD_DIR = "record-dir";
+ public static final String VNC_ENABLED = "vnc-enabled";
+ public static final String BIDI_ENABLED = "bidi-enabled";
+ public static final String DEACTIVATE_CDP_IF_POSSIBLE = "deactivate-cdp-if-possible";
+ public static final String MIN_BROWSER_CONSOLE_LOG_LEVEL = "min-browser-console-log-level";
public static final String DEFAULT_RECORD_DIR = "target/records";
@@ -57,7 +88,10 @@ public BrowserWebDriverContainer.RecordingMode recordingMode()
{
if(this.systemRecordingMode == null)
{
- final String resolvedRecordMode = this.resolve(RECORD_MODE).orElse(null);
+ final String resolvedRecordMode = this.resolve(RECORD_MODE)
+ .or(() -> this.resolve(LEGACY_RECORD_MODE)
+ .map(v -> this.reportLegacyConfigOption(LEGACY_RECORD_MODE, RECORD_MODE, v)))
+ .orElse(null);
this.systemRecordingMode = Stream.of(BrowserWebDriverContainer.RecordingMode.values())
.filter(rm -> rm.toString().equals(resolvedRecordMode))
.findFirst()
@@ -72,7 +106,10 @@ public Path dirForRecords()
{
if(this.dirForRecords == null)
{
- this.dirForRecords = Path.of(this.resolve(RECORD_DIR).orElse(DEFAULT_RECORD_DIR));
+ this.dirForRecords = Path.of(this.resolve(RECORD_DIR)
+ .or(() -> this.resolve(LEGACY_RECORD_DIR)
+ .map(v -> this.reportLegacyConfigOption(LEGACY_RECORD_DIR, RECORD_DIR, v)))
+ .orElse(DEFAULT_RECORD_DIR));
final boolean wasCreated = this.dirForRecords.toFile().mkdirs();
LOG.info(
"Default directory for records='{}', created={}", this.dirForRecords.toAbsolutePath(),
@@ -87,7 +124,10 @@ public boolean vncEnabled()
{
if(this.vncEnabled == null)
{
- this.vncEnabled = this.resolveBool(VNC_ENABLED, false);
+ this.vncEnabled = this.resolveBool(VNC_ENABLED)
+ .or(() -> this.resolveBool(LEGACY_VNC_ENABLED)
+ .map(v -> this.reportLegacyConfigOption(LEGACY_VNC_ENABLED, VNC_ENABLED, v)))
+ .orElse(false);
LOG.info("VNC enabled={}", this.vncEnabled);
}
return this.vncEnabled;
@@ -98,7 +138,10 @@ public boolean bidiEnabled()
{
if(this.bidiEnabled == null)
{
- this.bidiEnabled = this.resolveBool(BIDI_ENABLED, true);
+ this.bidiEnabled = this.resolveBool(BIDI_ENABLED)
+ .or(() -> this.resolveBool(LEGACY_BIDI_ENABLED)
+ .map(v -> this.reportLegacyConfigOption(LEGACY_BIDI_ENABLED, BIDI_ENABLED, v)))
+ .orElse(true);
LOG.info("BiDi enabled={}", this.bidiEnabled);
}
return this.bidiEnabled;
@@ -116,15 +159,35 @@ public boolean deactivateCdpIfPossible()
}
@Override
- public BrowserTCIFactory.BrowserConsoleLogLevel browserConsoleLogLevel()
+ public BrowserTCIFactory.BrowserConsoleLogLevel minBrowserConsoleLogLevel()
{
if(this.browserConsoleLogLevel == null)
{
- this.browserConsoleLogLevel = this.resolve(BROWSER_CONSOLE_LOG_LEVEL)
+ this.browserConsoleLogLevel = this.resolve(MIN_BROWSER_CONSOLE_LOG_LEVEL)
+ .or(() -> this.resolve(LEGACY_BROWSER_CONSOLE_LOG_LEVEL)
+ .map(v -> this.reportLegacyConfigOption(
+ LEGACY_BROWSER_CONSOLE_LOG_LEVEL,
+ MIN_BROWSER_CONSOLE_LOG_LEVEL,
+ v))
+ )
.map(BrowserTCIFactory.BrowserConsoleLogLevel::valueOf)
.orElse(BrowserTCIFactory.BrowserConsoleLogLevel.ERROR);
LOG.info("BrowserConsoleLogLevel={}", this.browserConsoleLogLevel);
}
return this.browserConsoleLogLevel;
}
+
+ protected T reportLegacyConfigOption(final String legacy, final String upToDate, final T value)
+ {
+ this.reportLegacyConfigOption(legacy, upToDate);
+ return value;
+ }
+
+ protected void reportLegacyConfigOption(final String legacy, final String upToDate)
+ {
+ LOG.warn(
+ "Detected deprecated config option that will be removed: {} - use {} instead",
+ legacy,
+ upToDate);
+ }
}
From 89e9a4b03e893be0d9bb75f80b65913c5da97314 Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 09:36:19 +0200
Subject: [PATCH 126/141] Standardize and update config properties
Fixes #562
---
CHANGELOG.md | 10 ++++
base/README.md | 56 +++++++++++++++++++
.../xdev/tci/config/DefaultConfig.java | 22 +++++++-
.../prestart/PreStartableTCIFactory.java | 2 +-
.../config/DefaultPreStartConfig.java | 15 ++++-
.../config/DefaultLeakDetectionConfig.java | 2 +-
image-build/README.md | 5 +-
jacoco/README.md | 7 +--
selenium/README.md | 9 ++-
.../DefaultBrowserTCIFactoryConfig.java | 14 -----
10 files changed, 110 insertions(+), 32 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 142feea7..dbe6104f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,16 @@
* `db-jdbc-spring-orm` & implementations
* No longer use `setPersistenceProviderClassName` because it has no effect
* Performance: Reuse `PersistenceProvider`
+* Standardized some configuration properties
+ * `tci.selenium`
+ * `recordMode` → `record-mode`
+ * `recordDir` → `record-dir`
+ * `vncEnabled` → `vnc-enabled`
+ * `bidiEnabled` → `bidi-enabled`
+ * `deactivateCdpIfPossible` → `deactivate-cdp-if-possible`
+ * `browserConsoleLogLevel` → `min-browser-console-log-level`
+ * `infra-pre-start` → `tci.infra-pre-start`
+ * `leak-detection` → `tci.leak-detection`
* Fixed a deadlock during service resolution
* Fixed rate limiting in some places
* Improve container cleanup
diff --git a/base/README.md b/base/README.md
index 78ad3563..bc62c4c0 100644
--- a/base/README.md
+++ b/base/README.md
@@ -20,3 +20,59 @@ Base for other modules and core components.
## Usage
Take a look at the [minimalistic demo](../base-demo/) that showcases the components individually.
+
+## Config
+
+### PreStart
+
+The configuration is dynamically loaded from (sorted by highest priority)
+
+* Environment variables
+ * prefixed with `TCI_INFRA-PRE-START__`*
+ * prefixed with `TCI_INFRA-PRE-START_`
+ * all properties are in UPPERCASE and use `_` instead of `.` or `-`
+* System properties
+ * prefixed with `tci.infra-pre-start..`*
+ * prefixed with `tci.infra-pre-start.`
+
+_NOTE: `*` indicates that only some properties support this_
+
+
+
+Full list of configuration options
+
+| Property | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `enabled` | `bool` | `false` | Should PreStarting be enabled? |
+| `keep-ready`* | `int` | [`junit.jupiter.execution.parallel.config.fixed.max-pool-size`](https://docs.junit.org/6.1.2/writing-tests/parallel-execution.html) or `1` | How many container should be kept ready for use in the background?
Setting this to a value `< 0` will effectively disable PreStarting |
+| `max-start-simultan`* | `int` | [`junit.jupiter.execution.parallel.config.fixed.max-pool-size`](https://docs.junit.org/6.1.2/writing-tests/parallel-execution.html) or `1` | How many contains should be started simultaneously?
Setting a negative value will remove this limitation |
+| `direct-network-attach-if-possible`* | `bool` | `true` | true - Directly attaches the container to the network during startup if possiblefalse - Always performs a network-connect as if PreStarting is active. This is slower, however it emulates PreStarting better and may help with finding bugs.
|
+| `fixate-exposed-ports-if-required`* | `bool` | `true` | Fixates exposed ports when no direct network attach is possible. This is a workaround for moby/moby#44137. |
+| `coordinator.idle-cpu-percent` | `int` | `40`% | Amount of CPU that needs to be idle to allow PreStarting of containers |
+| `coordinator.schedule-period-ms` | `int` | `1000` (1s) | How often PreStarting (one factory) should be tried |
+| `detect-ending-tests` | `bool` | `true` | Should PreStarting be stopped when tests are ending? |
+
+_NOTE: Properties marked with `*` can additionally can use the `preStartName` for configuration. Example: `tci.infra-pre-start.my-webapp.`_
+
+
+
+### Leak Detection
+
+The configuration is dynamically loaded from (sorted by highest priority)
+
+* Environment variables
+ * prefixed with `TCI_LEAK-DETECTION_`
+ * all properties are in UPPERCASE and use `_` instead of `.` or `-`
+* System properties
+ * prefixed with `tci.leak-detection.`
+
+
+
+Full list of configuration options
+
+| Property | Type | Default | Notes |
+| --- | --- | --- | --- |
+| `enabled` | `bool` | `false` | Should Leak-Detection be enabled? |
+| `stop-timeout-ms` | `int` | ~`20000` (~20s) actual value depends on cpuSlownessFactor | How long to wait until all infrastructure is stopped after tests have ended |
+
+
diff --git a/base/src/main/java/software/xdev/tci/config/DefaultConfig.java b/base/src/main/java/software/xdev/tci/config/DefaultConfig.java
index 4c5d06e9..38cf75d7 100644
--- a/base/src/main/java/software/xdev/tci/config/DefaultConfig.java
+++ b/base/src/main/java/software/xdev/tci/config/DefaultConfig.java
@@ -20,6 +20,8 @@
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
+import org.slf4j.LoggerFactory;
+
public abstract class DefaultConfig
{
@@ -27,7 +29,11 @@ public abstract class DefaultConfig
protected Optional resolve(final String propertyName)
{
- final String fullPropertyName = this.propertyNamePrefix() + "." + propertyName;
+ return this.resolveFullyBuildPropertyName(this.propertyNamePrefix() + "." + propertyName);
+ }
+
+ protected Optional resolveFullyBuildPropertyName(final String fullPropertyName)
+ {
return Optional.ofNullable(System.getenv(fullPropertyName
.replace(".", "_")
.toUpperCase(Locale.ROOT)))
@@ -82,4 +88,18 @@ protected long resolveLong(final String propertyName, final LongSupplier default
})
.orElseGet(defaultValueSupplier::getAsLong);
}
+
+ protected T reportLegacyConfigOption(final String legacy, final String upToDate, final T value)
+ {
+ this.reportLegacyConfigOption(legacy, upToDate);
+ return value;
+ }
+
+ protected void reportLegacyConfigOption(final String legacy, final String upToDate)
+ {
+ LoggerFactory.getLogger(this.getClass()).warn(
+ "Detected deprecated config option that will be removed: {} - use {} instead",
+ legacy,
+ upToDate);
+ }
}
diff --git a/base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java b/base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java
index ea2bc211..0b1ed959 100644
--- a/base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java
+++ b/base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java
@@ -150,7 +150,7 @@ public class PreStartableTCIFactory, I extends TCI
/**
* Has the following effects:
*
- * true (default) - Directly attaches the Container to the network during startup if
+ * true (default) - Directly attaches the container to the network during startup if
* possible
* false - Performs a Network#connect as if PreStarting is active.
* This is slower however it emulates PreStarting better and may help finding bugs.
diff --git a/base/src/main/java/software/xdev/tci/factory/prestart/config/DefaultPreStartConfig.java b/base/src/main/java/software/xdev/tci/factory/prestart/config/DefaultPreStartConfig.java
index 76f45e6e..7ffffbf6 100644
--- a/base/src/main/java/software/xdev/tci/factory/prestart/config/DefaultPreStartConfig.java
+++ b/base/src/main/java/software/xdev/tci/factory/prestart/config/DefaultPreStartConfig.java
@@ -90,7 +90,20 @@ public DefaultPreStartConfig()
@Override
public String propertyNamePrefix()
{
- return "infra-pre-start";
+ return "tci.infra-pre-start";
+ }
+
+ @Override
+ protected Optional resolve(final String propertyName)
+ {
+ return super.resolve(propertyName)
+ .or(() -> this.resolveFullyBuildPropertyName("infra-prestart." + propertyName)
+ .map(v -> this.reportLegacyConfigOption(
+ "infra-prestart." + propertyName,
+ this.propertyNamePrefix() + "." + propertyName,
+ v)
+ )
+ );
}
@Override
diff --git a/base/src/main/java/software/xdev/tci/leakdetection/config/DefaultLeakDetectionConfig.java b/base/src/main/java/software/xdev/tci/leakdetection/config/DefaultLeakDetectionConfig.java
index a6664d55..c827642f 100644
--- a/base/src/main/java/software/xdev/tci/leakdetection/config/DefaultLeakDetectionConfig.java
+++ b/base/src/main/java/software/xdev/tci/leakdetection/config/DefaultLeakDetectionConfig.java
@@ -35,7 +35,7 @@ public DefaultLeakDetectionConfig()
@Override
protected String propertyNamePrefix()
{
- return "leak-detection";
+ return "tci.leak-detection";
}
@Override
diff --git a/image-build/README.md b/image-build/README.md
index ba834dd3..2430e99d 100644
--- a/image-build/README.md
+++ b/image-build/README.md
@@ -16,15 +16,12 @@ Provides common utility and configuration to build images.
* where `imageName` is the (sanitized) name of the image to build
* prefixed with `tci.image-build.`
-_NOTE: Sanitized image names only include alphanumeric characters, `_` or `-`. All other characters are replaced by `_`_
+_NOTE: Sanitized image-names only include alphanumeric characters, `_` or `-`. All other characters are replaced by `_`_
Full list of configuration options
-_Please note that the preconfigured values usually work out of the box.
_
-_You should know exactly what you're doing when doing modifications._
-
| Property | Type | Default | Notes |
| --- | --- | --- | --- |
| `delete-on-exit` | `bool` | `false` | Should the image be deleted on exit? |
diff --git a/jacoco/README.md b/jacoco/README.md
index def6f75d..c746e030 100644
--- a/jacoco/README.md
+++ b/jacoco/README.md
@@ -12,18 +12,15 @@ Please have a look at the [advanced-demo](../advanced-demo/) and the correspondi
The configuration is dynamically loaded from (sorted by highest priority)
* Environment variables
- * prefixed with ``TCI_JACOCO_``
+ * prefixed with `TCI_JACOCO_`
* all properties are in UPPERCASE and use `_` instead of `.` or `-`
* System properties
- * prefixed with ``tci.jacoco.``
+ * prefixed with `tci.jacoco.`
Full list of configuration options
-_Please note that the preconfigured values usually work out of the box.
_
-_You should know exactly what you're doing when doing modifications._
-
| Property | Type | Default | Notes |
| --- | --- | --- | --- |
| `enabled` | `bool` | `false` | You should probably set this to `true` |
diff --git a/selenium/README.md b/selenium/README.md
index de4650c8..274838d8 100644
--- a/selenium/README.md
+++ b/selenium/README.md
@@ -15,18 +15,15 @@ TCI for [Selenium](https://github.com/SeleniumHQ/selenium).
The configuration is dynamically loaded from (sorted by highest priority)
* Environment variables
- * prefixed with ``TCI_SELENIUM_``
+ * prefixed with `TCI_SELENIUM_`
* all properties are in UPPERCASE and use `_` instead of `.` or `-`
* System properties
- * prefixed with ``tci.selenium.``
+ * prefixed with `tci.selenium.`
Full list of configuration options
-_Please note that the preconfigured values usually work out of the box.
_
-_You should know exactly what you're doing when doing modifications._
-
| Property | Type | Default | Notes |
| --- | --- | --- | --- |
| `record-mode` | `Enum` | `RECORD_FAILING` | Recording mode.
Available:- SKIP - Do not record any videos
- RECORD_ALL - Record all tests
- RECORD_FAILING - Record failing tests only
|
@@ -35,3 +32,5 @@ _You should know exactly what you're doing when doing modifications._
| `bidi-enabled` | `bool` | `true` | Use [Selenium BiDirectional functionality](https://www.selenium.dev/documentation/webdriver/bidi/) instead of legacy Chrome DevTools Protocol (CDP).
Disabling this will make certain operations unavailable e.g. listening for browser logs. |
| `deactivate-cdp-if-possible` | `bool` | `true` | Disable Chrome DevTools Protocol (CDP) if possible.
CDP requires additional maven dependencies (e.g. `selenium-devtools-v137)` that are not present by default and will result in a warning. |
| `min-browser-console-log-level` | `Enum` | `ERROR` | Prints out browser console logs. Configures the MINIMUM log level.
Available options:
+
+
diff --git a/selenium/src/main/java/software/xdev/tci/selenium/factory/config/DefaultBrowserTCIFactoryConfig.java b/selenium/src/main/java/software/xdev/tci/selenium/factory/config/DefaultBrowserTCIFactoryConfig.java
index 22119602..316952ac 100644
--- a/selenium/src/main/java/software/xdev/tci/selenium/factory/config/DefaultBrowserTCIFactoryConfig.java
+++ b/selenium/src/main/java/software/xdev/tci/selenium/factory/config/DefaultBrowserTCIFactoryConfig.java
@@ -176,18 +176,4 @@ public BrowserTCIFactory.BrowserConsoleLogLevel minBrowserConsoleLogLevel()
}
return this.browserConsoleLogLevel;
}
-
- protected T reportLegacyConfigOption(final String legacy, final String upToDate, final T value)
- {
- this.reportLegacyConfigOption(legacy, upToDate);
- return value;
- }
-
- protected void reportLegacyConfigOption(final String legacy, final String upToDate)
- {
- LOG.warn(
- "Detected deprecated config option that will be removed: {} - use {} instead",
- legacy,
- upToDate);
- }
}
From 63e25f75c05c7b855ef922205efcadb70c268a3d Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 09:37:18 +0200
Subject: [PATCH 127/141] Migrate existing properties
---
.github/workflows/run-integration-tests.yml | 4 ++--
.../tci/factory/prestart/config/DefaultPreStartConfig.java | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 228097ad..355161aa 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -80,7 +80,7 @@ jobs:
./mvnw -B test \
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
-P run-it \
- ${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
+ ${{ matrix.pre-start && '-Dtci.infra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
env:
ACTIONS_RUNTIME_TOKEN: ${{ steps.docker-image-cache-setup-buildx.outputs.ACTIONS_RUNTIME_TOKEN }}
@@ -165,7 +165,7 @@ jobs:
-Dtci.image-build.cache-to="type=gha,mode=max,scope=§image" \
-Dtci.image-build.tci-demo-webapp.cache-from="type=gha,scope=§image-j_avx${java_useavx}${{ matrix.jacoco && '-jacoco' || '' }}" \
-Dtci.image-build.tci-demo-webapp.cache-to="type=gha,mode=max,scope=§image-j_avx${java_useavx}${{ matrix.jacoco && '-jacoco' || '' }}" \
- ${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \
+ ${{ matrix.pre-start && '-Dtci.infra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
env:
ACTIONS_RUNTIME_TOKEN: ${{ steps.docker-image-cache-setup-buildx.outputs.ACTIONS_RUNTIME_TOKEN }}
diff --git a/base/src/main/java/software/xdev/tci/factory/prestart/config/DefaultPreStartConfig.java b/base/src/main/java/software/xdev/tci/factory/prestart/config/DefaultPreStartConfig.java
index 7ffffbf6..ae19b745 100644
--- a/base/src/main/java/software/xdev/tci/factory/prestart/config/DefaultPreStartConfig.java
+++ b/base/src/main/java/software/xdev/tci/factory/prestart/config/DefaultPreStartConfig.java
@@ -25,8 +25,8 @@
*
* Properties can be defined in the following way:
*
- * -Dinfra-pre-start.keep-ready=2
- * -Dinfra-pre-start.coordinator.idle-cpu-percent=50
+ * -Dtci.infra-pre-start.keep-ready=2
+ * -Dtci.infra-pre-start.coordinator.idle-cpu-percent=50
*
*
*/
From 92ef80d8d32954573347f954f6b5793dbb39382d Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 09:40:05 +0200
Subject: [PATCH 128/141] Improve table format
---
base/README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/base/README.md b/base/README.md
index bc62c4c0..451318eb 100644
--- a/base/README.md
+++ b/base/README.md
@@ -44,8 +44,8 @@ _NOTE: `*` indicates that only some properties support this_
| Property | Type | Default | Notes |
| --- | --- | --- | --- |
| `enabled` | `bool` | `false` | Should PreStarting be enabled? |
-| `keep-ready`* | `int` | [`junit.jupiter.execution.parallel.config.fixed.max-pool-size`](https://docs.junit.org/6.1.2/writing-tests/parallel-execution.html) or `1` | How many container should be kept ready for use in the background?
Setting this to a value `< 0` will effectively disable PreStarting |
-| `max-start-simultan`* | `int` | [`junit.jupiter.execution.parallel.config.fixed.max-pool-size`](https://docs.junit.org/6.1.2/writing-tests/parallel-execution.html) or `1` | How many contains should be started simultaneously?
Setting a negative value will remove this limitation |
+| `keep-ready`* | `int` | [`junit.jupiter.execution.parallel.`
`config.fixed.max-pool-size`](https://docs.junit.org/6.1.2/writing-tests/parallel-execution.html) or `1` | How many container should be kept ready for use in the background?
Setting this to a value `< 0` will effectively disable PreStarting |
+| `max-start-simultan`* | `int` | [`junit.jupiter.execution.parallel.`
`config.fixed.max-pool-size`](https://docs.junit.org/6.1.2/writing-tests/parallel-execution.html) or `1` | How many contains should be started simultaneously?
Setting a negative value will remove this limitation |
| `direct-network-attach-if-possible`* | `bool` | `true` | true - Directly attaches the container to the network during startup if possiblefalse - Always performs a network-connect as if PreStarting is active. This is slower, however it emulates PreStarting better and may help with finding bugs.
|
| `fixate-exposed-ports-if-required`* | `bool` | `true` | Fixates exposed ports when no direct network attach is possible. This is a workaround for moby/moby#44137. |
| `coordinator.idle-cpu-percent` | `int` | `40`% | Amount of CPU that needs to be idle to allow PreStarting of containers |
From 7f35e3ec2df4c27c7227229860f6e22a4164f34a Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 09:41:07 +0200
Subject: [PATCH 129/141] Update README.md
---
base/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/base/README.md b/base/README.md
index 451318eb..a8222fb2 100644
--- a/base/README.md
+++ b/base/README.md
@@ -45,7 +45,7 @@ _NOTE: `*` indicates that only some properties support this_
| --- | --- | --- | --- |
| `enabled` | `bool` | `false` | Should PreStarting be enabled? |
| `keep-ready`* | `int` | [`junit.jupiter.execution.parallel.`
`config.fixed.max-pool-size`](https://docs.junit.org/6.1.2/writing-tests/parallel-execution.html) or `1` | How many container should be kept ready for use in the background?
Setting this to a value `< 0` will effectively disable PreStarting |
-| `max-start-simultan`* | `int` | [`junit.jupiter.execution.parallel.`
`config.fixed.max-pool-size`](https://docs.junit.org/6.1.2/writing-tests/parallel-execution.html) or `1` | How many contains should be started simultaneously?
Setting a negative value will remove this limitation |
+| `max-start-simultan`* | `int` | [`junit.jupiter.execution.parallel.`
`config.fixed.max-pool-size`](https://docs.junit.org/6.1.2/writing-tests/parallel-execution.html) or `1` | Maximum amount of containers that should be started simultaneously
Setting a negative value will remove this limitation |
| `direct-network-attach-if-possible`* | `bool` | `true` | true - Directly attaches the container to the network during startup if possiblefalse - Always performs a network-connect as if PreStarting is active. This is slower, however it emulates PreStarting better and may help with finding bugs.
|
| `fixate-exposed-ports-if-required`* | `bool` | `true` | Fixates exposed ports when no direct network attach is possible. This is a workaround for moby/moby#44137. |
| `coordinator.idle-cpu-percent` | `int` | `40`% | Amount of CPU that needs to be idle to allow PreStarting of containers |
From 4bced1a1bb71b9e0d4e03325813a744a46d0a4a6 Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 15:27:23 +0200
Subject: [PATCH 130/141] Added `mailpit` module
---
.github/workflows/run-integration-tests.yml | 2 +
.../run-mailpit-integration-tests.yml | 67 ++++
CHANGELOG.md | 1 +
bom/pom.xml | 5 +
mailpit/Dockerfile | 3 +
mailpit/README.md | 3 +
mailpit/pom.xml | 339 ++++++++++++++++++
.../software/xdev/tci/mailpit/MailpitTCI.java | 87 +++++
.../mailpit/containers/MailpitContainer.java | 60 ++++
.../mailpit/factory/MailpitTCIFactory.java | 56 +++
.../xdev/mailpit/SimpleMailpitTest.java | 130 +++++++
pom.xml | 1 +
12 files changed, 754 insertions(+)
create mode 100644 .github/workflows/run-mailpit-integration-tests.yml
create mode 100644 mailpit/Dockerfile
create mode 100644 mailpit/README.md
create mode 100644 mailpit/pom.xml
create mode 100644 mailpit/src/main/java/software/xdev/tci/mailpit/MailpitTCI.java
create mode 100644 mailpit/src/main/java/software/xdev/tci/mailpit/containers/MailpitContainer.java
create mode 100644 mailpit/src/main/java/software/xdev/tci/mailpit/factory/MailpitTCIFactory.java
create mode 100644 mailpit/src/test/java/software/xdev/mailpit/SimpleMailpitTest.java
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 355161aa..6eb64a67 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -79,6 +79,7 @@ jobs:
run: |
./mvnw -B test \
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
+ -T2C \
-P run-it \
${{ matrix.pre-start && '-Dtci.infra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
@@ -159,6 +160,7 @@ jobs:
./mvnw -B test \
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
+ -T2C \
-P run-it${{ matrix.jacoco && ',jacoco' || '' }} \
-Dtci.image-build.delete-on-exit=true \
-Dtci.image-build.cache-from="type=gha,scope=§image" \
diff --git a/.github/workflows/run-mailpit-integration-tests.yml b/.github/workflows/run-mailpit-integration-tests.yml
new file mode 100644
index 00000000..97e0a4d2
--- /dev/null
+++ b/.github/workflows/run-mailpit-integration-tests.yml
@@ -0,0 +1,67 @@
+name: Run Mailpit integration tests
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [ develop ]
+ paths:
+ - 'mailpit/**'
+ pull_request:
+ branches: [ develop ]
+ paths:
+ - 'mailpit/**'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ mailpit:
+ runs-on: ubuntu-latest
+ if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
+ timeout-minutes: 7
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@v7
+
+ - parallel:
+ - name: Set up JDK
+ uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: ${{ matrix.java }}
+
+ - name: Cache Maven
+ uses: actions/cache@v6
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-mvn-it-build-mailpit-${{ hashFiles('**/pom.xml') }}
+
+ - name: Docker Image Cache Restore
+ id: docker-image-cache-restore
+ uses: ./.github/actions/docker-image-cache/restore
+ with:
+ # This cache could always change however because docker images can be changed remotely if the version is not fixated
+ # As a compromise it utilizes the hash of the pom files because when the dependencies stay the same
+ # the images should also be roughly the same
+ key: it-docker-images-mailpit-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-it-docker-images-mailpit-${{ hashFiles('**/pom.xml') }}
+ ${{ runner.os }}-it-docker-images-mailpit-
+
+ - name: Test
+ run: |
+ ./mvnw -B test \
+ -pl "mailpit" -am \
+ -T2C \
+ -P run-it
+
+ - name: Show images
+ run: docker image ls
+
+ - name: Docker Image Cache Save
+ uses: ./.github/actions/docker-image-cache/save
+ with:
+ key: ${{ steps.docker-image-cache-restore.outputs.key }}
+ cache-hit: ${{ steps.docker-image-cache-restore.outputs.cache-hit }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dbe6104f..4bf5df13 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
* This module utilizes v4 of [testcontainers-advanced-imagebuilder](https://github.com/xdev-software/testcontainers-advanced-imagebuilder)
* It contains some shortcuts that help with common configuration e.g. related to caching (see below)
* Can be configured using environment variables or system properties (see corresponding configuration section for details)
+* Added `mailpit` module
* Added dedicated composite actions for caching
* These can be found in `.github/actions/docker-image-cache`
* Currently 3 actions exist:
diff --git a/bom/pom.xml b/bom/pom.xml
index 7f6e1f83..e373dffe 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -93,6 +93,11 @@
junit-jupiter-api-support
4.0.0-SNAPSHOT
+
+ software.xdev.tci
+ mailpit
+ 4.0.0-SNAPSHOT
+
software.xdev.tci
mockserver
diff --git a/mailpit/Dockerfile b/mailpit/Dockerfile
new file mode 100644
index 00000000..3c9c49a3
--- /dev/null
+++ b/mailpit/Dockerfile
@@ -0,0 +1,3 @@
+# This file is just used as a reminder to update the latest minor version in
+# MailpitContainer.java from time to time
+FROM axllent/mailpit:v1.30
diff --git a/mailpit/README.md b/mailpit/README.md
new file mode 100644
index 00000000..9d251cbb
--- /dev/null
+++ b/mailpit/README.md
@@ -0,0 +1,3 @@
+# Mailpit
+
+TCI for [Mailpit](https://github.com/axllent/mailpit).
diff --git a/mailpit/pom.xml b/mailpit/pom.xml
new file mode 100644
index 00000000..8fd3b4cf
--- /dev/null
+++ b/mailpit/pom.xml
@@ -0,0 +1,339 @@
+
+
+ 4.0.0
+
+ software.xdev.tci
+ mailpit
+ 4.0.0-SNAPSHOT
+ jar
+
+ mailpit
+ TCI - mailpit
+ https://github.com/xdev-software/tci
+
+
+ https://github.com/xdev-software/tci
+ scm:git:https://github.com/xdev-software/tci.git
+
+
+ 2025
+
+
+ XDEV Software
+ https://xdev.software
+
+
+
+
+ XDEV Software
+ XDEV Software
+ https://xdev.software
+
+
+
+
+
+ Apache-2.0
+ https://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ 21
+ ${javaVersion}
+
+ UTF-8
+ UTF-8
+
+
+ true
+
+
+
+
+ software.xdev.tci
+ base
+ 4.0.0-SNAPSHOT
+
+
+
+ software.xdev
+ mailpit-java-client
+ 1.0.0
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 6.1.2
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ 2.0.18
+ test
+
+
+
+ org.simplejavamail
+ simple-java-mail
+ 9.1.0
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-site-plugin
+ 4.0.0-M16
+
+
+ org.apache.maven.plugins
+ maven-project-info-reports-plugin
+ 3.9.0
+
+
+
+
+
+ com.mycila
+ license-maven-plugin
+ 5.0.0
+
+
+ ${project.organization.url}
+
+
+
+ com/mycila/maven/plugin/license/templates/APACHE-2.txt
+
+ src/main/java/**
+ src/test/java/**
+
+
+
+
+
+
+ first
+
+ format
+
+ process-sources
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.15.0
+
+ ${maven.compiler.release}
+
+ -proc:none
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.12.0
+
+
+ attach-javadocs
+ package
+
+ jar
+
+
+
+
+ true
+ none
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.4.0
+
+
+ attach-sources
+ package
+
+ jar-no-fork
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.6
+
+ ${skipTests}
+
+
+
+
+
+
+ ignore-service-loading
+
+
+
+ src/main/resources
+
+ META-INF/services/**
+
+
+
+
+
+
+ publish
+
+
+
+ org.codehaus.mojo
+ flatten-maven-plugin
+ 1.7.3
+
+ ossrh
+
+
+
+ flatten
+ process-resources
+
+ flatten
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 3.2.8
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+ --pinentry-mode
+ loopback
+
+
+
+
+
+
+
+
+
+ publish-sonatype-central-portal
+
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.11.0
+ true
+
+ sonatype-central-portal
+ true
+
+
+
+
+
+
+ checkstyle
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.6.0
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 13.8.0
+
+
+
+ ../.config/checkstyle/checkstyle.xml
+ true
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+ pmd
+
+
+
+ org.apache.maven.plugins
+ maven-pmd-plugin
+ 3.28.0
+
+ true
+ true
+ true
+
+ ../.config/pmd/java/ruleset.xml
+
+
+
+
+ net.sourceforge.pmd
+ pmd-core
+ 7.26.0
+
+
+ net.sourceforge.pmd
+ pmd-java
+ 7.26.0
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jxr-plugin
+ 3.6.0
+
+
+
+
+
+
diff --git a/mailpit/src/main/java/software/xdev/tci/mailpit/MailpitTCI.java b/mailpit/src/main/java/software/xdev/tci/mailpit/MailpitTCI.java
new file mode 100644
index 00000000..e63c7639
--- /dev/null
+++ b/mailpit/src/main/java/software/xdev/tci/mailpit/MailpitTCI.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.mailpit;
+
+import java.time.Duration;
+
+import org.apache.hc.client5.http.config.ConnectionConfig;
+import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
+import org.apache.hc.core5.util.Timeout;
+import org.slf4j.LoggerFactory;
+
+import software.xdev.mailpit.client.ApiClient;
+import software.xdev.tci.TCI;
+import software.xdev.tci.mailpit.containers.MailpitContainer;
+
+
+public class MailpitTCI extends TCI
+{
+ protected ApiClient apiClient;
+
+ public MailpitTCI(final MailpitContainer container, final String networkAlias)
+ {
+ super(container, networkAlias);
+ }
+
+ public String getExternalHTTPEndpoint()
+ {
+ return "http://"
+ + this.getContainer().getHost()
+ + ":"
+ + this.getContainer().getMappedPort(MailpitContainer.WEB_PORT);
+ }
+
+ public ApiClient apiClient()
+ {
+ if(this.apiClient != null)
+ {
+ return this.apiClient;
+ }
+
+ final Duration defaultTimeout = Duration.ofSeconds(30);
+
+ this.apiClient = new ApiClient();
+ this.apiClient.setHttpClient(HttpClientBuilder.create()
+ .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
+ .setDefaultConnectionConfig(ConnectionConfig.custom()
+ .setConnectTimeout(Timeout.of(defaultTimeout))
+ .setSocketTimeout(Timeout.of(defaultTimeout))
+ .build())
+ .build())
+ .build());
+ this.apiClient.setBasePath(this.getExternalHTTPEndpoint());
+
+ return this.apiClient;
+ }
+
+ @Override
+ public void stop()
+ {
+ if(this.apiClient != null)
+ {
+ try
+ {
+ this.apiClient.getHttpClient().close();
+ }
+ catch(final Exception e)
+ {
+ LoggerFactory.getLogger(this.getClass()).warn("Failed to close API client", e);
+ }
+ }
+ super.stop();
+ }
+}
diff --git a/mailpit/src/main/java/software/xdev/tci/mailpit/containers/MailpitContainer.java b/mailpit/src/main/java/software/xdev/tci/mailpit/containers/MailpitContainer.java
new file mode 100644
index 00000000..785088f2
--- /dev/null
+++ b/mailpit/src/main/java/software/xdev/tci/mailpit/containers/MailpitContainer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.mailpit.containers;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
+
+
+public class MailpitContainer extends GenericContainer
+{
+ public static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse("axllent/mailpit:v1.30");
+
+ public static final int WEB_PORT = 8025;
+ public static final int SMTP_PORT = 1025;
+
+ public MailpitContainer()
+ {
+ super(DEFAULT_IMAGE);
+
+ // Version check is not needed in tests
+ this.addEnv("MP_DISABLE_VERSION_CHECK", "true");
+ // Resolving client reverse dns is not needed
+ this.addEnv("MP_SMTP_DISABLE_RDNS", "true");
+
+ // Enforce CSP (only important when debugging)
+ this.addEnv("MP_BLOCK_REMOTE_CSS_AND_FONTS", "true");
+
+ // Use normal SMTP
+ this.addEnv("MP_SMTP_AUTH_ALLOW_INSECURE", "true");
+
+ this.addExposedPort(WEB_PORT);
+ }
+
+ public MailpitContainer withSmtpAuth(final Map usernamePasswords)
+ {
+ this.addEnv(
+ "MP_SMTP_AUTH",
+ usernamePasswords.entrySet()
+ .stream()
+ .map(e -> e.getKey() + ":" + e.getValue())
+ .collect(Collectors.joining(" ")));
+ return this;
+ }
+}
diff --git a/mailpit/src/main/java/software/xdev/tci/mailpit/factory/MailpitTCIFactory.java b/mailpit/src/main/java/software/xdev/tci/mailpit/factory/MailpitTCIFactory.java
new file mode 100644
index 00000000..a5d172fc
--- /dev/null
+++ b/mailpit/src/main/java/software/xdev/tci/mailpit/factory/MailpitTCIFactory.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.tci.mailpit.factory;
+
+import java.util.Map;
+import java.util.function.Supplier;
+
+import software.xdev.tci.factory.prestart.PreStartableTCIFactory;
+import software.xdev.tci.mailpit.MailpitTCI;
+import software.xdev.tci.mailpit.containers.MailpitContainer;
+import software.xdev.tci.misc.ContainerMemory;
+
+
+public class MailpitTCIFactory extends PreStartableTCIFactory
+{
+ public static final String DEFAULT_USER = "no-reply@test.localhost";
+ public static final String DEFAULT_PW = "test";
+ public static final int SMTP_PORT = MailpitContainer.SMTP_PORT;
+
+ public MailpitTCIFactory()
+ {
+ this(MailpitTCIFactory::createDefaultContainer);
+ }
+
+ public MailpitTCIFactory(final Supplier mailpitContainerSupplier)
+ {
+ super(
+ MailpitTCI::new,
+ mailpitContainerSupplier,
+ "mailpit",
+ "container.mailpit",
+ "Mailpit"
+ );
+ }
+
+ @SuppressWarnings("resource")
+ public static MailpitContainer createDefaultContainer()
+ {
+ return new MailpitContainer()
+ .withSmtpAuth(Map.of(DEFAULT_USER, DEFAULT_PW))
+ .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M));
+ }
+}
diff --git a/mailpit/src/test/java/software/xdev/mailpit/SimpleMailpitTest.java b/mailpit/src/test/java/software/xdev/mailpit/SimpleMailpitTest.java
new file mode 100644
index 00000000..9752cb47
--- /dev/null
+++ b/mailpit/src/test/java/software/xdev/mailpit/SimpleMailpitTest.java
@@ -0,0 +1,130 @@
+package software.xdev.mailpit;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.simplejavamail.api.mailer.Mailer;
+import org.simplejavamail.api.mailer.config.TransportStrategy;
+import org.simplejavamail.email.EmailBuilder;
+import org.simplejavamail.mailer.MailerBuilder;
+import org.simplejavamail.recipient.RecipientBuilder;
+
+import software.xdev.mailpit.api.MessageApi;
+import software.xdev.mailpit.client.ApiClient;
+import software.xdev.mailpit.model.Message;
+import software.xdev.tci.concurrent.TCIExecutorServiceHolder;
+import software.xdev.tci.factory.registry.TCIFactoryRegistry;
+import software.xdev.tci.mailpit.MailpitTCI;
+import software.xdev.tci.mailpit.containers.MailpitContainer;
+import software.xdev.tci.mailpit.factory.MailpitTCIFactory;
+import software.xdev.tci.network.LazyNetwork;
+import software.xdev.tci.network.LazyNetworkPool;
+
+
+class SimpleMailpitTest
+{
+ protected static final MailpitTCIFactory FACTORY = new MailpitTCIFactory(() -> {
+ final MailpitContainer container = MailpitTCIFactory.createDefaultContainer();
+ // For testing we send mails form the host so SMTP must be exposed
+ container.addExposedPort(MailpitTCIFactory.SMTP_PORT);
+ return container;
+ });
+ protected static final LazyNetworkPool LAZY_NETWORK_POOL = new LazyNetworkPool();
+
+ protected LazyNetwork network;
+ protected MailpitTCI mailpitInfra;
+
+ @BeforeAll
+ static void beforeAll()
+ {
+ LAZY_NETWORK_POOL.managePoolAsync();
+
+ TCIFactoryRegistry.instance().warmUp();
+
+ // Preload classes
+ CompletableFuture.runAsync(ApiClient::new, TCIExecutorServiceHolder.instance());
+ }
+
+ @BeforeEach
+ void beforeEach()
+ {
+ this.network = LAZY_NETWORK_POOL.getNew();
+ this.mailpitInfra = FACTORY.getNew(this.network);
+ }
+
+ @Test
+ void check()
+ {
+ final String toAddress = "m.mustermann@test.localhost";
+ final String subject = "Test";
+ final String plainText = "This is a test";
+
+ try(final Mailer mailer = MailerBuilder.withSMTPServer(
+ this.mailpitInfra.getContainer().getHost(),
+ this.mailpitInfra.getContainer().getMappedPort(MailpitTCIFactory.SMTP_PORT),
+ MailpitTCIFactory.DEFAULT_USER,
+ MailpitTCIFactory.DEFAULT_PW)
+ .withTransportStrategy(TransportStrategy.SMTP)
+ .withDebugLogging(true)
+ .buildMailer())
+ {
+ mailer.sendMail(EmailBuilder.startingBlank()
+ .from(MailpitTCIFactory.DEFAULT_USER)
+ .withRecipients(new RecipientBuilder()
+ .withType(jakarta.mail.Message.RecipientType.TO)
+ .withAddress(toAddress)
+ .withName("Max Mustermann")
+ .build())
+ .withSubject(subject)
+ .withPlainText(plainText)
+ .buildEmail());
+ }
+ catch(final Exception ex)
+ {
+ throw new IllegalStateException("Mailer failed", ex);
+ }
+
+ final Message message = new MessageApi(this.mailpitInfra.apiClient()).getMessageParams("latest");
+ assertNotNull(message);
+ assertAll(
+ () -> assertEquals(toAddress, message.getTo().getFirst().getAddress()),
+ () -> assertEquals(subject, message.getSubject()),
+ () -> assertEquals(
+ plainText,
+ Arrays.stream(message.getText().split("\n"))
+ .map(String::trim)
+ .findFirst()
+ .orElse(null))
+ );
+ }
+
+ @AfterEach
+ void afterEach()
+ {
+ if(this.mailpitInfra != null)
+ {
+ this.mailpitInfra.stop();
+ this.mailpitInfra = null;
+ }
+ if(this.network != null)
+ {
+ this.network.close();
+ this.network = null;
+ }
+ }
+
+ @AfterAll
+ static void afterAll()
+ {
+ FACTORY.close();
+ }
+}
diff --git a/pom.xml b/pom.xml
index fdc2b45a..d4bf205d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,6 +25,7 @@
jacoco
jul-to-slf4j
junit-jupiter-api-support
+ mailpit
mockserver
oidc-server-mock
selenium
From 490b2a74a73256bea7d8afe4ec1dd0a1d5294daa Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 15:28:55 +0200
Subject: [PATCH 131/141] Add missing license header
---
.../software/xdev/mailpit/SimpleMailpitTest.java | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/mailpit/src/test/java/software/xdev/mailpit/SimpleMailpitTest.java b/mailpit/src/test/java/software/xdev/mailpit/SimpleMailpitTest.java
index 9752cb47..ff666022 100644
--- a/mailpit/src/test/java/software/xdev/mailpit/SimpleMailpitTest.java
+++ b/mailpit/src/test/java/software/xdev/mailpit/SimpleMailpitTest.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2025 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package software.xdev.mailpit;
import static org.junit.jupiter.api.Assertions.assertAll;
From c39a389f16bee755c3af078ddb912ea1d11084ed Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 15:29:51 +0200
Subject: [PATCH 132/141] Fix syntax
---
.github/workflows/run-mailpit-integration-tests.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/run-mailpit-integration-tests.yml b/.github/workflows/run-mailpit-integration-tests.yml
index 97e0a4d2..dbb22111 100644
--- a/.github/workflows/run-mailpit-integration-tests.yml
+++ b/.github/workflows/run-mailpit-integration-tests.yml
@@ -30,7 +30,7 @@ jobs:
uses: actions/setup-java@v5
with:
distribution: temurin
- java-version: ${{ matrix.java }}
+ java-version: 25
- name: Cache Maven
uses: actions/cache@v6
From 67cba02c46cbba70f9031a0ec5b04bf24abe5447 Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 15:32:17 +0200
Subject: [PATCH 133/141] Fix workflow exec
---
.github/actions/docker-image-cache/save/action.yml | 2 +-
mailpit/pom.xml | 6 ++++++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/.github/actions/docker-image-cache/save/action.yml b/.github/actions/docker-image-cache/save/action.yml
index 4d21716d..de54456d 100644
--- a/.github/actions/docker-image-cache/save/action.yml
+++ b/.github/actions/docker-image-cache/save/action.yml
@@ -34,7 +34,7 @@ runs:
image_ids_to_save=$(comm -23 <(echo "${image_ids_to_save[@]}") <(cat ~/.cache/docker-preinstalled/ids-${{ github.run_id }}.txt || echo ""))
fi
echo && echo "Saving image ids" && echo $image_ids_to_save
- docker save $image_ids_to_save -o ~/.cache/docker/images.tar
+ docker save $image_ids_to_save -o ~/.cache/docker/images.tar || true
ls -lha ~/.cache/docker
if [ "$has_preinstalled_ids" = true ]; then
diff --git a/mailpit/pom.xml b/mailpit/pom.xml
index 8fd3b4cf..180b2c1b 100644
--- a/mailpit/pom.xml
+++ b/mailpit/pom.xml
@@ -187,6 +187,12 @@
+
+ run-it
+
+ false
+
+
ignore-service-loading
From 1b945abbf90dcd82ef7653f218ea88522fb53570 Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 15:37:04 +0200
Subject: [PATCH 134/141] Add proper logging for tests
---
mailpit/pom.xml | 15 +++++++++---
mailpit/src/test/resources/log4j2-test.xml | 28 ++++++++++++++++++++++
2 files changed, 40 insertions(+), 3 deletions(-)
create mode 100644 mailpit/src/test/resources/log4j2-test.xml
diff --git a/mailpit/pom.xml b/mailpit/pom.xml
index 180b2c1b..1edbaf10 100644
--- a/mailpit/pom.xml
+++ b/mailpit/pom.xml
@@ -50,6 +50,8 @@
true
+
+ 2.26.1
@@ -72,10 +74,17 @@
6.1.2
test
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j.version}
+ test
+
- org.slf4j
- slf4j-simple
- 2.0.18
+ org.apache.logging.log4j
+ log4j-slf4j2-impl
+ ${log4j.version}
test
diff --git a/mailpit/src/test/resources/log4j2-test.xml b/mailpit/src/test/resources/log4j2-test.xml
new file mode 100644
index 00000000..3b76bc01
--- /dev/null
+++ b/mailpit/src/test/resources/log4j2-test.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+ %d{HH:mm:ss} %-5p [%t] [%-25.25c] %m %n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From e239d144ff1838b8eb390ecb3755eea848b9d9c5 Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 15:39:31 +0200
Subject: [PATCH 135/141] Skip license update in tests
---
.github/workflows/run-integration-tests.yml | 2 ++
.github/workflows/run-mailpit-integration-tests.yml | 3 ++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 6eb64a67..a458c8e3 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -81,6 +81,7 @@ jobs:
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
-T2C \
-P run-it \
+ -Dlicense.skip \
${{ matrix.pre-start && '-Dtci.infra-pre-start.enabled=1 ' || '' }} \
${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }}
env:
@@ -162,6 +163,7 @@ jobs:
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
-T2C \
-P run-it${{ matrix.jacoco && ',jacoco' || '' }} \
+ -Dlicense.skip \
-Dtci.image-build.delete-on-exit=true \
-Dtci.image-build.cache-from="type=gha,scope=§image" \
-Dtci.image-build.cache-to="type=gha,mode=max,scope=§image" \
diff --git a/.github/workflows/run-mailpit-integration-tests.yml b/.github/workflows/run-mailpit-integration-tests.yml
index dbb22111..7278bcc1 100644
--- a/.github/workflows/run-mailpit-integration-tests.yml
+++ b/.github/workflows/run-mailpit-integration-tests.yml
@@ -55,7 +55,8 @@ jobs:
./mvnw -B test \
-pl "mailpit" -am \
-T2C \
- -P run-it
+ -P run-it \
+ -Dlicense.skip
- name: Show images
run: docker image ls
From 85bc970f38ce6c688d26c9fc899b679d7586700e Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 15:45:58 +0200
Subject: [PATCH 136/141] Do not log shutdown on warn
---
.../xdev/tci/factory/prestart/PreStartableTCIFactory.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java b/base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java
index 0b1ed959..b9329f28 100644
--- a/base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java
+++ b/base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java
@@ -484,7 +484,7 @@ protected boolean isPreStartingDisabled()
@Override
public void close()
{
- this.log().warn("[{}] Shutting down", this.name);
+ this.log().info("[{}] Shutting down", this.name);
if(!this.isPreStartingDisabled())
{
GlobalPreStartCoordinator.instance().unregister(this);
From 729806c3601ea56f3d770bbe411e1cab62f1d714 Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 15:53:04 +0200
Subject: [PATCH 137/141] Reference issue
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4bf5df13..a84d54bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,7 @@
* This module utilizes v4 of [testcontainers-advanced-imagebuilder](https://github.com/xdev-software/testcontainers-advanced-imagebuilder)
* It contains some shortcuts that help with common configuration e.g. related to caching (see below)
* Can be configured using environment variables or system properties (see corresponding configuration section for details)
-* Added `mailpit` module
+* Added `mailpit` module #455
* Added dedicated composite actions for caching
* These can be found in `.github/actions/docker-image-cache`
* Currently 3 actions exist:
From f340646eafe6809a5c801f63b821e4d0e3a69a94 Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 15:56:33 +0200
Subject: [PATCH 138/141] Log reported java AVX level
---
.github/workflows/run-integration-tests.yml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index a458c8e3..2bd32b26 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -153,11 +153,13 @@ jobs:
- name: Test
run: |
- # Report CPU
+ # Report CPU details
cat /proc/cpuinfo | grep 'name' | uniq
+ cat /proc/cpuinfo | grep 'flags' | uniq
# Differentiate cache between AVX microcode used by Java (until https://github.com/xdev-software/tci/issues/688 is fixed)
java_useavx=$(java -XX:+PrintFlagsFinal -version 2> /dev/null | awk '/UseAVX/ {print $4}')
+ echo "Java UseAVX: ${java_useavx}"
./mvnw -B test \
-pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \
From 676de0c3e8ac3d364f084fc3be994ace6f64dda3 Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 16:02:18 +0200
Subject: [PATCH 139/141] Add dedicated job for CPU details
Because when the job is running the "Test" step will get cut off
---
.github/workflows/run-integration-tests.yml | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 2bd32b26..e37d1e97 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -151,12 +151,13 @@ jobs:
id: docker-image-cache-setup-buildx
uses: ./.github/actions/docker-image-cache/setup-buildx
- - name: Test
+ - name: Report CPU details
run: |
- # Report CPU details
cat /proc/cpuinfo | grep 'name' | uniq
cat /proc/cpuinfo | grep 'flags' | uniq
+ - name: Test
+ run: |
# Differentiate cache between AVX microcode used by Java (until https://github.com/xdev-software/tci/issues/688 is fixed)
java_useavx=$(java -XX:+PrintFlagsFinal -version 2> /dev/null | awk '/UseAVX/ {print $4}')
echo "Java UseAVX: ${java_useavx}"
From 14febbd50a80060abe316f27a384289dd71f2276 Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 16:09:45 +0200
Subject: [PATCH 140/141] Correctly grep when Intel CPU is present
---
.github/workflows/run-integration-tests.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index e37d1e97..2f5724a5 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -153,8 +153,8 @@ jobs:
- name: Report CPU details
run: |
- cat /proc/cpuinfo | grep 'name' | uniq
- cat /proc/cpuinfo | grep 'flags' | uniq
+ cat /proc/cpuinfo | grep -P '^name' | uniq
+ cat /proc/cpuinfo | grep -P '^flags' | uniq
- name: Test
run: |
From cb0138d10397e36ab875ef8ec0668c5f660582e6 Mon Sep 17 00:00:00 2001
From: AB
Date: Wed, 15 Jul 2026 16:18:49 +0200
Subject: [PATCH 141/141] Update run-integration-tests.yml
---
.github/workflows/run-integration-tests.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml
index 2f5724a5..4d8deaa7 100644
--- a/.github/workflows/run-integration-tests.yml
+++ b/.github/workflows/run-integration-tests.yml
@@ -153,7 +153,7 @@ jobs:
- name: Report CPU details
run: |
- cat /proc/cpuinfo | grep -P '^name' | uniq
+ cat /proc/cpuinfo | grep -P '^model name' | uniq
cat /proc/cpuinfo | grep -P '^flags' | uniq
- name: Test