Skip to content

Commit 1e513ca

Browse files
test: Improve registry+v1 render regression test (#2103)
1 parent 1530e34 commit 1e513ca

File tree

59 files changed

+196
-19
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+196
-19
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: test-regression
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
merge_group:
7+
push:
8+
branches:
9+
- main
10+
11+
jobs:
12+
test-regression:
13+
runs-on: ubuntu-latest
14+
steps:
15+
16+
- uses: actions/checkout@v4
17+
18+
- uses: actions/setup-go@v5
19+
with:
20+
go-version-file: go.mod
21+
22+
- name: Run regression tests
23+
run: |
24+
make test-regression
25+
26+
- uses: codecov/[email protected]
27+
with:
28+
disable_search: true
29+
files: coverage/regression.out
30+
flags: unit
31+
token: ${{ secrets.CODECOV_TOKEN }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@ site
4242
.tiltbuild/
4343
.catalogd-tmp/
4444
.vscode
45+
46+
# Tmporary files and directories
47+
/test/regression/convert/testdata/tmp/*

Makefile

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -172,15 +172,9 @@ generate: $(CONTROLLER_GEN) #EXHELP Generate code containing DeepCopy, DeepCopyI
172172
$(CONTROLLER_GEN) --load-build-tags=$(GO_BUILD_TAGS) object:headerFile="hack/boilerplate.go.txt" paths="./..."
173173

174174
.PHONY: verify
175-
verify: k8s-pin kind-verify-versions fmt generate manifests crd-ref-docs generate-test-data #HELP Verify all generated code is up-to-date. Runs k8s-pin instead of just tidy.
175+
verify: k8s-pin kind-verify-versions fmt generate manifests crd-ref-docs #HELP Verify all generated code is up-to-date. Runs k8s-pin instead of just tidy.
176176
git diff --exit-code
177177

178-
# Renders registry+v1 bundles in test/convert
179-
# Used by CI in verify to catch regressions in the registry+v1 -> plain conversion code
180-
.PHONY: generate-test-data
181-
generate-test-data:
182-
go run test/convert/generate-manifests.go
183-
184178
.PHONY: fix-lint
185179
fix-lint: $(GOLANGCI_LINT) #EXHELP Fix lint issues
186180
$(GOLANGCI_LINT) run --fix --build-tags $(GO_BUILD_TAGS) $(GOLANGCI_LINT_ARGS)
@@ -209,7 +203,7 @@ verify-crd-compatibility: $(CRD_DIFF) manifests
209203
#SECTION Test
210204

211205
.PHONY: test
212-
test: manifests generate fmt lint test-unit test-e2e #HELP Run all tests.
206+
test: manifests generate fmt lint test-unit test-e2e test-regression #HELP Run all tests.
213207

214208
.PHONY: e2e
215209
e2e: #EXHELP Run the e2e tests.
@@ -252,6 +246,12 @@ test-unit: $(SETUP_ENVTEST) envtest-k8s-bins #HELP Run the unit tests
252246
$(UNIT_TEST_DIRS) \
253247
-test.gocoverdir=$(COVERAGE_UNIT_DIR)
254248

249+
COVERAGE_REGRESSION_DIR := $(ROOT_DIR)/coverage/regression
250+
.PHONY: test-regression
251+
test-regression: #HELP Run regression test
252+
rm -rf $(COVERAGE_REGRESSION_DIR) && mkdir -p $(COVERAGE_REGRESSION_DIR)
253+
go test -count=1 -v ./test/regression/... -cover -coverprofile ${ROOT_DIR}/coverage/regression.out -test.gocoverdir=$(COVERAGE_REGRESSION_DIR)
254+
255255
.PHONY: image-registry
256256
E2E_REGISTRY_IMAGE=localhost/e2e-test-registry:devel
257257
image-registry: export GOOS=linux

codecov.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
codecov:
22
notify:
3-
after_n_builds: 3
3+
# Configure the 4 builds to wait before sending a notification.
4+
# test-unit, test-regression, test-e2e and test-experimental-e2e.
5+
after_n_builds: 4
46

57
# Configure the paths to include in coverage reports.
68
# Exclude documentation, YAML configurations, and test files.

test/convert/README.md

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
## registry+v1 bundle regression test
3+
4+
This test in convert_test.go verifies that rendering registry+v1 bundles to manifests
5+
always produces the same files and content.
6+
7+
It runs: go run generate-manifests.go -output-dir=./testdata/tmp/rendered/
8+
Then compares: ./testdata/tmp/rendered/ vs ./testdata/expected-manifests/
9+
10+
Files are sorted by kind/namespace/name for consistency.
11+
12+
To update expected output (only on purpose), run:
13+
14+
go run generate-manifests.go -output-dir=./testdata/expected-manifests/
15+
*/
16+
package main
17+
18+
import (
19+
"bytes"
20+
"fmt"
21+
"os"
22+
"os/exec"
23+
"path/filepath"
24+
"testing"
25+
26+
"github.com/google/go-cmp/cmp"
27+
"github.com/stretchr/testify/require"
28+
)
29+
30+
// Test_RenderedOutputMatchesExpected runs generate-manifests.go,
31+
// then compares the generated files in ./testdata/tmp/rendered/
32+
// against expected-manifests/.
33+
// It fails if any file differs or is missing.
34+
// TMP dir is cleaned up after test ends.
35+
func Test_RenderedOutput_MatchesExpected(t *testing.T) {
36+
tmpRoot := "./testdata/tmp/rendered/"
37+
expectedRoot := "./testdata/expected-manifests/"
38+
39+
// Remove the temporary output directory always
40+
t.Cleanup(func() {
41+
_ = os.RemoveAll("./testdata/tmp")
42+
})
43+
44+
// Call the generate-manifests.go script to generate the manifests
45+
// in the temporary directory.
46+
cmd := exec.Command("go", "run", "generate-manifests.go", "-output-dir="+tmpRoot)
47+
cmd.Stderr = os.Stderr
48+
cmd.Stdout = os.Stdout
49+
50+
err := cmd.Run()
51+
require.NoError(t, err, "failed to generate manifests")
52+
53+
// Compare structure + contents
54+
err = compareDirs(expectedRoot, tmpRoot)
55+
require.NoError(t, err, "rendered output differs from expected")
56+
}
57+
58+
// compareDirs compares expectedRootPath and generatedRootPath directories recursively.
59+
// It returns an error if any file is missing, extra, or has content mismatch.
60+
// On mismatch, it includes a detailed diff using cmp.Diff.
61+
func compareDirs(expectedRootPath, generatedRootPath string) error {
62+
// Step 1: Ensure every expected file exists in actual and contents match
63+
err := filepath.Walk(expectedRootPath, func(expectedPath string, info os.FileInfo, err error) error {
64+
if err != nil {
65+
return err
66+
}
67+
if info.IsDir() {
68+
return nil
69+
}
70+
71+
relPath, err := filepath.Rel(expectedRootPath, expectedPath)
72+
if err != nil {
73+
return err
74+
}
75+
actualPath := filepath.Join(generatedRootPath, relPath)
76+
77+
expectedBytes, err := os.ReadFile(expectedPath)
78+
if err != nil {
79+
return fmt.Errorf("failed to read expected file: %s", expectedPath)
80+
}
81+
actualBytes, err := os.ReadFile(actualPath)
82+
if err != nil {
83+
return fmt.Errorf("missing file: %s", relPath)
84+
}
85+
86+
if !bytes.Equal(expectedBytes, actualBytes) {
87+
diff := cmp.Diff(string(expectedBytes), string(actualBytes))
88+
return fmt.Errorf("file content mismatch at: %s\nDiff (-expected +actual):\n%s", relPath, diff)
89+
}
90+
return nil
91+
})
92+
if err != nil {
93+
return err
94+
}
95+
96+
// Step 2: Ensure actual does not contain unexpected files
97+
err = filepath.Walk(generatedRootPath, func(actualPath string, info os.FileInfo, err error) error {
98+
if err != nil {
99+
return err
100+
}
101+
if info.IsDir() {
102+
return nil
103+
}
104+
105+
relPath, err := filepath.Rel(generatedRootPath, actualPath)
106+
if err != nil {
107+
return err
108+
}
109+
expectedPath := filepath.Join(expectedRootPath, relPath)
110+
111+
_, err = os.Stat(expectedPath)
112+
if os.IsNotExist(err) {
113+
return fmt.Errorf("unexpected extra file: %s", relPath)
114+
} else if err != nil {
115+
return fmt.Errorf("error checking expected file: %s", expectedPath)
116+
}
117+
return nil
118+
})
119+
return err
120+
}

test/convert/generate-manifests.go renamed to test/regression/convert/generate-manifests.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1+
// generate-manifests.go
2+
//
3+
// Renders registry+v1 bundles into YAML manifests for regression testing.
4+
// Used by tests to make sure output from the BundleRenderer stays consistent.
5+
//
6+
// By default, writes to ./testdata/tmp/generate/.
7+
// To update expected output, run:
8+
//
9+
// go run generate-manifests.go -output-dir=./testdata/expected-manifests/
10+
//
11+
// Only re-generate if you intentionally change rendering behavior.
12+
// Note that if the test fails is likely a regression in the renderer.
113
package main
214

315
import (
416
"cmp"
17+
"flag"
518
"fmt"
619
"os"
720
"path/filepath"
@@ -16,11 +29,26 @@ import (
1629
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1"
1730
)
1831

32+
// This is a helper for a regression test to make sure the renderer output doesn't change.
33+
//
34+
// It renders known bundles into YAML files and writes them to a target output dir.
35+
// By default, it writes to a temp path used in tests:
36+
//
37+
// ./testdata/tmp/rendered/
38+
//
39+
// If you want to update the expected output, run it with:
40+
//
41+
// go run generate-manifests.go -output-dir=./testdata/expected-manifests/
42+
//
43+
// Note: Expected output should never change unless the renderer changes which is unlikely.
44+
// If the convert_test.go test fails, it likely means a regression was introduced in the renderer.
1945
func main() {
2046
bundleRootDir := "testdata/bundles/"
21-
outputRootDir := "test/convert/expected-manifests/"
47+
defaultOutputDir := "./testdata/tmp/rendered/"
48+
outputRootDir := flag.String("output-dir", defaultOutputDir, "path to write rendered manifests to")
49+
flag.Parse()
2250

23-
if err := os.RemoveAll(outputRootDir); err != nil {
51+
if err := os.RemoveAll(*outputRootDir); err != nil {
2452
fmt.Printf("error removing output directory: %v\n", err)
2553
os.Exit(1)
2654
}
@@ -52,7 +80,7 @@ func main() {
5280
},
5381
} {
5482
bundlePath := filepath.Join(bundleRootDir, tc.bundle)
55-
generatedManifestPath := filepath.Join(outputRootDir, tc.bundle, tc.testCaseName)
83+
generatedManifestPath := filepath.Join(*outputRootDir, tc.bundle, tc.testCaseName)
5684
if err := generateManifests(generatedManifestPath, bundlePath, tc.installNamespace, tc.watchNamespace); err != nil {
5785
fmt.Printf("Error generating manifests: %v", err)
5886
os.Exit(1)

0 commit comments

Comments
 (0)