-
Notifications
You must be signed in to change notification settings - Fork 24
feat: add unit tests and Codecov integration #202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # | ||
| # Copyright (c) 2020-2024 Red Hat, Inc. | ||
| # This program and the accompanying materials are made | ||
| # available under the terms of the Eclipse Public License 2.0 | ||
| # which is available at https://www.eclipse.org/legal/epl-2.0/ | ||
| # | ||
| # SPDX-License-Identifier: EPL-2.0 | ||
| # | ||
| name: Unit Tests & Coverage | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| pull_request: | ||
| branches: [main] | ||
|
|
||
| jobs: | ||
| unit-tests: | ||
| name: Run unit tests | ||
| runs-on: ubuntu-24.04 | ||
| permissions: | ||
| id-token: write | ||
|
|
||
| steps: | ||
| - name: Check out code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Go | ||
| uses: actions/setup-go@v5 | ||
| with: | ||
| go-version-file: go.mod | ||
| cache: true | ||
|
|
||
| - name: Run unit tests with coverage | ||
| run: make test-coverage | ||
|
|
||
| - name: Upload coverage to Codecov | ||
| uses: codecov/codecov-action@v6 | ||
| with: | ||
| use_oidc: true | ||
| flags: unit-tests | ||
| files: ./coverage.out | ||
| fail_ci_if_error: false |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| .idea | ||
| _output | ||
| generated | ||
| coverage.out |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| codecov: | ||
| require_ci_to_pass: yes | ||
|
|
||
| coverage: | ||
| precision: 2 | ||
| round: down | ||
| range: "50...100" | ||
|
|
||
| status: | ||
| project: | ||
| default: | ||
| target: auto | ||
| threshold: 1% | ||
| patch: | ||
| default: | ||
| target: auto | ||
| threshold: 1% | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just checking what behavior we're expecting from the Codecov configuration. Is
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is intentional and follows the standard configuration from the Code Coverage Onboarding Guide. target: auto sets the target to the base commit's coverage (not a fixed minimum), and threshold: 1% means the Codecov status check passes as long as coverage doesn't drop more than 1% from the base. So it's reporting + soft enforcement. It flags regressions but doesn't impose a fixed floor. |
||
|
|
||
| ignore: | ||
| - "vendor/**" | ||
| - "build/**" | ||
| - "hack/**" | ||
| - "docs/**" | ||
| - "manifests/**" | ||
| - "metadata/**" | ||
| - "dev-scripts/**" | ||
|
|
||
| flags: | ||
| unit-tests: | ||
| carryforward: true | ||
|
|
||
| comment: | ||
| layout: "reach,diff,flags,files" | ||
| behavior: default | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| // | ||
| // Copyright (c) 2021-2024 Red Hat, Inc. | ||
| // This program and the accompanying materials are made | ||
| // available under the terms of the Eclipse Public License 2.0 | ||
| // which is available at https://www.eclipse.org/legal/epl-2.0/ | ||
| // | ||
| // SPDX-License-Identifier: EPL-2.0 | ||
| // | ||
|
|
||
| package config | ||
|
|
||
| import ( | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestGetDefaultToolingImage(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| envVal string | ||
| want string | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "returns image when env var is set", | ||
| envVal: "quay.io/wto/web-terminal-tooling:latest", | ||
| want: "quay.io/wto/web-terminal-tooling:latest", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "returns error when env var is unset", | ||
| envVal: "", | ||
| want: "", | ||
| wantErr: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| t.Setenv(toolingImageEnvVar, tt.envVal) | ||
| got, err := GetDefaultToolingImage() | ||
| if (err != nil) != tt.wantErr { | ||
| t.Errorf("GetDefaultToolingImage() error = %v, wantErr %v", err, tt.wantErr) | ||
| return | ||
| } | ||
| if got != tt.want { | ||
| t.Errorf("GetDefaultToolingImage() = %v, want %v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestGetDefaultExecImage(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| envVal string | ||
| want string | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "returns image when env var is set", | ||
| envVal: "quay.io/wto/web-terminal-exec:latest", | ||
| want: "quay.io/wto/web-terminal-exec:latest", | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "returns error when env var is unset", | ||
| envVal: "", | ||
| want: "", | ||
| wantErr: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| t.Setenv(execImageEnvVar, tt.envVal) | ||
| got, err := GetDefaultExecImage() | ||
| if (err != nil) != tt.wantErr { | ||
| t.Errorf("GetDefaultExecImage() error = %v, wantErr %v", err, tt.wantErr) | ||
| return | ||
| } | ||
| if got != tt.want { | ||
| t.Errorf("GetDefaultExecImage() = %v, want %v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestGetNamespaceReturnsErrorOutsideCluster(t *testing.T) { | ||
| _, err := GetNamespace() | ||
| if err == nil { | ||
| t.Fatal("expected error when service account namespace file does not exist") | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| // | ||
| // Copyright (c) 2021-2024 Red Hat, Inc. | ||
| // This program and the accompanying materials are made | ||
| // available under the terms of the Eclipse Public License 2.0 | ||
| // which is available at https://www.eclipse.org/legal/epl-2.0/ | ||
| // | ||
| // SPDX-License-Identifier: EPL-2.0 | ||
| // | ||
|
|
||
| package webterminal | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" | ||
| v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
|
|
||
| "github.com/redhat-developer/web-terminal-operator/pkg/config" | ||
| ) | ||
|
|
||
| func makeExecTemplate(image string) *dw.DevWorkspaceTemplate { | ||
| return &dw.DevWorkspaceTemplate{ | ||
| ObjectMeta: v1.ObjectMeta{ | ||
| Name: config.ExecTemplateName, | ||
| Namespace: "test-ns", | ||
| }, | ||
| Spec: dw.DevWorkspaceTemplateSpec{ | ||
| DevWorkspaceTemplateSpecContent: dw.DevWorkspaceTemplateSpecContent{ | ||
| Components: []dw.Component{ | ||
| { | ||
| Name: config.ExecTemplateName, | ||
| ComponentUnion: dw.ComponentUnion{ | ||
| Container: &dw.ContainerComponent{ | ||
| Container: dw.Container{ | ||
| Image: image, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func TestHandleUnmanagedExecState(t *testing.T) { | ||
| const defaultImage = "quay.io/wto/web-terminal-exec:latest" | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| specImage string | ||
| clusterImage string | ||
| expectedImage string | ||
| }{ | ||
| { | ||
| name: "same image returns cluster unchanged", | ||
| specImage: defaultImage, | ||
| clusterImage: defaultImage, | ||
| expectedImage: defaultImage, | ||
| }, | ||
| { | ||
| name: "same repo different tag updates image", | ||
| specImage: defaultImage, | ||
| clusterImage: "quay.io/wto/web-terminal-exec:old", | ||
| expectedImage: defaultImage, | ||
| }, | ||
| { | ||
| name: "same repo different digest updates image", | ||
| specImage: defaultImage, | ||
| clusterImage: "quay.io/wto/web-terminal-exec@sha256:abc123", | ||
| expectedImage: defaultImage, | ||
| }, | ||
| { | ||
| name: "different repo does not update", | ||
| specImage: defaultImage, | ||
| clusterImage: "quay.io/other/custom-exec:latest", | ||
| expectedImage: "quay.io/other/custom-exec:latest", | ||
| }, | ||
| { | ||
| name: "empty cluster image returns cluster unchanged", | ||
| specImage: defaultImage, | ||
| clusterImage: "", | ||
| expectedImage: "", | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| t.Setenv("RELATED_IMAGE_web_terminal_exec", tt.specImage) | ||
|
|
||
| spec := makeExecTemplate(tt.specImage) | ||
| cluster := makeExecTemplate(tt.clusterImage) | ||
|
|
||
| result := handleUnmanagedExecState(spec, cluster) | ||
|
|
||
| var resultImage string | ||
| for _, component := range result.Spec.Components { | ||
| if component.Name == config.ExecTemplateName && component.Container != nil { | ||
| resultImage = component.Container.Image | ||
| } | ||
| } | ||
| if resultImage != tt.expectedImage { | ||
| t.Errorf("expected image %q, got %q", tt.expectedImage, resultImage) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestHandleUnmanagedExecStateEnvUnset(t *testing.T) { | ||
| t.Setenv("RELATED_IMAGE_web_terminal_exec", "") | ||
|
|
||
| spec := makeExecTemplate("any-image") | ||
| cluster := makeExecTemplate("quay.io/wto/web-terminal-exec:old") | ||
|
|
||
| result := handleUnmanagedExecState(spec, cluster) | ||
|
|
||
| var resultImage string | ||
| for _, component := range result.Spec.Components { | ||
| if component.Name == config.ExecTemplateName && component.Container != nil { | ||
| resultImage = component.Container.Image | ||
| } | ||
| } | ||
| if resultImage != "quay.io/wto/web-terminal-exec:old" { | ||
| t.Errorf("expected cluster image unchanged when env var unset, got %q", resultImage) | ||
| } | ||
| } | ||
|
|
||
| func TestGetSpecExecTemplate(t *testing.T) { | ||
| const testImage = "quay.io/wto/web-terminal-exec:test" | ||
| t.Setenv("RELATED_IMAGE_web_terminal_exec", testImage) | ||
|
|
||
| dwt, err := getSpecExecTemplate("test-namespace") | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
| if dwt.Name != config.ExecTemplateName { | ||
| t.Errorf("expected name %q, got %q", config.ExecTemplateName, dwt.Name) | ||
| } | ||
| if dwt.Namespace != "test-namespace" { | ||
| t.Errorf("expected namespace %q, got %q", "test-namespace", dwt.Namespace) | ||
| } | ||
| if len(dwt.Spec.Components) != 1 { | ||
| t.Fatalf("expected 1 component, got %d", len(dwt.Spec.Components)) | ||
| } | ||
| if dwt.Spec.Components[0].Container.Image != testImage { | ||
| t.Errorf("expected image %q, got %q", testImage, dwt.Spec.Components[0].Container.Image) | ||
| } | ||
| } | ||
|
|
||
| func TestGetSpecExecTemplateEnvUnset(t *testing.T) { | ||
| t.Setenv("RELATED_IMAGE_web_terminal_exec", "") | ||
|
|
||
| _, err := getSpecExecTemplate("test-namespace") | ||
| if err == nil { | ||
| t.Error("expected error when env var is unset") | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added .PHONY declaration. Thanks.