Skip to content

Switch ran eco gotests version comparison to semver CNF-20904#1322

Open
bazem8 wants to merge 2 commits intorh-ecosystem-edge:mainfrom
bazem8:switch-ran-eco-gotests-version-comparison-to-semver
Open

Switch ran eco gotests version comparison to semver CNF-20904#1322
bazem8 wants to merge 2 commits intorh-ecosystem-edge:mainfrom
bazem8:switch-ran-eco-gotests-version-comparison-to-semver

Conversation

@bazem8
Copy link
Copy Markdown
Collaborator

@bazem8 bazem8 commented Apr 9, 2026

switch RAN eco-gotests version comparison to semver - CNF-20904
passed the review of "/cnf-ran-review" .

Summary by CodeRabbit

Release Notes

  • Refactor

    • Reorganized version checking system to use semantic versioning for more precise version comparisons across test suites.
    • Updated all version-based test conditions to use standardized version format for improved accuracy and consistency.
  • Chores

    • Updated dependency management to resolve version constraints.

bazem8 added 2 commits April 9, 2026 15:57
Replace IsVersionStringInRange logic that used a regex on the first two
dot-separated integers and compared major/minor independently. Add
semver_range.go using github.com/Masterminds/semver/v3 for SemVer 2.0 ordering,
document X.Y.0-0 lower bounds for pre-releases, and interpret two-segment
maximum strings as whole minor lines (upper bound is the next minor).

- Add semver_range.go; keep cluster/ZTP helpers in version.go unchanged
- Update version_test.go with pre-release and v-prefix cases; document
  UNIT_TEST=true for local tests without kubeconfig
- Declare Masterminds semver/v3 as a direct dependency in go.mod
- Update RAN call sites (GitOps/ZTP, TALM, PTP, power management) to pass
  4.N.0-0 minimums where the gate means at least OpenShift minor 4.N

Made-with: Cursor
- Document three-segment maximum (exclusive next patch) and legacy (true,nil) for unparseable version with empty maximum.

- version tests: use ErrorContains instead of comparing errors; add cases for three-segment max; drop testify/require (not vendored).

- TALM tests: align By/Skip wording with 4.11.0-0 threshold.

Made-with: Cursor
@bazem8 bazem8 requested a review from klaskosk April 9, 2026 14:28
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

This change refactors the semantic version range checking utility and updates version gating checks across test files. The IsVersionStringInRange function is moved to a new file with enhanced SemVer 2.0 compatibility logic. Version minimum bounds in 25+ test files are updated from short format (e.g., "4.12") to fully-qualified format (e.g., "4.12.0-0"). The semver dependency is adjusted in go.mod.

Changes

Cohort / File(s) Summary
Dependency Update
go.mod
Removed and re-added github.com/Masterminds/semver/v3 v3.4.0 as a direct requirement in the require block.
Version Utility Refactoring
tests/cnf/ran/internal/version/semver_range.go, tests/cnf/ran/internal/version/version.go, tests/cnf/ran/internal/version/version_test.go
Refactored IsVersionStringInRange function: moved from version.go to new semver_range.go with enhanced SemVer 2.0 parsing including prerelease/metadata handling and improved bound coercion logic; updated tests to validate new behavior with additional edge cases.
ZTP Test Version Gating
tests/cnf/ran/gitopsztp/tests/ztp-argocd-acm-crs.go, ztp-argocd-clusters-app.go, ztp-argocd-hub-templating.go, ztp-argocd-node-delete.go, ztp-argocd-policies-app.go, ztp-bios-day-zero.go, ztp-cluster-instance-delete.go, ztp-siteconfig-day-two.go, ztp-siteconfig-failover.go, ztp-siteconfig-negative.go
Updated minimum version bounds passed to version checks from short format (e.g., "4.12", "4.14") to full SemVer format (e.g., "4.12.0-0", "4.14.0-0") across 10 test files.
Power Management Test Updates
tests/cnf/ran/powermanagement/tests/cpufreq.go
Updated CPU frequency tuning test version check minimum from "4.16" to "4.16.0-0".
PTP Test Version Gating
tests/cnf/ran/ptp/internal/consumer/bothversions.go, ptp/internal/mustgather/mustgather.go, ptp/tests/ptp-event-consumer.go, ptp/tests/ptp-interfaces.go, ptp/tests/ptp-log-reduction.go, ptp/tests/ptp-ntp-fallback.go, ptp/tests/ptp-oc-2-port.go, ptp/tests/ptp-process-restart.go
Updated minimum version bounds from short format (e.g., "4.16", "4.18", "4.20") to full SemVer format (e.g., "4.16.0-0", "4.18.0-0", "4.20.0-0") across 8 test files.
TALM Test Version Gating
tests/cnf/ran/talm/tests/talm-backup.go, talm-batching.go, talm-blockingcr.go, talm-precache.go
Updated minimum version bounds from short format to full SemVer format; adjusted gating thresholds in some tests (e.g., lowering minimum from 4.12 to 4.11 in batching and blockingcr tests).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly identifies the main change: switching version comparison logic from regex-based to SemVer-based approach, and includes the issue reference (CNF-20904).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch switch-ran-eco-gotests-version-comparison-to-semver

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
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

🧹 Nitpick comments (1)
tests/cnf/ran/internal/version/version_test.go (1)

128-138: Add named subtests to make failures easier to pinpoint.

Current table loop makes failures harder to triage. A name field + t.Run would improve diagnostics without changing behavior.

♻️ Suggested refactor
 func TestIsVersionStringInRange(t *testing.T) {
 	testCases := []struct {
+		name             string
 		version          string
 		minimum          string
 		maximum          string
 		expectedResult   bool
 		wantErrSubstring string // if non-empty, err must contain this substring; if empty, err must be nil
 	}{
 		{
+			name:           "version within minimum only range",
 			version:        "4.16.0",
 			minimum:        "4.10.0-0",
 			maximum:        "",
 			expectedResult: true,
 		},
+		// ...populate name for remaining cases...
 	}

 	for _, testCase := range testCases {
-		result, err := IsVersionStringInRange(testCase.version, testCase.minimum, testCase.maximum)
-
-		assert.Equal(t, testCase.expectedResult, result)
-		if testCase.wantErrSubstring != "" {
-			assert.Error(t, err)
-			assert.ErrorContains(t, err, testCase.wantErrSubstring)
-		} else {
-			assert.NoError(t, err)
-		}
+		t.Run(testCase.name, func(t *testing.T) {
+			result, err := IsVersionStringInRange(testCase.version, testCase.minimum, testCase.maximum)
+
+			assert.Equal(t, testCase.expectedResult, result)
+			if testCase.wantErrSubstring != "" {
+				assert.Error(t, err)
+				assert.ErrorContains(t, err, testCase.wantErrSubstring)
+			} else {
+				assert.NoError(t, err)
+			}
+		})
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cnf/ran/internal/version/version_test.go` around lines 128 - 138, The
table-driven tests loop over testCases without named subtests, making failures
hard to trace; add a name field to the testCase struct and wrap each iteration
in t.Run(testCase.name, func(t *testing.T) { ... }) when calling
IsVersionStringInRange so assertions run as distinct subtests, keeping the
existing assertions and error checks unchanged and referencing the existing
testCases slice and IsVersionStringInRange call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/cnf/ran/internal/version/semver_range.go`:
- Around line 82-98: The two-segment branch in the function that parses maximums
(after splitSemverCore(s) into core, tail and parts) currently ignores tail and
widens ranges; update the len(parts) == 2 case in that function to check if tail
is non-empty and, if so, return the same invalid-maximum error (e.g.,
fmt.Errorf("invalid maximum provided: '%s'", maximum)) instead of proceeding to
construct a broadened version with semver.NewVersion; keep the existing behavior
for plain "X.Y" (tail empty) and only allow constructing the X.Y.0-0 version
when tail == "".

---

Nitpick comments:
In `@tests/cnf/ran/internal/version/version_test.go`:
- Around line 128-138: The table-driven tests loop over testCases without named
subtests, making failures hard to trace; add a name field to the testCase struct
and wrap each iteration in t.Run(testCase.name, func(t *testing.T) { ... }) when
calling IsVersionStringInRange so assertions run as distinct subtests, keeping
the existing assertions and error checks unchanged and referencing the existing
testCases slice and IsVersionStringInRange call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 02fc7a03-1f9f-49d4-806c-da05688da92d

📥 Commits

Reviewing files that changed from the base of the PR and between f35d253 and e8e0c34.

📒 Files selected for processing (27)
  • go.mod
  • tests/cnf/ran/gitopsztp/tests/ztp-argocd-acm-crs.go
  • tests/cnf/ran/gitopsztp/tests/ztp-argocd-clusters-app.go
  • tests/cnf/ran/gitopsztp/tests/ztp-argocd-hub-templating.go
  • tests/cnf/ran/gitopsztp/tests/ztp-argocd-node-delete.go
  • tests/cnf/ran/gitopsztp/tests/ztp-argocd-policies-app.go
  • tests/cnf/ran/gitopsztp/tests/ztp-bios-day-zero.go
  • tests/cnf/ran/gitopsztp/tests/ztp-cluster-instance-delete.go
  • tests/cnf/ran/gitopsztp/tests/ztp-siteconfig-day-two.go
  • tests/cnf/ran/gitopsztp/tests/ztp-siteconfig-failover.go
  • tests/cnf/ran/gitopsztp/tests/ztp-siteconfig-negative.go
  • tests/cnf/ran/internal/version/semver_range.go
  • tests/cnf/ran/internal/version/version.go
  • tests/cnf/ran/internal/version/version_test.go
  • tests/cnf/ran/powermanagement/tests/cpufreq.go
  • tests/cnf/ran/ptp/internal/consumer/bothversions.go
  • tests/cnf/ran/ptp/internal/mustgather/mustgather.go
  • tests/cnf/ran/ptp/tests/ptp-event-consumer.go
  • tests/cnf/ran/ptp/tests/ptp-interfaces.go
  • tests/cnf/ran/ptp/tests/ptp-log-reduction.go
  • tests/cnf/ran/ptp/tests/ptp-ntp-fallback.go
  • tests/cnf/ran/ptp/tests/ptp-oc-2-port.go
  • tests/cnf/ran/ptp/tests/ptp-process-restart.go
  • tests/cnf/ran/talm/tests/talm-backup.go
  • tests/cnf/ran/talm/tests/talm-batching.go
  • tests/cnf/ran/talm/tests/talm-blockingcr.go
  • tests/cnf/ran/talm/tests/talm-precache.go
💤 Files with no reviewable changes (1)
  • tests/cnf/ran/internal/version/version.go

Comment on lines +82 to +98
core, tail := splitSemverCore(s)
parts := strings.Split(core, ".")
for _, p := range parts {
if _, err := strconv.ParseUint(p, 10, 64); err != nil {
return nil, fmt.Errorf("invalid maximum provided: '%s'", maximum)
}
}

switch len(parts) {
case 0, 1:
return nil, fmt.Errorf("invalid maximum provided: '%s'", maximum)
case 2:
maj, _ := strconv.ParseUint(parts[0], 10, 64)
min, _ := strconv.ParseUint(parts[1], 10, 64)

return semver.NewVersion(fmt.Sprintf("%d.%d.0-0", maj, min+1))
default:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject suffixed two-segment maximums instead of silently broadening range.

In the len(parts) == 2 path, tail is currently ignored, so inputs like 4.15-rc.1 or 4.15+meta are treated as plain 4.15. That can admit versions the caller did not intend.

🐛 Proposed fix
 func parseMaximumExclusiveUpper(maximum string) (*semver.Version, error) {
 	if maximum == "" {
 		return nil, nil
 	}

 	s := trimSemverVPrefix(maximum)
 	core, tail := splitSemverCore(s)
 	parts := strings.Split(core, ".")
 	for _, p := range parts {
 		if _, err := strconv.ParseUint(p, 10, 64); err != nil {
 			return nil, fmt.Errorf("invalid maximum provided: '%s'", maximum)
 		}
 	}

 	switch len(parts) {
 	case 0, 1:
 		return nil, fmt.Errorf("invalid maximum provided: '%s'", maximum)
 	case 2:
+		if tail != "" {
+			return nil, fmt.Errorf("invalid maximum provided: '%s'", maximum)
+		}
 		maj, _ := strconv.ParseUint(parts[0], 10, 64)
 		min, _ := strconv.ParseUint(parts[1], 10, 64)

-		return semver.NewVersion(fmt.Sprintf("%d.%d.0-0", maj, min+1))
+		upper, err := semver.NewVersion(fmt.Sprintf("%d.%d.0-0", maj, min+1))
+		if err != nil {
+			return nil, fmt.Errorf("invalid maximum provided: '%s'", maximum)
+		}
+		return upper, nil
 	default:
 		full := core
 		if tail != "" {
 			full = core + tail
 		}
📝 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
core, tail := splitSemverCore(s)
parts := strings.Split(core, ".")
for _, p := range parts {
if _, err := strconv.ParseUint(p, 10, 64); err != nil {
return nil, fmt.Errorf("invalid maximum provided: '%s'", maximum)
}
}
switch len(parts) {
case 0, 1:
return nil, fmt.Errorf("invalid maximum provided: '%s'", maximum)
case 2:
maj, _ := strconv.ParseUint(parts[0], 10, 64)
min, _ := strconv.ParseUint(parts[1], 10, 64)
return semver.NewVersion(fmt.Sprintf("%d.%d.0-0", maj, min+1))
default:
core, tail := splitSemverCore(s)
parts := strings.Split(core, ".")
for _, p := range parts {
if _, err := strconv.ParseUint(p, 10, 64); err != nil {
return nil, fmt.Errorf("invalid maximum provided: '%s'", maximum)
}
}
switch len(parts) {
case 0, 1:
return nil, fmt.Errorf("invalid maximum provided: '%s'", maximum)
case 2:
if tail != "" {
return nil, fmt.Errorf("invalid maximum provided: '%s'", maximum)
}
maj, _ := strconv.ParseUint(parts[0], 10, 64)
min, _ := strconv.ParseUint(parts[1], 10, 64)
upper, err := semver.NewVersion(fmt.Sprintf("%d.%d.0-0", maj, min+1))
if err != nil {
return nil, fmt.Errorf("invalid maximum provided: '%s'", maximum)
}
return upper, nil
default:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cnf/ran/internal/version/semver_range.go` around lines 82 - 98, The
two-segment branch in the function that parses maximums (after
splitSemverCore(s) into core, tail and parts) currently ignores tail and widens
ranges; update the len(parts) == 2 case in that function to check if tail is
non-empty and, if so, return the same invalid-maximum error (e.g.,
fmt.Errorf("invalid maximum provided: '%s'", maximum)) instead of proceeding to
construct a broadened version with semver.NewVersion; keep the existing behavior
for plain "X.Y" (tail empty) and only allow constructing the X.Y.0-0 version
when tail == "".

Copy link
Copy Markdown
Collaborator

@klaskosk klaskosk left a comment

Choose a reason for hiding this comment

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

I suspect it'll be hard to avoid merge conflicts with a PR this big, but those also need fixed before merging. 1 main comments about how we handle maximums, I think it'll simplify the process there. Otherwise just minor things

@@ -0,0 +1,148 @@
package version
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

minor point, but since we're just leaving a single function in the old file, we can just move all this content to the old file instead of having a separate new file

"github.com/Masterminds/semver/v3"
)

// IsVersionStringInRange reports whether version satisfies minimum <= version < maximumUpper using SemVer 2.0
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we can simplify the maximum handling by making it exclusive. Similar to how slicing an array like arr[1:3] means grab elements arr[1] and arr[2] but not arr[3], we could do the same thing with versions. The math term would be a "half-open interval"

The reason this is nicer is that there's a clear minimum for a given y-stream (x.y.0-0), but there's no clear maximum

So if we wanted to do all z-streams for 4.16 and 4.17, we could write the function call as IsVersionStringInRange(myVersionString, "4.16.0-0", "4.18.0-0") and then make sure the parsed myVersion is greater than or equal to (equivalently, not less than) 4.16.0-0 and less than 4.18.0-0

Comment thread go.mod
k8s.io/apiextensions-apiserver v0.34.5
github.com/Masterminds/semver/v3 v3.4.0
github.com/rh-ecosystem-edge/eco-goinfra v0.0.0-20260324223608-9209edd329fe
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: you can add the github.com/Masterminds/semver/v3 v3.4.0 line to the top require block

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants