diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 206429943e..963863a461 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -160,11 +160,29 @@ jobs: if: runner.os == 'Linux' run: mvn test -P testcontainers + # The full IT suite is split ("sharded") into three disjoint slices selected by JUnit 5 tags, so + # the wall-clock time of a run is the slowest shard (~50 min) instead of the whole suite (~2h+): + # api - every HTTP-level IT (untagged, i.e. everything not tagged "ui") + # ui - the browser-driven ITs except the sample-project and camel families + # samples - the sample-project clone/publish ITs ("sample") + the camel route ITs ("camel") + # The shards partition the suite: api + ui + samples = the complete IT set. A new untagged IT + # lands in "api"; a new UI IT (extends UserInterfaceIntegrationTest) lands in "ui" unless it is + # explicitly tagged "sample" or "camel". integration-tests-h2: runs-on: ubuntu-latest - # A green full IT run takes ~1h30m; a heap-exhausted JVM used to thrash for 4h+ before anyone + strategy: + fail-fast: false + matrix: + shard: + - name: api + groups: "!ui" + - name: ui + groups: "ui & !sample & !camel" + - name: samples + groups: "sample | camel" + # A green shard takes ~50 min; a heap-exhausted JVM used to thrash for 4h+ before anyone # noticed. Cap the job well above the healthy time so hangs fail fast instead of burning runners. - timeout-minutes: 150 + timeout-minutes: 90 steps: - uses: actions/checkout@v4 with: @@ -201,14 +219,14 @@ jobs: run: ttyd --version - name: Integration tests - run: mvn clean install -P integration-tests + run: mvn clean install -P integration-tests -Dit.groups='${{ matrix.shard.groups }}' - name: Generate a random artifact name if: always() id: generate_name run: | TIMESTAMP=$(date +"%Y%m%d_%H%M%S") - echo "ARTIFACT_NAME=selenide-screenshots-${TIMESTAMP}.zip" >> $GITHUB_ENV + echo "ARTIFACT_NAME=selenide-screenshots-h2-${{ matrix.shard.name }}-${TIMESTAMP}.zip" >> $GITHUB_ENV - name: Upload selenide screenshots uses: actions/upload-artifact@v4 @@ -220,8 +238,18 @@ jobs: integration-tests-postgresql: runs-on: ubuntu-latest - # Same rationale as integration-tests-h2: green run ~1h30m, cap runaway JVMs. - timeout-minutes: 150 + # Same sharding + timeout rationale as integration-tests-h2. + strategy: + fail-fast: false + matrix: + shard: + - name: api + groups: "!ui" + - name: ui + groups: "ui & !sample & !camel" + - name: samples + groups: "sample | camel" + timeout-minutes: 90 env: POSTGRES_DB: testdb POSTGRES_USER: testuser @@ -276,7 +304,7 @@ jobs: run: ttyd --version - name: Integration tests - run: mvn clean install -P integration-tests + run: mvn clean install -P integration-tests -Dit.groups='${{ matrix.shard.groups }}' env: DIRIGIBLE_DATASOURCE_DEFAULT_DRIVER: org.postgresql.Driver DIRIGIBLE_DATASOURCE_DEFAULT_URL: jdbc:postgresql://localhost:5432/${{ env.POSTGRES_DB }} @@ -288,7 +316,7 @@ jobs: id: generate_name run: | TIMESTAMP=$(date +"%Y%m%d_%H%M%S") - echo "ARTIFACT_NAME=selenide-screenshots-${TIMESTAMP}.zip" >> $GITHUB_ENV + echo "ARTIFACT_NAME=selenide-screenshots-postgresql-${{ matrix.shard.name }}-${TIMESTAMP}.zip" >> $GITHUB_ENV - name: Upload selenide screenshots uses: actions/upload-artifact@v4 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7e99455661..a34f8cbdee 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,9 +1,11 @@ name: Nightly Integration Tests # The full Selenide UI integration suite (everything, incl. the sample-project clone/publish tests) -# on both H2 and PostgreSQL. It is heavy (~1.5h per DB), so it does not run on every PR - PRs run the -# fast "smoke-tests" job (see pull-request.yml). Runs on a nightly schedule and on demand; the same -# full suite also runs on every push to master (see build.yml). +# on both H2 and PostgreSQL. It is heavy (~2h of test time per DB), so it does not run on every PR - +# PRs run the fast "smoke-tests" job (see pull-request.yml). Runs on a nightly schedule and on +# demand; the same full suite also runs on every push to master (see build.yml). Like build.yml, the +# suite is sharded into three tag-selected slices (api / ui / samples) that run in parallel, so the +# wall-clock time is the slowest shard (~50 min), not the whole suite. on: schedule: - cron: '0 2 * * *' @@ -16,6 +18,17 @@ concurrency: jobs: integration-tests-h2: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shard: + - name: api + groups: "!ui" + - name: ui + groups: "ui & !sample & !camel" + - name: samples + groups: "sample | camel" + timeout-minutes: 90 steps: - uses: actions/checkout@v4 with: @@ -52,14 +65,14 @@ jobs: run: ttyd --version - name: Integration tests - run: mvn clean install -P integration-tests + run: mvn clean install -P integration-tests -Dit.groups='${{ matrix.shard.groups }}' - name: Generate a random artifact name if: always() id: generate_name run: | TIMESTAMP=$(date +"%Y%m%d_%H%M%S") - echo "ARTIFACT_NAME=selenide-screenshots-h2-${TIMESTAMP}.zip" >> $GITHUB_ENV + echo "ARTIFACT_NAME=selenide-screenshots-h2-${{ matrix.shard.name }}-${TIMESTAMP}.zip" >> $GITHUB_ENV - name: Upload selenide screenshots uses: actions/upload-artifact@v4 @@ -71,6 +84,17 @@ jobs: integration-tests-postgresql: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shard: + - name: api + groups: "!ui" + - name: ui + groups: "ui & !sample & !camel" + - name: samples + groups: "sample | camel" + timeout-minutes: 90 env: POSTGRES_DB: testdb POSTGRES_USER: testuser @@ -125,7 +149,7 @@ jobs: run: ttyd --version - name: Integration tests - run: mvn clean install -P integration-tests + run: mvn clean install -P integration-tests -Dit.groups='${{ matrix.shard.groups }}' env: DIRIGIBLE_DATASOURCE_DEFAULT_DRIVER: org.postgresql.Driver DIRIGIBLE_DATASOURCE_DEFAULT_URL: jdbc:postgresql://localhost:5432/${{ env.POSTGRES_DB }} @@ -137,7 +161,7 @@ jobs: id: generate_name run: | TIMESTAMP=$(date +"%Y%m%d_%H%M%S") - echo "ARTIFACT_NAME=selenide-screenshots-postgresql-${TIMESTAMP}.zip" >> $GITHUB_ENV + echo "ARTIFACT_NAME=selenide-screenshots-postgresql-${{ matrix.shard.name }}-${TIMESTAMP}.zip" >> $GITHUB_ENV - name: Upload selenide screenshots uses: actions/upload-artifact@v4 diff --git a/CLAUDE.md b/CLAUDE.md index d8545f7b45..61506ba870 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -437,7 +437,7 @@ Treat `modules/commons/commons-config/src/main/java/org/eclipse/dirigible/common - `code-style`: `mvn -T 1C formatter:validate` - `tests` (ubuntu + windows matrix): `mvn clean install -P unit-tests` -- `integration-tests-h2` / `-postgresql`: the **full** Selenide IT suite, `mvn clean install -P integration-tests` with the matching `DIRIGIBLE_DATASOURCE_DEFAULT_*` env vars (MSSQL is no longer a CI leg — removed in #6150) +- `integration-tests-h2` / `-postgresql`: the **full** Selenide IT suite, `mvn clean install -P integration-tests` with the matching `DIRIGIBLE_DATASOURCE_DEFAULT_*` env vars (MSSQL is no longer a CI leg — removed in #6150). Each DB leg is **sharded into three parallel matrix jobs** selected by tag expression — `api` (`!ui`), `ui` (`ui & !sample & !camel`), `samples` (`sample | camel`) — so the run's wall clock is the slowest shard (~50 min), not the whole ~2h suite. The shards partition the suite; keep them disjoint and complete when adding tags. - `build-deploy`: `mvn clean install -P quick-build` then Docker buildx multi-arch image push to `dirigiblelabs/dirigible` ### PR gate vs full suite (smoke / nightly split) @@ -451,5 +451,6 @@ The full Selenide UI suite takes ~1.5h per DB, so it does **not** run on every P - Every browser-driven IT is `@Tag("ui")` - inherited from the `UserInterfaceIntegrationTest` base (and thus by `SampleProjectRepositoryIT` and all sample-project ITs). Do not tag these individually. - HTTP-level ITs (`extends IntegrationTest` directly) carry no tag, so they are always in the smoke set. - To force a specific UI IT to run on every PR, add `@Tag("smoke")` to that class (keep the list small - smoke must stay fast). +- Shard-routing tags: `@Tag("sample")` sits on the `SampleProjectRepositoryIT` base (inherited by every sample-project IT); `@Tag("camel")` sits on each IT in `ui/tests/camel` (their `PredefinedProjectIT` base is shared with non-camel tests, so the base cannot carry it — tag new camel ITs individually). These route classes into the `samples` CI shard; everything else UI stays in the `ui` shard. `codeql.yml`, `release.yml` cover CodeQL and Maven Central release respectively. diff --git a/tests/tests-integrations/pom.xml b/tests/tests-integrations/pom.xml index 1730d817d9..eec095eb50 100644 --- a/tests/tests-integrations/pom.xml +++ b/tests/tests-integrations/pom.xml @@ -116,7 +116,9 @@ maven-failsafe-plugin diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelAsyncStepIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelAsyncStepIT.java index 1f9dbdb506..a5b4ef7270 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelAsyncStepIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelAsyncStepIT.java @@ -11,8 +11,10 @@ import org.eclipse.dirigible.tests.base.PredefinedProjectIT; import org.eclipse.dirigible.tests.base.TestProject; +import org.junit.jupiter.api.Tag; import org.springframework.beans.factory.annotation.Autowired; +@Tag("camel") public class CamelAsyncStepIT extends PredefinedProjectIT { @Autowired diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelCronRouteStarterTemplateIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelCronRouteStarterTemplateIT.java index bbd310dcc9..0ac027d26c 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelCronRouteStarterTemplateIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelCronRouteStarterTemplateIT.java @@ -16,12 +16,14 @@ import org.eclipse.dirigible.tests.framework.logging.LogsAsserter; import org.eclipse.dirigible.tests.framework.util.SynchronizationUtil; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; +@Tag("camel") public class CamelCronRouteStarterTemplateIT extends UserInterfaceIntegrationTest { private static final String TEMPLATE_TITLE = "Cron Route Project Starter"; diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleJavaScriptComponentCronRouteIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleJavaScriptComponentCronRouteIT.java index 8ce5694c1c..c263154bfd 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleJavaScriptComponentCronRouteIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleJavaScriptComponentCronRouteIT.java @@ -11,8 +11,10 @@ import org.eclipse.dirigible.tests.base.PredefinedProjectIT; import org.eclipse.dirigible.tests.base.TestProject; +import org.junit.jupiter.api.Tag; import org.springframework.beans.factory.annotation.Autowired; +@Tag("camel") public class CamelDirigibleJavaScriptComponentCronRouteIT extends PredefinedProjectIT { @Autowired diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleJavaScriptComponentHttpRouteIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleJavaScriptComponentHttpRouteIT.java index bc0b504dfa..6494ea2e19 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleJavaScriptComponentHttpRouteIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleJavaScriptComponentHttpRouteIT.java @@ -11,8 +11,10 @@ import org.eclipse.dirigible.tests.base.PredefinedProjectIT; import org.eclipse.dirigible.tests.base.TestProject; +import org.junit.jupiter.api.Tag; import org.springframework.beans.factory.annotation.Autowired; +@Tag("camel") public class CamelDirigibleJavaScriptComponentHttpRouteIT extends PredefinedProjectIT { @Autowired diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleTwoStepsJSInvokerCronRouteIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleTwoStepsJSInvokerCronRouteIT.java index ac522b990b..9f59054ea8 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleTwoStepsJSInvokerCronRouteIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleTwoStepsJSInvokerCronRouteIT.java @@ -11,8 +11,10 @@ import org.eclipse.dirigible.tests.base.PredefinedProjectIT; import org.eclipse.dirigible.tests.base.TestProject; +import org.junit.jupiter.api.Tag; import org.springframework.beans.factory.annotation.Autowired; +@Tag("camel") public class CamelDirigibleTwoStepsJSInvokerCronRouteIT extends PredefinedProjectIT { @Autowired diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleTwoStepsJSInvokerHttpRouteIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleTwoStepsJSInvokerHttpRouteIT.java index f5c94131f6..a66484af6a 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleTwoStepsJSInvokerHttpRouteIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelDirigibleTwoStepsJSInvokerHttpRouteIT.java @@ -11,8 +11,10 @@ import org.eclipse.dirigible.tests.base.PredefinedProjectIT; import org.eclipse.dirigible.tests.base.TestProject; +import org.junit.jupiter.api.Tag; import org.springframework.beans.factory.annotation.Autowired; +@Tag("camel") public class CamelDirigibleTwoStepsJSInvokerHttpRouteIT extends PredefinedProjectIT { @Autowired diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelExtractTransformLoadJdbcIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelExtractTransformLoadJdbcIT.java index bfdf39368d..08797923bf 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelExtractTransformLoadJdbcIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelExtractTransformLoadJdbcIT.java @@ -11,8 +11,10 @@ import org.eclipse.dirigible.tests.base.PredefinedProjectIT; import org.eclipse.dirigible.tests.base.TestProject; +import org.junit.jupiter.api.Tag; import org.springframework.beans.factory.annotation.Autowired; +@Tag("camel") public class CamelExtractTransformLoadJdbcIT extends PredefinedProjectIT { @Autowired diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelExtractTransformLoadTypescriptIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelExtractTransformLoadTypescriptIT.java index fee5034361..3f8521d3f4 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelExtractTransformLoadTypescriptIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelExtractTransformLoadTypescriptIT.java @@ -11,8 +11,10 @@ import org.eclipse.dirigible.tests.base.PredefinedProjectIT; import org.eclipse.dirigible.tests.base.TestProject; +import org.junit.jupiter.api.Tag; import org.springframework.beans.factory.annotation.Autowired; +@Tag("camel") public class CamelExtractTransformLoadTypescriptIT extends PredefinedProjectIT { @Autowired diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelHttpRouteStarterTemplateIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelHttpRouteStarterTemplateIT.java index caf8628b4a..5e4b99ba57 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelHttpRouteStarterTemplateIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelHttpRouteStarterTemplateIT.java @@ -14,12 +14,14 @@ import org.eclipse.dirigible.tests.framework.ide.Workbench; import org.eclipse.dirigible.tests.framework.restassured.RestAssuredExecutor; import org.eclipse.dirigible.tests.framework.util.SynchronizationUtil; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.containsString; +@Tag("camel") public class CamelHttpRouteStarterTemplateIT extends UserInterfaceIntegrationTest { private static final String TEMPLATE_TITLE = "HTTP Route Project Starter"; diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelTransactionsCommitIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelTransactionsCommitIT.java index 36ec0bfefe..f2e0e0e0d4 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelTransactionsCommitIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelTransactionsCommitIT.java @@ -11,8 +11,10 @@ import org.eclipse.dirigible.tests.base.PredefinedProjectIT; import org.eclipse.dirigible.tests.base.TestProject; +import org.junit.jupiter.api.Tag; import org.springframework.beans.factory.annotation.Autowired; +@Tag("camel") public class CamelTransactionsCommitIT extends PredefinedProjectIT { @Autowired diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelTransactionsRollbackIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelTransactionsRollbackIT.java index 79d466f120..ae060cfaa8 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelTransactionsRollbackIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/camel/CamelTransactionsRollbackIT.java @@ -12,9 +12,11 @@ import org.eclipse.dirigible.tests.base.PredefinedProjectIT; import org.eclipse.dirigible.tests.base.TestProject; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; import org.springframework.beans.factory.annotation.Autowired; @Disabled("Disabled until transaction logic is implemented") +@Tag("camel") public class CamelTransactionsRollbackIT extends PredefinedProjectIT { @Autowired diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/sample/SampleProjectRepositoryIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/sample/SampleProjectRepositoryIT.java index ffaf310688..dc3e61ac0f 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/sample/SampleProjectRepositoryIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/sample/SampleProjectRepositoryIT.java @@ -16,9 +16,13 @@ import org.eclipse.dirigible.tests.framework.ide.Workbench; import org.eclipse.dirigible.tests.framework.restassured.RestAssuredExecutor; import org.eclipse.dirigible.tests.framework.util.SynchronizationUtil; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +// "sample" (on top of the inherited "ui") routes the whole sample-project family into its own CI +// shard - see the integration-tests matrix in .github/workflows/build.yml. +@Tag("sample") abstract class SampleProjectRepositoryIT extends UserInterfaceIntegrationTest { @Autowired