diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml new file mode 100644 index 00000000000..226080f2f01 --- /dev/null +++ b/.github/workflows/validation.yml @@ -0,0 +1,194 @@ +name: Validation + +on: + pull_request: + branches: ['main', '25.*'] + merge_group: + workflow_dispatch: + inputs: + components: + description: 'Space-separated component names (e.g. "grid combo-box"), empty for all' + required: false + default: '' + +permissions: + contents: read + checks: write + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.merge_group.head_ref || github.ref }} + cancel-in-progress: true + +env: + FORK_COUNT: 5 + +jobs: + validation: + name: Build and Test + runs-on: ubuntu-latest + timeout-minutes: 120 + services: + selenium: + image: selenium/standalone-chrome:latest + options: --shm-size=2g + ports: + - 4444:4444 + steps: + - name: Set Maven args + run: | + args="-ntp -B" + [ "${{ runner.debug }}" != "1" ] && args="$args -q" + echo "MAVEN_ARGS=$args" >> "$GITHUB_ENV" + + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha || github.sha }} + fetch-depth: ${{ github.event_name == 'merge_group' && 50 || 1 }} + + - name: Setup JDK 21 + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + + - name: Setup Node 20 + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Install TestBench license + if: env.TB_LICENSE != '' + env: + TB_LICENSE: ${{ secrets.TB_LICENSE }} + run: | + mkdir -p ~/.vaadin + user="${TB_LICENSE%%/*}" + key="${TB_LICENSE#*/}" + echo "{\"username\":\"${user}\",\"proKey\":\"${key}\"}" > ~/.vaadin/proKey + + - name: Show environment info + run: | + java -version + mvn -version + node --version + npm --version + + - name: Compile all sources + run: mvn clean test-compile -DskipFrontend $MAVEN_ARGS + + - name: Unit tests and install + run: | + mvn install -Drelease -T $FORK_COUNT $MAVEN_ARGS || { + echo "::warning::First attempt failed (Maven multi-thread race). Retrying..." + sleep 15 + mvn install -Drelease -T $FORK_COUNT $MAVEN_ARGS + } + + - name: Check for skip-ci + id: skip-ci + if: github.event_name == 'pull_request' || github.event_name == 'merge_group' + env: + GH_TOKEN: ${{ github.token }} + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + if [ "${{ github.event_name }}" = "merge_group" ]; then + echo "skip=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + if echo "$PR_TITLE" | grep -q '\[skip ci\]'; then + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + pr_number="${{ github.event.pull_request.number }}" + commits=$(gh api "repos/${{ github.repository }}/pulls/$pr_number/commits" --jq '.[].commit.message') + if echo "$commits" | grep -q '\[skip ci\]'; then + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "skip=false" >> "$GITHUB_OUTPUT" + + - name: Detect modified components + id: components + if: steps.skip-ci.outputs.skip != 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + elements="${{ github.event.inputs.components || '' }}" + changed_files="" + if [ -z "$elements" ] && [ "${{ github.event_name }}" = "pull_request" ]; then + pr_number="${{ github.event.pull_request.number }}" + changed_files=$(gh api "repos/${{ github.repository }}/pulls/$pr_number/files" --jq '.[].filename') + elif [ -z "$elements" ] && [ "${{ github.event_name }}" = "merge_group" ]; then + changed_files=$(git diff --name-only "${{ github.event.merge_group.base_sha }}" "${{ github.event.merge_group.head_sha }}") + fi + if [ -n "$changed_files" ]; then + modified=$(echo "$changed_files" \ + | grep 'vaadin.*flow-parent' \ + | sed 's,^vaadin-\(.*\)-flow-parent.*,\1,' \ + | sort -u) + nmods=$(echo "$modified" | wc -w | tr -d ' ') + all_files=$(echo "$changed_files" | sort -u | tr -d '[:space:]') + component_files=$(echo "$changed_files" | grep 'vaadin.*flow-parent' | sort -u | tr -d '[:space:]') + if [ "$nmods" -lt 5 ] && [ ${#all_files} -eq ${#component_files} ]; then + elements=$(echo $modified | tr '\n' ' ') + echo "Running partial build for: $elements" + fi + fi + echo "elements=$elements" >> "$GITHUB_OUTPUT" + + - name: npm install + if: steps.skip-ci.outputs.skip != 'true' + run: npm install --ignore-scripts --silent --quiet --no-progress + + - name: Merge ITs + if: steps.skip-ci.outputs.skip != 'true' + run: node scripts/mergeITs.js ${{ steps.components.outputs.elements }} + + - name: Pre-compile merged ITs + if: steps.skip-ci.outputs.skip != 'true' + run: cd integration-tests && mvn compile test-compile $MAVEN_ARGS -DskipUnitTests + + - name: WTR tests + if: steps.skip-ci.outputs.skip != 'true' + run: node scripts/wtr.js ${{ steps.components.outputs.elements }} + + - name: Run integration tests + if: steps.skip-ci.outputs.skip != 'true' + env: + TBLICENSE: ${{ secrets.TB_LICENSE }} + run: | + args="$MAVEN_ARGS" + [ -n "$TBLICENSE" ] && args="$args -Dvaadin.testbench.developer.license=$TBLICENSE" + args="$args -Dtest.use.hub=true -Dcom.vaadin.testbench.Parameters.hubHostname=localhost -Dcom.vaadin.testbench.Parameters.hubPort=4444" + args="$args -Dfailsafe.rerunFailingTestsCount=2 -Dmaven.test.redirectTestOutputToFile=true" + mode="-Dfailsafe.forkCount=$FORK_COUNT -Dcom.vaadin.testbench.Parameters.testsInParallel=1" + + mvn verify -Dvaadin.pnpm.enable -Drun-it -Drelease -Dvaadin.productionMode \ + -Dvaadin.force.production.build=true \ + $mode $args -pl integration-tests -DskipUnitTests + + - name: Publish unit test results + if: always() + ## Make sonar happy for external actions + uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 + with: + name: Unit Tests + path: '**/target/surefire-reports/TEST-*.xml' + reporter: java-junit + + - name: Publish integration test results + if: always() + uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 + with: + name: Integration Tests + path: 'integration-tests/target/failsafe-reports/TEST-*.xml' + reporter: java-junit + + - name: Upload error screenshots + if: failure() + uses: actions/upload-artifact@v5 + with: + name: error-screenshots + path: 'integration-tests/error-screenshots/' + retention-days: 5 diff --git a/scripts/build.sh b/scripts/build.sh index 5a364ea8983..f5a7a5b2b9e 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -157,6 +157,10 @@ cmd="node scripts/mergeITs.js "`echo $elements` tcLog "Merge IT modules - $cmd" $cmd || tcStatus 1 "Merging ITs failed" +## Pre-compile merged ITs to avoid incremental compilation issues +tcLog "Pre-compiling merged ITs" +(cd integration-tests && mvn compile test-compile $args -DskipUnitTests) || tcStatus 1 "Merged ITs compilation failed" + ## Compute variable to run tests [ -n "$TBLICENSE" ] && args="$args -Dvaadin.testbench.developer.license=$TBLICENSE" [ -n "$TBHUB" ] && args="$args -Dtest.use.hub=true -Dcom.vaadin.testbench.Parameters.hubHostname=$TBHUB" @@ -197,7 +201,7 @@ then else mode="-Dfailsafe.forkCount=$FORK_COUNT -Dcom.vaadin.testbench.Parameters.testsInParallel=$TESTS_IN_PARALLEL" ### Run IT's in merged module - cmd="mvn $verify -Drun-it -Drelease -Dvaadin.productionMode -Dfailsafe.rerunFailingTestsCount=2 $mode $args -pl integration-tests -DskipUnitTests" + cmd="mvn $verify -Drun-it -Drelease -Dvaadin.productionMode -Dvaadin.force.production.build=true -Dfailsafe.rerunFailingTestsCount=2 $mode $args -pl integration-tests -DskipUnitTests" tcLog "Running merged ITs - mvn $verify -B -Drun-it -Drelease -pl integration-tests ..." echo $cmd $cmd diff --git a/scripts/mergeITs.js b/scripts/mergeITs.js index c641a8077e1..f2f70fa626d 100755 --- a/scripts/mergeITs.js +++ b/scripts/mergeITs.js @@ -252,6 +252,20 @@ async function copySources() { } }); + // clean stale Flow-generated files from previous runs + ['package.json', 'package-lock.json', 'pnpm-lock.yaml', 'tsconfig.json', 'types.d.ts', 'vite.generated.ts', '.npmrc', '.pnpmfile.cjs'] + .forEach(f => { + const file = `${itFolder}/${f}`; + if (fs.existsSync(file)) { + console.log(`removing ${file}`); + fs.rmSync(file); + } + }); + + // Create empty package.json so that build-frontend does not + // perform aggressive cleanup that deletes flow-build-info.json + fs.writeFileSync(`${itFolder}/package.json`, '{}'); + modules.forEach(parent => { const id = parent.replace('-parent', ''); console.log(`Copying ${parent}/${id}-integration-tests`); diff --git a/scripts/run.js b/scripts/run.js index c3a427d1c34..534595dda68 100755 --- a/scripts/run.js +++ b/scripts/run.js @@ -98,9 +98,9 @@ function generateHistoryItems() { // === Maven Execution === function runMvn(module, modeKey, args, message, onOutput) { - const command = `mvn ${args.join(' ')}`; + const command = `mvn -B -ntp ${args.join(' ')}`; args = [...args, '-Dstyle.color=always']; - console.log(`\n${message}\n`); + console.log(`\n${message}\n${command}\n`); const mvn = spawn('mvn', args, { stdio: ['inherit', 'pipe', 'pipe'] }); diff --git a/scripts/wtr.js b/scripts/wtr.js index 122558cbb55..20e894d1f07 100755 --- a/scripts/wtr.js +++ b/scripts/wtr.js @@ -47,7 +47,7 @@ function runTests() { } // Install the IT module dependencies - execSync(`mvn -DskipTests flow:prepare-frontend flow:build-frontend`, { + execSync(`mvn -DskipTests -Dvaadin.force.production.build=true flow:prepare-frontend flow:build-frontend`, { cwd: itFolder, stdio: 'inherit' });