Skip to content

WIP: Allow ART to build with code coverage#2450

Open
jupierce wants to merge 6 commits intoopenshift-eng:mainfrom
jupierce:coverage_stream
Open

WIP: Allow ART to build with code coverage#2450
jupierce wants to merge 6 commits intoopenshift-eng:mainfrom
jupierce:coverage_stream

Conversation

@jupierce
Copy link
Contributor

@jupierce jupierce commented Feb 5, 2026

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 5, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign sosiouxme for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

Important

Review skipped

Auto reviews are limited based on label configuration.

🚫 Excluded labels (none allowed) (1)
  • do-not-merge/work-in-progress

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

When build_profiles.enable_go_cover is true, build metadata/envs receive GO_COMPLIANCE_COVER=1, util.inject_coverage_server is invoked to copy a Go coverage_server.go into discovered main packages, and ART Go wrapper scripts add coverage flags and may append the coverage server source during go build/install. New JSON Schema definitions support enabling this feature via build profiles.

Changes

Cohort / File(s) Summary
Distgit / Backend
doozer/doozerlib/distgit.py, doozer/doozerlib/backend/rebaser.py
Detect build_profiles.enable_go_cover and inject ENV GO_COMPLIANCE_COVER=1 into build metadata environments; invoke util.inject_coverage_server(...) to copy coverage_server.go into Go package main directories.
Utility & resource
doozer/doozerlib/util.py, doozer/doozerlib/resources/coverage_server.go
Add utilities to detect Go package main directories and inject an embedded Go HTTP coverage server source exposing a /coverage endpoint, started automatically in init.
Go wrapper scripts
doozer/doozerlib/golang_builder_FIPS_wrapper.sh, doozer/doozerlib/rpm_builder_go_wrapper.sh
Introduce GO_COMPLIANCE_COVER env var (default "0") and internal flags COVERAGE_ADDED/IN_BUILD; when enabled during build or install commands, append -cover and -covermode=atomic and conditionally add the coverage server source to Go build arguments.
RPM build generation
doozer/doozerlib/rpm_builder.py
On enable_go_cover true, call util.inject_coverage_server(Path(rpm.source_path), logger) during rebase; update generated go shim shell script to include a shebang and GO_COMPLIANCE_COVER environment variable setting matching the build profile.
Schemas / Validation
ocp-build-data-validator/validator/json_schemas/build_profiles.schema.json, ocp-build-data-validator/validator/json_schemas/assembly_group_config.schema.json
Add new build_profiles.schema.json defining enable_go_cover boolean plus image- and rpm-specific configs; update assembly group config schema to reference and alias the new build profiles schema property.
Tests
doozer/tests/test_util.py
Add extensive unit tests covering detection of Go package main directories, build constraint handling, injection of coverage_server.go, and utility function behaviors with various edge cases.
Misc (small edits)
doozer/doozerlib/distgit.py, doozer/doozerlib/golang_builder_FIPS_wrapper.sh
Minor comment and control flow clarifications; tracking flag COVERAGE_ADDED prevents duplicate coverage file additions.

Sequence Diagram(s)

sequenceDiagram
  participant Config as GroupConfig.build_profiles
  participant Distgit as Distgit/Rebaser
  participant Util as util.inject_coverage_server
  participant Docker as Dockerfile/BuildContext
  participant Wrapper as ART Go wrapper
  participant GoBuild as go build/install
  participant Coverage as coverage_server.go

  Config->>Distgit: enable_go_cover = true
  Distgit->>Docker: add ENV GO_COMPLIANCE_COVER=1
  Distgit->>Util: inject_coverage_server(dest_dir)
  Util-->>Distgit: copies coverage_server.go into main package dirs
  Docker->>Wrapper: run build/install with GO_COMPLIANCE_COVER=1
  Wrapper->>GoBuild: append -cover -covermode=atomic (and server file if found)
  GoBuild->>Coverage: runtime serves /coverage endpoint
  Coverage-->>GoBuild: returns coverage payload (meta + counters)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@doozer/doozerlib/resources/coverage_server.go`:
- Around line 103-108: The handler currently calls
json.NewEncoder(w).Encode(response) which streams directly to the ResponseWriter
so a partial write can leave headers sent and http.Error ineffective; change
this to encode into a temporary buffer (e.g., use json.Marshal or
json.NewEncoder(&bytes.Buffer)) and check for encoding errors first, then set
w.Header().Set("Content-Type","application/json"), write any status code, and
finally write the buffer to w while checking the write error; update the block
around json.NewEncoder(w).Encode, http.Error, and the response variable to
perform buffer encoding and guarded writing instead.
🧹 Nitpick comments (1)
doozer/doozerlib/resources/coverage_server.go (1)

77-84: Hash extraction relies on internal metadata format.

The hardcoded byte offsets metaData[16:32] assume a specific binary layout of Go's coverage metadata. This could produce incorrect hashes if the format changes in future Go versions. The fallback to "unknown" prevents crashes but may lead to filename collisions.

Consider adding a comment documenting the assumed format and the Go version it was tested against.

Comment on lines 103 to 108
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
log.Printf("[COVERAGE] Error encoding response: %v", err)
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Error response after partial write may fail silently.

json.NewEncoder(w).Encode() writes directly to the ResponseWriter. If encoding fails after headers are sent or data is partially written, the subsequent http.Error() won't take effect because HTTP headers can only be written once.

To handle this correctly, encode to a buffer first, then write to the response.

🔧 Proposed fix
-	w.Header().Set("Content-Type", "application/json")
-	if err := json.NewEncoder(w).Encode(response); err != nil {
-		log.Printf("[COVERAGE] Error encoding response: %v", err)
-		http.Error(w, "Failed to encode response", http.StatusInternalServerError)
-		return
-	}
+	jsonData, err := json.Marshal(response)
+	if err != nil {
+		log.Printf("[COVERAGE] Error encoding response: %v", err)
+		http.Error(w, "Failed to encode response", http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	if _, err := w.Write(jsonData); err != nil {
+		log.Printf("[COVERAGE] Error writing response: %v", err)
+		return
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
log.Printf("[COVERAGE] Error encoding response: %v", err)
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
return
}
jsonData, err := json.Marshal(response)
if err != nil {
log.Printf("[COVERAGE] Error encoding response: %v", err)
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(jsonData); err != nil {
log.Printf("[COVERAGE] Error writing response: %v", err)
return
}
🤖 Prompt for AI Agents
In `@doozer/doozerlib/resources/coverage_server.go` around lines 103 - 108, The
handler currently calls json.NewEncoder(w).Encode(response) which streams
directly to the ResponseWriter so a partial write can leave headers sent and
http.Error ineffective; change this to encode into a temporary buffer (e.g., use
json.Marshal or json.NewEncoder(&bytes.Buffer)) and check for encoding errors
first, then set w.Header().Set("Content-Type","application/json"), write any
status code, and finally write the buffer to w while checking the write error;
update the block around json.NewEncoder(w).Encode, http.Error, and the response
variable to perform buffer encoding and guarded writing instead.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@doozer/doozerlib/resources/coverage_server.go`:
- Around line 13-14: coverage_server.go imports runtime/coverage (requires Go
1.20+) but the repo has no go.mod and the Dockerfile/build do not compile Go, so
the file cannot build; either (A) add Go module and build integration: create a
go.mod with "go 1.20", add the package to the module, update the
Dockerfile/build pipeline to install Go and run go build for the package (or add
a specific build target invoked by CI), or (B) remove/disable the file if
unused: delete coverage_server.go (and its runtime/coverage import) or add a
build tag (e.g., //go:build coverage) so it is excluded unless explicitly built;
reference the file name coverage_server.go and the runtime/coverage import when
making the change.

In `@doozer/doozerlib/rpm_builder.py`:
- Around line 418-422: The generated shim written to $GOSHIM_DIR/go uses an
invalid shebang '#/bin/sh' which causes exec format errors; update the shim
template in rpm_builder.py where the heredoc is created (the block feeding cat >
$GOSHIM_DIR/go) to use a valid shebang '#!/bin/sh' (fix the string that
currently contains '#/bin/sh') while keeping the rest of the heredoc content
(GOSHIM_DIR, GO_COMPLIANCE_COVER logic, and rpm_builder_go_wrapper_sh)
unchanged.
- Around line 122-125: The coverage-server injection currently runs when
rpm.runtime.group_config.build_profiles.enable_go_cover is true
(util.inject_coverage_server), but the build flag GO_COMPLIANCE_COVER is only
emitted when go_compliance_shim is enabled in _populate_specfile_async; update
the logic so injection is gated to the actual shim flag (i.e., only call
util.inject_coverage_server when go_compliance_shim is enabled too) or
alternatively ensure GO_COMPLIANCE_COVER is set for non-shim builds; modify the
conditional around inject_coverage_server to check
rpm.runtime.group_config.build_profiles.go_compliance_shim (or add an explicit
runtime assertion/fail-fast error when enable_go_cover is true but
go_compliance_shim is false) so the source is only injected when the build will
compile with -cover.

In `@doozer/doozerlib/util.py`:
- Around line 1016-1019: The loop that injects _COVERAGE_SERVER_GO into
main_dirs uses shutil.copy2 which will overwrite any existing
coverage_server.go; update the logic inside the loop (where main_dirs, dest and
_COVERAGE_SERVER_GO are used and shutil.copy2 is called) to first check if
dest.exists() and if so either skip the copy or only overwrite when contents
differ (compare with filecmp.cmp or byte-read comparison), and only call
shutil.copy2 when safe; adjust logger_instance.info to reflect whether the file
was injected, skipped, or unchanged.
🧹 Nitpick comments (1)
doozer/doozerlib/util.py (1)

961-975: Avoid false negatives when package main appears after long headers.

Limiting the scan to 50 lines can miss package main in generated files or long license headers, which would silently skip injection. Consider scanning until the first package declaration (or using Go parsing).

Possible improvement
-    try:
-        with file_path.open('rb') as f:
-            for _ in range(50):
-                line = f.readline()
-                if not line:
-                    break
-                if _PKG_MAIN_PATTERN.match(line):
-                    return True
+    try:
+        with file_path.open('rb') as f:
+            for line in f:
+                if _PKG_MAIN_PATTERN.match(line):
+                    return True
+                if line.lstrip().startswith(b'package '):
+                    return False

Comment on lines 13 to 14
"runtime/coverage"
"time"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check the go.mod file for minimum Go version
if [ -f "go.mod" ]; then
  head -20 go.mod | grep -E "^go "
fi

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Check coverage_server.go for build tags and full context
cat -n doozer/doozerlib/resources/coverage_server.go | head -30

Repository: openshift-eng/art-tools

Length of output: 937


🏁 Script executed:

# Search for any Go version-related constraints or build tags in the codebase
rg "build go1\.|//go:build|// +build" --type go -A 2 -B 2 | head -50

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Find go.mod file
fd "go\.mod" --type f

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Check if there's a go.mod at the repository root and show its first 15 lines
if [ -f "./go.mod" ]; then
  cat -n ./go.mod | head -15
else
  find . -name "go.mod" -type f -exec sh -c 'echo "Found: {}"; head -15 "$1"' _ {} \;
fi

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Check for any coverage-related files or alternative implementations
fd "coverage" --type f | grep -E "\.(go|mod)$"

Repository: openshift-eng/art-tools

Length of output: 112


🌐 Web query:

When was runtime/coverage package introduced in Go - Go version requirement

💡 Result:

The standard library package runtime/coverage was introduced in Go 1.20 (released February 1, 2023). Therefore, the minimum Go version requirement is Go 1.20+. [1][2]


🏁 Script executed:

# Check the repository root structure
ls -la | head -20

Repository: openshift-eng/art-tools

Length of output: 1238


🏁 Script executed:

# Look for all go.mod files in the entire repo (not just root)
find . -name "go.mod" -type f 2>/dev/null

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Check if there's a go.work file (workspace)
find . -name "go.work" -type f 2>/dev/null

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Look at the parent directory structure of coverage_server.go
ls -la doozer/doozerlib/resources/ | head -20

Repository: openshift-eng/art-tools

Length of output: 245


🏁 Script executed:

# Check doozer directory structure
ls -la doozer/ | head -20

Repository: openshift-eng/art-tools

Length of output: 1226


🏁 Script executed:

# Look deeper in doozer for go.mod
find doozer -name "go.mod" -o -name "go.work" 2>/dev/null

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Check if coverage_server.go is actually being compiled or used
rg "coverage_server" --type go doozer/

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Look for Python build configuration or setup files
find doozer -name "setup.py" -o -name "pyproject.toml" -o -name "Makefile" 2>/dev/null | head -10

Repository: openshift-eng/art-tools

Length of output: 92


🏁 Script executed:

# Check the build_doozer script for compilation instructions
cat doozer/build_doozer

Repository: openshift-eng/art-tools

Length of output: 424


🏁 Script executed:

# Check the Dockerfile for Go compilation setup
cat doozer/Dockerfile | grep -A 5 -B 5 -i "go\|coverage"

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Check setup.py to see if it references coverage_server or Go compilation
cat doozer/rundoozer/setup.py

Repository: openshift-eng/art-tools

Length of output: 765


🏁 Script executed:

# Check if coverage_server.go is referenced in any config files
rg "coverage_server" --type toml --type yaml --type json --type txt

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Double-check by looking for any references to coverage or the file
rg -i "coverage" doozer/doozerlib/resources/ --type go

Repository: openshift-eng/art-tools

Length of output: 2887


🏁 Script executed:

# Check git history to see when this file was added
cd doozer/doozerlib/resources && git log --oneline coverage_server.go 2>/dev/null | head -5

Repository: openshift-eng/art-tools

Length of output: 98


Add Go module configuration and build integration or remove unused code.

coverage_server.go imports runtime/coverage, which requires Go 1.20+ and has no build guards. However, the fundamental issue is that there is no go.mod file in the repository, and the file is not integrated into any build system (the Dockerfile contains no Go compiler setup). The file was recently added but is not referenced anywhere in the codebase and cannot currently compile. Either:

  • Add proper Go module setup (go.mod) and integrate compilation into the Dockerfile and build pipeline, or
  • Remove the file and the import if it is not intended for immediate use.
🤖 Prompt for AI Agents
In `@doozer/doozerlib/resources/coverage_server.go` around lines 13 - 14,
coverage_server.go imports runtime/coverage (requires Go 1.20+) but the repo has
no go.mod and the Dockerfile/build do not compile Go, so the file cannot build;
either (A) add Go module and build integration: create a go.mod with "go 1.20",
add the package to the module, update the Dockerfile/build pipeline to install
Go and run go build for the package (or add a specific build target invoked by
CI), or (B) remove/disable the file if unused: delete coverage_server.go (and
its runtime/coverage import) or add a build tag (e.g., //go:build coverage) so
it is excluded unless explicitly built; reference the file name
coverage_server.go and the runtime/coverage import when making the change.

Comment on lines +122 to +125
if rpm.runtime.group_config.build_profiles.enable_go_cover is True:
# Inject the coverage HTTP server source into every Go main package
# directory so that it is compiled into the binary via its init() function.
util.inject_coverage_server(Path(rpm.source_path), logger)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Align coverage injection with the shim that actually enables -cover.

inject_coverage_server runs whenever enable_go_cover is true, but GO_COMPLIANCE_COVER is only emitted when go_compliance_shim is enabled in _populate_specfile_async. If these flags can diverge, the server is injected but the build won’t compile with coverage, so /coverage will return errors. Consider failing fast or gating injection on go_compliance_shim (or otherwise ensure GO_COMPLIANCE_COVER is set for non‑shim builds).

Proposed guard to prevent a mismatched configuration
-        if rpm.runtime.group_config.build_profiles.enable_go_cover is True:
+        if rpm.runtime.group_config.build_profiles.enable_go_cover is True:
+            if not go_compliance_shim:
+                raise ValueError(
+                    "enable_go_cover requires compliance.rpm_shim.enabled to emit -cover flags"
+                )
             # Inject the coverage HTTP server source into every Go main package
             # directory so that it is compiled into the binary via its init() function.
             util.inject_coverage_server(Path(rpm.source_path), logger)
🤖 Prompt for AI Agents
In `@doozer/doozerlib/rpm_builder.py` around lines 122 - 125, The coverage-server
injection currently runs when
rpm.runtime.group_config.build_profiles.enable_go_cover is true
(util.inject_coverage_server), but the build flag GO_COMPLIANCE_COVER is only
emitted when go_compliance_shim is enabled in _populate_specfile_async; update
the logic so injection is gated to the actual shim flag (i.e., only call
util.inject_coverage_server when go_compliance_shim is enabled too) or
alternatively ensure GO_COMPLIANCE_COVER is set for non-shim builds; modify the
conditional around inject_coverage_server to check
rpm.runtime.group_config.build_profiles.go_compliance_shim (or add an explicit
runtime assertion/fail-fast error when enable_go_cover is true but
go_compliance_shim is false) so the source is only injected when the build will
compile with -cover.

Comment on lines +418 to 422
# Use single quotes 'EOF' to avoid variable expansion within script content.
cat > $GOSHIM_DIR/go << 'EOF'
#!/bin/sh
export GO_COMPLIANCE_COVER={'1' if rpm.runtime.group_config.build_profiles.enable_go_cover is True else '0'}
{rpm_builder_go_wrapper_sh}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix the invalid shebang in the generated shim.

#/bin/sh is missing the !, so the wrapper will fail with an “exec format error” when invoked via PATH.

🐛 Suggested fix
-#/bin/sh
+#!/bin/sh
🤖 Prompt for AI Agents
In `@doozer/doozerlib/rpm_builder.py` around lines 418 - 422, The generated shim
written to $GOSHIM_DIR/go uses an invalid shebang '#/bin/sh' which causes exec
format errors; update the shim template in rpm_builder.py where the heredoc is
created (the block feeding cat > $GOSHIM_DIR/go) to use a valid shebang
'#!/bin/sh' (fix the string that currently contains '#/bin/sh') while keeping
the rest of the heredoc content (GOSHIM_DIR, GO_COMPLIANCE_COVER logic, and
rpm_builder_go_wrapper_sh) unchanged.

Comment on lines +1016 to +1019
for pkg_dir in main_dirs:
dest = pkg_dir / _COVERAGE_SERVER_GO.name
shutil.copy2(str(_COVERAGE_SERVER_GO), str(dest))
logger_instance.info('Injected %s into %s', _COVERAGE_SERVER_GO.name, pkg_dir)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid clobbering an existing coverage_server.go.

copy2 will overwrite any existing file with the same name. If a repo already contains a file named coverage_server.go, this will silently replace it. Consider skipping injection when the destination exists (or only overwriting when the contents match).

Safer injection
     for pkg_dir in main_dirs:
         dest = pkg_dir / _COVERAGE_SERVER_GO.name
-        shutil.copy2(str(_COVERAGE_SERVER_GO), str(dest))
+        if dest.exists():
+            logger_instance.debug('%s already present in %s; skipping injection', _COVERAGE_SERVER_GO.name, pkg_dir)
+            continue
+        shutil.copy2(str(_COVERAGE_SERVER_GO), str(dest))
         logger_instance.info('Injected %s into %s', _COVERAGE_SERVER_GO.name, pkg_dir)
🤖 Prompt for AI Agents
In `@doozer/doozerlib/util.py` around lines 1016 - 1019, The loop that injects
_COVERAGE_SERVER_GO into main_dirs uses shutil.copy2 which will overwrite any
existing coverage_server.go; update the logic inside the loop (where main_dirs,
dest and _COVERAGE_SERVER_GO are used and shutil.copy2 is called) to first check
if dest.exists() and if so either skip the copy or only overwrite when contents
differ (compare with filecmp.cmp or byte-read comparison), and only call
shutil.copy2 when safe; adjust logger_instance.info to reflect whether the file
was injected, skipped, or unchanged.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@doozer/doozerlib/golang_builder_FIPS_wrapper.sh`:
- Around line 327-342: Pre-scan the incoming build arguments before the existing
injection block to detect if "coverage_server.go" is already present (check the
basename of each element in the args list/array) and, if found, set
COVERAGE_ADDED=1 so the injection logic (which checks GO_COMPLIANCE_COVER,
IN_BUILD, COVERAGE_ADDED, and arg) will skip adding a duplicate; update the code
that manipulates ARGS and the injection condition to rely on this flag
(referencing COVERAGE_ADDED, ARGS, and the existing coverage_server.go checks)
so you never append coverage_server.go twice.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@doozer/doozerlib/util.py`:
- Around line 844-848: The golang-priority branch currently returns
constants.KONFLUX_DEFAULT_BUILD_PRIORITY + 2 which can exceed the allowed 1–10
Konflux range; update the 'golang' branch (the block that checks if 'golang' in
group and currently returns str(constants.KONFLUX_DEFAULT_BUILD_PRIORITY + 2))
to clamp the computed priority to a maximum of 10 (e.g., compute priority =
min(constants.KONFLUX_DEFAULT_BUILD_PRIORITY + 2, 10)) and then return it as a
string so the value always stays within the valid Konflux range.

Comment on lines 844 to 848
# 3. Golang builder images are high priority since they block other builds.
if 'golang' in group:
# Prioritize since golang builds may be part of addressing security issues.
logger.info(f"Using golang priority for {metadata.distgit_key}")
return str(constants.KONFLUX_DEFAULT_BUILD_PRIORITY + 2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clamp Golang priority to the valid Konflux range.

The function contract describes a 1–10 priority; adding 2 can exceed the upper bound if the default is already high. Consider clamping to 10 to avoid invalid priorities.

🔧 Proposed fix
-        return str(constants.KONFLUX_DEFAULT_BUILD_PRIORITY + 2)
+        return str(min(10, constants.KONFLUX_DEFAULT_BUILD_PRIORITY + 2))
🤖 Prompt for AI Agents
In `@doozer/doozerlib/util.py` around lines 844 - 848, The golang-priority branch
currently returns constants.KONFLUX_DEFAULT_BUILD_PRIORITY + 2 which can exceed
the allowed 1–10 Konflux range; update the 'golang' branch (the block that
checks if 'golang' in group and currently returns
str(constants.KONFLUX_DEFAULT_BUILD_PRIORITY + 2)) to clamp the computed
priority to a maximum of 10 (e.g., compute priority =
min(constants.KONFLUX_DEFAULT_BUILD_PRIORITY + 2, 10)) and then return it as a
string so the value always stays within the valid Konflux range.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@doozer/doozerlib/rpm_builder_go_wrapper.sh`:
- Around line 70-77: When GO_COMPLIANCE_COVER is set the script unconditionally
adds -cover flags (lines manipulating ARGS and echoerr), which only work on Go
1.20+. Add a runtime Go version check: when GO_COMPLIANCE_COVER==1 call `go
version`, parse the major.minor, and if it’s lower than 1.20 log an error via
echoerr and exit non-zero (or disable coverage with a clear message), before
appending "-cover" and "-covermode=atomic" to ARGS; reference the
GO_COMPLIANCE_COVER variable, the ARGS array, and the echoerr/IN_BUILD logic to
place the check.

Comment on lines 70 to 77
IN_BUILD="1" # This is a go build invocation
ARGS+=("${arg}") # Add "build"

if [[ "${GO_COMPLIANCE_COVER:-}" == "1" ]]; then
echoerr "adding -cover for build or install operation"
ARGS+=("-cover")
ARGS+=("-covermode=atomic")
fi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Search for Go version constraints in the repository
find . -maxdepth 3 -type f \( -name "go.mod" -o -name "Dockerfile" -o -name ".github" -o -name ".gitlab-ci.yml" -o -name "Makefile" \) | head -20

Repository: openshift-eng/art-tools

Length of output: 177


🏁 Script executed:

# Look for go.mod file
fd "go.mod" -t f

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Check for CI/CD configurations that specify Go version
fd -e yml -e yaml | xargs grep -l "go" | head -10

Repository: openshift-eng/art-tools

Length of output: 195


🌐 Web query:

go build -cover flag support when was it introduced Go release history

💡 Result:

go build -cover was introduced in Go 1.20 (released February 2023) as part of the new support for collecting coverage profiles from built programs/integration tests (by running the instrumented binary with GOCOVERDIR set). [1] [2] [3]

(For context: coverage via go test -cover dates back to Go 1.2.) [2] [3]

Sources:
[1] Go 1.20 release notes (go build now supports -cover)
[2] Go blog: Code coverage for Go integration tests (dated 2023-03-08; “available in Go 1.20”)
[3] Go documentation: Coverage profiling support for integration tests


🏁 Script executed:

# Check doozer Dockerfile for Go version
cat doozer/Dockerfile | head -50

Repository: openshift-eng/art-tools

Length of output: 2126


🏁 Script executed:

# Check Makefile for Go version
cat Makefile

Repository: openshift-eng/art-tools

Length of output: 1496


🏁 Script executed:

# Look for go.mod anywhere in the repo
find . -name "go.mod" -type f

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Check if there's a .go-version or similar file
fd -e "txt" | xargs grep -l "1\.[0-9]" 2>/dev/null | grep -i go | head -5

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Read the rpm_builder_go_wrapper.sh script to understand its context
cat -n doozer/doozerlib/rpm_builder_go_wrapper.sh

Repository: openshift-eng/art-tools

Length of output: 8641


🏁 Script executed:

# Look for CI/CD pipeline files that might specify Go version
fd -e yml -e yaml | xargs grep -A 5 -B 5 "go" 2>/dev/null | grep -i version | head -20

Repository: openshift-eng/art-tools

Length of output: 49


🏁 Script executed:

# Check for README or documentation files that mention Go requirements
find . -maxdepth 2 -type f \( -name "README*" -o -name "*.md" \) | xargs grep -i "go\|version" 2>/dev/null | head -30

Repository: openshift-eng/art-tools

Length of output: 4509


Require Go 1.20+ or add a version check for -cover flag support.

The -cover flag for go build was introduced in Go 1.20 (February 2023). Line 73–77 adds this flag when GO_COMPLIANCE_COVER=1, but there's no enforcement of a minimum Go version. If the environment running this wrapper uses Go < 1.20 with GO_COMPLIANCE_COVER=1 enabled, the build will fail. Either document that Go 1.20+ is required for this compliance coverage feature, or add a runtime version check to prevent silent failures.

🤖 Prompt for AI Agents
In `@doozer/doozerlib/rpm_builder_go_wrapper.sh` around lines 70 - 77, When
GO_COMPLIANCE_COVER is set the script unconditionally adds -cover flags (lines
manipulating ARGS and echoerr), which only work on Go 1.20+. Add a runtime Go
version check: when GO_COMPLIANCE_COVER==1 call `go version`, parse the
major.minor, and if it’s lower than 1.20 log an error via echoerr and exit
non-zero (or disable coverage with a clear message), before appending "-cover"
and "-covermode=atomic" to ARGS; reference the GO_COMPLIANCE_COVER variable, the
ARGS array, and the echoerr/IN_BUILD logic to place the check.

Go can target a directory or specific files. Inject coverage_server
to specific files list.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@doozer/doozerlib/util.py`:
- Around line 832-835: The helper higher_by uses the variable phase in its
logger call but phase is assigned later, causing a NameError; either move the
phase definition to before the higher_by function or remove/replace phase from
the log message so higher_by no longer references an undefined variable. Update
the code paths that call higher_by (notably the golang builder branch) to ensure
phase is defined first if you choose to keep phase in logs, or change the logger
in higher_by to log without phase (e.g., only metadata.distgit_key and
new_priority) so it is safe regardless of call order.
🧹 Nitpick comments (1)
doozer/doozerlib/golang_builder_FIPS_wrapper.sh (1)

1-1: Shebang declares POSIX sh but script uses bash-isms.

The script uses [[ ]], arrays (+=), and == comparisons which are bash-specific. While this is a pre-existing pattern in the file, consider updating the shebang to #!/bin/bash for correctness if the script depends on bash features.

Comment on lines +832 to +835
def higher_by(val: int) -> str:
new_priority = str(max(1, constants.KONFLUX_DEFAULT_BUILD_PRIORITY - val))
logger.info(f"Using phase-based priority for {metadata.distgit_key}: {new_priority} (phase: {phase})")
return new_priority
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

NameError: phase is referenced before assignment.

The higher_by helper references phase in the log message (line 834), but when called from line 852 (golang builder branch), phase hasn't been defined yet—it's only assigned at line 855. This will cause a runtime exception.

🐛 Proposed fix: move phase definition earlier or adjust log message
     def higher_by(val: int) -> str:
         new_priority = str(max(1, constants.KONFLUX_DEFAULT_BUILD_PRIORITY - val))
-        logger.info(f"Using phase-based priority for {metadata.distgit_key}: {new_priority} (phase: {phase})")
+        logger.info(f"Using computed priority for {metadata.distgit_key}: {new_priority}")
         return new_priority

Alternatively, move the phase assignment before the higher_by definition if you want to retain phase in the log.

🤖 Prompt for AI Agents
In `@doozer/doozerlib/util.py` around lines 832 - 835, The helper higher_by uses
the variable phase in its logger call but phase is assigned later, causing a
NameError; either move the phase definition to before the higher_by function or
remove/replace phase from the log message so higher_by no longer references an
undefined variable. Update the code paths that call higher_by (notably the
golang builder branch) to ensure phase is defined first if you choose to keep
phase in logs, or change the logger in higher_by to log without phase (e.g.,
only metadata.distgit_key and new_priority) so it is safe regardless of call
order.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@doozer/tests/test_util.py`:
- Around line 1-3: Run the code formatter to apply Ruff's recommended formatting
to the test module test_util: execute ruff format on that file to reorder/format
the imports (logging, pathlib, tempfile), apply any whitespace/line-ending
changes Ruff suggests, save the file, and commit the formatted changes so CI
stops failing.

Comment on lines +1 to +3
import logging
import pathlib
import tempfile
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix Ruff formatting to unblock CI.

The unit-tests job reports ruff format would reformat this file. Run ruff format doozer/tests/test_util.py (or ruff format) and commit the formatting changes.

🧰 Tools
🪛 GitHub Actions: unit-tests

[error] 1-1: Ruff format check failed. 1 file would be reformatted: 'doozer/tests/test_util.py'. Run 'ruff format' to fix code style issues.

🤖 Prompt for AI Agents
In `@doozer/tests/test_util.py` around lines 1 - 3, Run the code formatter to
apply Ruff's recommended formatting to the test module test_util: execute ruff
format on that file to reorder/format the imports (logging, pathlib, tempfile),
apply any whitespace/line-ending changes Ruff suggests, save the file, and
commit the formatted changes so CI stops failing.

Found issues building golang-github-prometheus-prometheus
where a file with package main existed in a .go file, but
it was a utility cmd with //build:ignore. Ensure we spot
these a skip the directory.
This can lead to coverage server being copied into
what Konflux perceives as the vendor directory, throwing
off checksums. If go.mod is detected, stop the search for
main packages.
@pruan-rht
Copy link
Contributor

pruan-rht commented Feb 10, 2026

@jupierce unit-tests gatekeeper is failing...looks like a lint/formating issue. PTAL

@jupierce jupierce changed the title Allow ART to build with code coverage WIP: Allow ART to build with code coverage Feb 11, 2026
@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Feb 11, 2026
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 13, 2026

@jupierce: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/security f4dce42 link false /test security

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants