This is an automated email from the ASF dual-hosted git repository.

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new b52727eaaa NIFI-15432 Add system tests to code coverage workflow 
(#10735)
b52727eaaa is described below

commit b52727eaaa2cc4f54ebfaaf1ce25b57c48389c6e
Author: Pierre Villard <[email protected]>
AuthorDate: Wed Jan 21 03:06:37 2026 +0100

    NIFI-15432 Add system tests to code coverage workflow (#10735)
    
    - Add system-tests job to collect coverage from spawned NiFi instances
    - Configure JaCoCo agent injection via JACOCO_AGENT_PATH environment 
variable
    - Modify SpawnedStandaloneNiFiInstanceFactory to configure JaCoCo agent in 
bootstrap.conf
    - Collect and merge jacoco.exec files from system tests and stateless tests
    - Convert merged coverage data to XML format for Codecov upload
    - Dynamically extract module list from nifi-code-coverage/pom.xml
    - Add nifi-registry-integration-tests profile to integration tests job
    - Add jacoco.version property to root pom.xml for workflow compatibility
    
    Signed-off-by: David Handermann <[email protected]>
---
 .github/scripts/collect-system-tests-coverage.sh   | 95 ++++++++++++++++++++++
 .github/workflows/code-coverage.yml                | 86 +++++++++++++++++++-
 .../SpawnedStandaloneNiFiInstanceFactory.java      | 68 +++++++++++++++-
 pom.xml                                            |  3 +-
 4 files changed, 248 insertions(+), 4 deletions(-)

diff --git a/.github/scripts/collect-system-tests-coverage.sh 
b/.github/scripts/collect-system-tests-coverage.sh
new file mode 100755
index 0000000000..ad02cdfba0
--- /dev/null
+++ b/.github/scripts/collect-system-tests-coverage.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+# Collects JaCoCo coverage files from system tests, merges them, and generates 
an XML report.
+# This script is used by the code-coverage GitHub Actions workflow.
+
+set -e
+
+# Collect all jacoco.exec files from spawned NiFi instance directories
+EXEC_FILES=""
+for dir in nifi-system-tests/nifi-system-test-suite/target/*/coverage; do
+  if [ -f "$dir/jacoco.exec" ]; then
+    EXEC_FILES="$EXEC_FILES $dir/jacoco.exec"
+    echo "Found: $dir/jacoco.exec"
+  fi
+done
+
+# Also collect jacoco-it.exec from stateless tests (runs in-process, captured 
by JaCoCo agent)
+if [ -f 
"nifi-system-tests/nifi-stateless-system-test-suite/target/jacoco-it.exec" ]; 
then
+  EXEC_FILES="$EXEC_FILES 
nifi-system-tests/nifi-stateless-system-test-suite/target/jacoco-it.exec"
+  echo "Found: 
nifi-system-tests/nifi-stateless-system-test-suite/target/jacoco-it.exec"
+fi
+
+if [ -z "$EXEC_FILES" ]; then
+  echo "No coverage files found"
+  exit 0
+fi
+
+# Download JaCoCo CLI
+JACOCO_VERSION=$(./mvnw help:evaluate -Dexpression=jacoco.version -q 
-DforceStdout 2>/dev/null)
+./mvnw dependency:copy 
-Dartifact=org.jacoco:org.jacoco.cli:${JACOCO_VERSION}:jar:nodeps 
-DoutputDirectory=target/jacoco-cli
+
+# Merge all exec files into one
+mkdir -p target/system-tests-coverage
+echo "Merging coverage files..."
+java -jar target/jacoco-cli/org.jacoco.cli-${JACOCO_VERSION}-nodeps.jar merge \
+  $EXEC_FILES \
+  --destfile target/system-tests-coverage/jacoco-merged.exec
+
+# Build classfiles by merging all module classes into a single directory
+# This avoids duplicate class errors that occur when same class exists in 
multiple modules
+mkdir -p target/merged-classes
+
+# Extract module names from nifi-code-coverage/pom.xml (nifi-*, minifi-*, c2-* 
only)
+# Exclude nifi-mock as it has class files that JaCoCo cannot analyze
+MODULES=$(grep '<artifactId>' nifi-code-coverage/pom.xml | sed 
's/.*<artifactId>//' | sed 's/<.*//' | grep -E '^(nifi-|minifi-|c2-)' | grep -v 
'nifi-code-coverage' | grep -v 'nifi-mock' | sort -u)
+
+SOURCEFILES_ARGS=""
+MODULE_COUNT=0
+
+for module in $MODULES; do
+  classdir=$(find . -type d -path "*/${module}/target/classes" 2>/dev/null | 
head -1)
+  if [ -n "$classdir" ] && [ -d "$classdir" ]; then
+    # Copy classes to merged directory (later copies overwrite, deduplicating)
+    cp -r "$classdir"/* target/merged-classes/ 2>/dev/null || true
+    MODULE_COUNT=$((MODULE_COUNT + 1))
+    # Also add corresponding source directory
+    srcdir=$(echo "$classdir" | sed 's|/target/classes|/src/main/java|')
+    if [ -d "$srcdir" ]; then
+      SOURCEFILES_ARGS="$SOURCEFILES_ARGS --sourcefiles $srcdir"
+    fi
+  fi
+done
+
+echo "Merged classes from $MODULE_COUNT modules"
+
+echo "Generating XML report from merged classes..."
+java -jar target/jacoco-cli/org.jacoco.cli-${JACOCO_VERSION}-nodeps.jar report 
\
+  target/system-tests-coverage/jacoco-merged.exec \
+  --classfiles target/merged-classes \
+  $SOURCEFILES_ARGS \
+  --xml target/system-tests-coverage/jacoco.xml 2>&1 || true
+
+# Check if report was generated
+if [ -f "target/system-tests-coverage/jacoco.xml" ]; then
+  echo "=== Generated XML report ==="
+  ls -la target/system-tests-coverage/jacoco.xml
+  head -20 target/system-tests-coverage/jacoco.xml
+else
+  echo "WARNING: No XML report was generated"
+fi
+
diff --git a/.github/workflows/code-coverage.yml 
b/.github/workflows/code-coverage.yml
index a31cb4c581..da851caff8 100644
--- a/.github/workflows/code-coverage.yml
+++ b/.github/workflows/code-coverage.yml
@@ -58,7 +58,6 @@ jobs:
         run: >
           ./mvnw --fail-fast --no-snapshot-updates --no-transfer-progress 
--show-version
           --threads 1C
-          -D include-python-integration-tests=true
           -P report-code-coverage
           jacoco:prepare-agent
           verify
@@ -69,6 +68,7 @@ jobs:
           files: ./nifi-code-coverage/target/site/jacoco-aggregate/jacoco.xml
           flags: unit-tests
           token: ${{ secrets.CODECOV_TOKEN }}
+          disable_search: true
 
   integration-tests:
     timeout-minutes: 120
@@ -79,6 +79,7 @@ jobs:
         verify
         -P skip-unit-tests
         -P integration-tests
+        -P nifi-registry-integration-tests
         -P report-code-coverage
       MAVEN_BUILD_EXCLUDE_PROJECTS: >-
         -pl -:minifi-assembly
@@ -122,3 +123,86 @@ jobs:
           files: ./nifi-code-coverage/target/site/jacoco-aggregate/jacoco.xml
           flags: integration-tests
           token: ${{ secrets.CODECOV_TOKEN }}
+          disable_search: true
+
+  system-tests:
+    timeout-minutes: 120
+    runs-on: ubuntu-24.04
+    name: Run System Tests
+    env:
+      MAVEN_BUILD_ARGUMENTS: >-
+        install
+        -D skipTests
+        -pl nifi-code-coverage
+        -am
+      MAVEN_RUN_ARGUMENTS: >-
+        verify
+        -P integration-tests
+        -P report-code-coverage
+        -D include-python-integration-tests=true
+      MAVEN_PROJECTS: >-
+        -pl :nifi-python-framework
+        -pl :nifi-python-extension-api
+        -pl :nifi-python-test-extensions
+        -pl :nifi-py4j-integration-tests
+        -pl nifi-system-tests/nifi-system-test-suite
+        -pl nifi-system-tests/nifi-stateless-system-test-suite
+    steps:
+      - name: Checkout Code
+        uses: actions/checkout@v6
+      - name: Set up Java 21
+        uses: actions/setup-java@v5
+        with:
+          distribution: 'zulu'
+          java-version: 21
+          cache: 'maven'
+      - name: Set up Python 3.10
+        uses: actions/setup-python@v6
+        with:
+          python-version: '3.10'
+      - name: Set up Astral uv
+        uses: astral-sh/setup-uv@v5
+        with:
+          python-version: '3.10'
+          enable-cache: false
+      - name: Build Dependencies
+        env:
+          MAVEN_OPTS: >-
+            ${{ env.DEFAULT_MAVEN_OPTS }}
+        run: >
+          ./mvnw
+          -V
+          -nsu
+          -ntp
+          -ff
+          ${{ env.MAVEN_BUILD_ARGUMENTS }}
+          ${{ env.MAVEN_PROJECTS }}
+      - name: Download JaCoCo Agent
+        run: |
+          JACOCO_VERSION=$(./mvnw help:evaluate -Dexpression=jacoco.version -q 
-DforceStdout 2>/dev/null)
+          ./mvnw dependency:copy 
-Dartifact=org.jacoco:org.jacoco.agent:${JACOCO_VERSION}:jar:runtime 
-DoutputDirectory=target/jacoco-agent
+          echo 
"JACOCO_AGENT_PATH=$(pwd)/target/jacoco-agent/org.jacoco.agent-${JACOCO_VERSION}-runtime.jar"
 >> $GITHUB_ENV
+      - name: Run System Tests with Coverage
+        env:
+          MAVEN_OPTS: >-
+            ${{ env.DEFAULT_MAVEN_OPTS }}
+          JACOCO_AGENT_PATH: ${{ env.JACOCO_AGENT_PATH }}
+        run: >
+          ./mvnw
+          -V
+          -nsu
+          -ntp
+          -ff
+          ${{ env.MAVEN_RUN_ARGUMENTS }}
+          ${{ env.MAVEN_PROJECTS }}
+      - name: Collect and Convert Coverage Files
+        if: always()
+        run: .github/scripts/collect-system-tests-coverage.sh
+      - name: Upload Coverage to Codecov
+        uses: codecov/codecov-action@v5
+        if: github.repository_owner == 'apache'
+        with:
+          files: target/system-tests-coverage/jacoco.xml
+          flags: system-tests
+          token: ${{ secrets.CODECOV_TOKEN }}
+          disable_search: true
diff --git 
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/SpawnedStandaloneNiFiInstanceFactory.java
 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/SpawnedStandaloneNiFiInstanceFactory.java
index bc3c17887f..5474a54a46 100644
--- 
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/SpawnedStandaloneNiFiInstanceFactory.java
+++ 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/SpawnedStandaloneNiFiInstanceFactory.java
@@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -51,6 +52,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class SpawnedStandaloneNiFiInstanceFactory implements 
NiFiInstanceFactory {
     private static final Logger logger = 
LoggerFactory.getLogger(SpawnedStandaloneNiFiInstanceFactory.class);
+
+    /**
+     * Environment variable name for JaCoCo agent JAR path.
+     * When set, enables code coverage collection for spawned NiFi instances.
+     */
+    private static final String JACOCO_AGENT_PATH_ENV = "JACOCO_AGENT_PATH";
+
     private final InstanceConfiguration instanceConfig;
 
     public SpawnedStandaloneNiFiInstanceFactory(final InstanceConfiguration 
instanceConfig) {
@@ -158,6 +166,9 @@ public class SpawnedStandaloneNiFiInstanceFactory 
implements NiFiInstanceFactory
             copyContents(bootstrapConfigFile.getParentFile(), destinationConf);
             bootstrapConfigFile = new File(destinationConf, 
bootstrapConfigFile.getName());
 
+            // Configure JaCoCo agent for code coverage if environment 
variable is set
+            configureJacocoAgent(bootstrapConfigFile);
+
             final File destinationLib = new File(instanceDirectory, "lib");
             copyContents(new File("target/nifi-lib-assembly/lib"), 
destinationLib);
 
@@ -241,6 +252,56 @@ public class SpawnedStandaloneNiFiInstanceFactory 
implements NiFiInstanceFactory
             }
         }
 
+        /**
+         * Configures JaCoCo agent for code coverage collection if 
JACOCO_AGENT_PATH environment variable is set.
+         * Appends JaCoCo agent JVM argument to the bootstrap configuration 
file with a unique destfile
+         * based on the instance directory name.
+         *
+         * @param bootstrapConfig The bootstrap configuration file to modify
+         * @throws IOException if unable to write to the bootstrap 
configuration file
+         */
+        private void configureJacocoAgent(final File bootstrapConfig) throws 
IOException {
+            final String jacocoAgentPath = 
System.getenv(JACOCO_AGENT_PATH_ENV);
+            if (jacocoAgentPath == null || jacocoAgentPath.isBlank()) {
+                return;
+            }
+
+            final File jacocoAgentFile = new File(jacocoAgentPath);
+            if (!jacocoAgentFile.exists()) {
+                logger.warn("JaCoCo agent path specified but file not found: 
{}", jacocoAgentPath);
+                return;
+            }
+
+            // Create coverage output directory in instance directory
+            final File coverageDir = new File(instanceDirectory, "coverage");
+            if (!coverageDir.exists()) {
+                assertTrue(coverageDir.mkdirs());
+            }
+
+            // Use instance directory name to create unique coverage file
+            final String instanceName = instanceDirectory.getName();
+            final File coverageFile = new File(coverageDir, "jacoco.exec");
+
+            // Build JaCoCo agent argument with options:
+            // - destfile: unique file per instance to avoid conflicts
+            // - output=file: write coverage on JVM exit
+            // - append=true: append to existing coverage data if file exists
+            final String jacocoArg = String.format(
+                    "-javaagent:%s=destfile=%s,output=file,append=true",
+                    jacocoAgentFile.getAbsolutePath(),
+                    coverageFile.getAbsolutePath()
+            );
+
+            // Append JaCoCo agent argument to bootstrap.conf
+            try (final FileWriter writer = new FileWriter(bootstrapConfig, 
true)) {
+                writer.write(System.lineSeparator());
+                writer.write("# JaCoCo agent for code coverage 
(auto-configured)" + System.lineSeparator());
+                writer.write("java.arg.jacoco=" + jacocoArg + 
System.lineSeparator());
+            }
+
+            logger.info("Configured JaCoCo agent for NiFi instance [{}]: 
destfile={}", instanceName, coverageFile.getAbsolutePath());
+        }
+
         @Override
         public boolean isAccessible() {
             if (process == null) {
@@ -377,9 +438,12 @@ public class SpawnedStandaloneNiFiInstanceFactory 
implements NiFiInstanceFactory
 
         @Override
         public void quarantineTroubleshootingInfo(final File destinationDir, 
final Throwable cause) throws IOException {
-            final String[] dirsToCopy = new String[] {"conf", "logs"};
+            final String[] dirsToCopy = new String[] {"conf", "logs", 
"coverage"};
             for (final String dirToCopy : dirsToCopy) {
-                copyContents(new File(getInstanceDirectory(), dirToCopy), new 
File(destinationDir, dirToCopy));
+                final File sourceDir = new File(getInstanceDirectory(), 
dirToCopy);
+                if (sourceDir.exists()) {
+                    copyContents(sourceDir, new File(destinationDir, 
dirToCopy));
+                }
             }
 
             if (process == null) {
diff --git a/pom.xml b/pom.xml
index 6200fa35c3..a237d4adf2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -206,6 +206,7 @@
         <pmd.version>7.20.0</pmd.version>
         <checkstyle.version>13.0.0</checkstyle.version>
         <testcontainers.version>2.0.3</testcontainers.version>
+        <jacoco.version>0.8.14</jacoco.version>
     </properties>
     <dependencyManagement>
         <dependencies>
@@ -821,7 +822,7 @@
                 <plugin>
                     <groupId>org.jacoco</groupId>
                     <artifactId>jacoco-maven-plugin</artifactId>
-                    <version>0.8.14</version>
+                    <version>${jacoco.version}</version>
                 </plugin>
                 <plugin>
                     <groupId>io.swagger.core.v3</groupId>

Reply via email to