Skip to content

Improve unit test coverage from 78% to 82% by adding comprehensive tests #2665

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

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions coverage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>filewatcher: Go Coverage Report</title>
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }

</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">

<option value="file0">sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go (82.6%)</option>

</select>
</div>
<div id="legend">
<span>not tracked</span>

<span class="cov0">not covered</span>
<span class="cov8">covered</span>

</div>
</div>
<div id="content">

<pre class="file" id="file0" style="display: none">/*
Copyright 2024 The Kubernetes Authors.

Licensed 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.
*/

package filewatcher

import (
"os"
"path/filepath"
"sync"

"github.com/fsnotify/fsnotify"
"k8s.io/klog/v2"
)

// exit is a separate function to handle program termination
var exit = func(code int) <span class="cov0" title="0">{
os.Exit(code)
}</span>

var watchCertificateFileOnce sync.Once

// resetWatchCertificateFileOnce resets the watchCertificateFileOnce variable. This is used for testing purposes.
func resetWatchCertificateFileOnce() <span class="cov8" title="1">{
watchCertificateFileOnce = sync.Once{}
}</span>

// WatchFileForChanges watches the file, fileToWatch, for changes. If the file contents have changed, the pod this
// function is running on will be restarted.
func WatchFileForChanges(fileToWatch string) error <span class="cov8" title="1">{
var err error

// This starts only one occurrence of the file watcher, which watches the file, fileToWatch.
watchCertificateFileOnce.Do(func() </span><span class="cov8" title="1">{
klog.V(2).Infof("Starting the file change watcher on file, %s", fileToWatch)

// Update the file path to watch in case this is a symlink
fileToWatch, err = filepath.EvalSymlinks(fileToWatch)
if err != nil </span><span class="cov8" title="1">{
return
}</span>
<span class="cov8" title="1">klog.V(2).Infof("Watching file, %s", fileToWatch)

// Start the file watcher to monitor file changes
err = checkForFileChanges(fileToWatch)</span>
})
<span class="cov8" title="1">return err</span>
}

// checkForFileChanges starts a new file watcher. If the file is changed, the pod running this function will exit.
func checkForFileChanges(path string) error <span class="cov8" title="1">{
watcher, err := fsnotify.NewWatcher()
if err != nil </span><span class="cov0" title="0">{
return err
}</span>

<span class="cov8" title="1">go func() </span><span class="cov8" title="1">{
for </span><span class="cov8" title="1">{
select </span>{
case event, ok := &lt;-watcher.Events:<span class="cov8" title="1">
if ok &amp;&amp; (event.Has(fsnotify.Write) || event.Has(fsnotify.Chmod) || event.Has(fsnotify.Remove)) </span><span class="cov8" title="1">{
klog.V(2).Infof("file, %s, was modified, exiting...", event.Name)
exit(0)
}</span>
case err, ok := &lt;-watcher.Errors:<span class="cov0" title="0">
if ok </span><span class="cov0" title="0">{
klog.Errorf("file watcher error: %v", err)
}</span>
}
}
}()

<span class="cov8" title="1">return watcher.Add(path)</span>
}
</pre>

</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>
18 changes: 18 additions & 0 deletions coverage.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
mode: set
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:29.27,31.2 1 0
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:36.38,38.2 1 1
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:42.52,46.37 2 1
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:46.37,51.17 3 1
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:51.17,53.4 1 1
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:54.3,57.41 2 1
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:59.2,59.12 1 1
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:63.45,65.16 2 1
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:65.16,67.3 1 0
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:69.2,69.12 1 1
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:69.12,70.7 1 1
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:70.7,71.11 1 1
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:72.39,73.101 1 1
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:73.101,76.6 2 1
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:77.37,78.11 1 0
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:78.11,80.6 1 0
sigs.k8s.io/azurefile-csi-driver/pkg/filewatcher/filewatcher.go:85.2,85.26 1 1
170 changes: 170 additions & 0 deletions pkg/azurefile-proxy/pb/pb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
Copyright 2024 The Kubernetes Authors.

Licensed 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.
*/

package pb

import (
"context"
"testing"

"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func TestMountAzureFileRequest(t *testing.T) {
req := &MountAzureFileRequest{
Source: "/source/path",
Target: "/target/path",
Fstype: "cifs",
MountOptions: []string{"ro", "noexec"},
SensitiveOptions: []string{"username=test", "password=secret"},
}

// Test getter methods
if req.GetSource() != "/source/path" {
t.Errorf("Expected source '/source/path', got '%s'", req.GetSource())
}

if req.GetTarget() != "/target/path" {
t.Errorf("Expected target '/target/path', got '%s'", req.GetTarget())
}

if req.GetFstype() != "cifs" {
t.Errorf("Expected fstype 'cifs', got '%s'", req.GetFstype())
}

mountOptions := req.GetMountOptions()
if len(mountOptions) != 2 || mountOptions[0] != "ro" || mountOptions[1] != "noexec" {
t.Errorf("Expected mount options ['ro', 'noexec'], got %v", mountOptions)
}

sensitiveOptions := req.GetSensitiveOptions()
if len(sensitiveOptions) != 2 || sensitiveOptions[0] != "username=test" || sensitiveOptions[1] != "password=secret" {
t.Errorf("Expected sensitive options ['username=test', 'password=secret'], got %v", sensitiveOptions)
}

// Test String() method
str := req.String()
if str == "" {
t.Error("String() method should return non-empty string")
}

// Test Reset() method
req.Reset()
if req.GetSource() != "" || req.GetTarget() != "" || req.GetFstype() != "" {
t.Error("Reset() should clear all fields")
}
}

func TestMountAzureFileResponse(t *testing.T) {

Check failure on line 73 in pkg/azurefile-proxy/pb/pb_test.go

View workflow job for this annotation

GitHub Actions / Go Lint

unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _ (revive)

Check failure on line 73 in pkg/azurefile-proxy/pb/pb_test.go

View workflow job for this annotation

GitHub Actions / Go Lint

unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _ (revive)
resp := &MountAzureFileResponse{}

// Test String() method - it's ok if it returns empty string for empty struct
str := resp.String()
// Just ensure it doesn't panic - empty string is acceptable
_ = str

// Test Reset() method - should not panic
resp.Reset()
// No fields to verify after reset since MountAzureFileResponse has no public fields
}

func TestUnimplementedMountServiceServer(t *testing.T) {
server := &UnimplementedMountServiceServer{}

// Test that the unimplemented method returns proper error
req := &MountAzureFileRequest{}
resp, err := server.MountAzureFile(context.Background(), req)

if resp != nil {
t.Error("Expected nil response from unimplemented method")
}

if err == nil {
t.Error("Expected error from unimplemented method")
}

// Verify it's the correct gRPC error
st, ok := status.FromError(err)
if !ok {
t.Error("Expected gRPC status error")
}

if st.Code() != codes.Unimplemented {
t.Errorf("Expected Unimplemented error code, got %v", st.Code())
}
}

// Mock client connection for testing
type mockClientConn struct {
grpc.ClientConnInterface
invokeFunc func(ctx context.Context, method string, args interface{}, reply interface{}, opts ...grpc.CallOption) error
}

func (m *mockClientConn) Invoke(ctx context.Context, method string, args interface{}, reply interface{}, opts ...grpc.CallOption) error {
if m.invokeFunc != nil {
return m.invokeFunc(ctx, method, args, reply, opts...)
}
return nil
}

func TestMountServiceClient(t *testing.T) {
// Test successful call
mockConn := &mockClientConn{
invokeFunc: func(ctx context.Context, method string, args interface{}, reply interface{}, opts ...grpc.CallOption) error {

Check failure on line 128 in pkg/azurefile-proxy/pb/pb_test.go

View workflow job for this annotation

GitHub Actions / Go Lint

unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)

Check failure on line 128 in pkg/azurefile-proxy/pb/pb_test.go

View workflow job for this annotation

GitHub Actions / Go Lint

unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)
if method != "/MountService/MountAzureFile" {
t.Errorf("Expected method '/MountService/MountAzureFile', got '%s'", method)
}

Check failure on line 132 in pkg/azurefile-proxy/pb/pb_test.go

View workflow job for this annotation

GitHub Actions / Go Lint

File is not properly formatted (gofmt)

Check failure on line 132 in pkg/azurefile-proxy/pb/pb_test.go

View workflow job for this annotation

GitHub Actions / Go Lint

File is not properly formatted (gofmt)
// Just return success - response has no fields to set
return nil
},
}

client := NewMountServiceClient(mockConn)
req := &MountAzureFileRequest{
Source: "/test",
Target: "/mount",
}

resp, err := client.MountAzureFile(context.Background(), req)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

if resp == nil {
t.Error("Expected non-nil response")
}

// Test error case
mockConnError := &mockClientConn{
invokeFunc: func(ctx context.Context, method string, args interface{}, reply interface{}, opts ...grpc.CallOption) error {

Check failure on line 155 in pkg/azurefile-proxy/pb/pb_test.go

View workflow job for this annotation

GitHub Actions / Go Lint

unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)

Check failure on line 155 in pkg/azurefile-proxy/pb/pb_test.go

View workflow job for this annotation

GitHub Actions / Go Lint

unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)
return status.Error(codes.Internal, "test error")
},
}

clientError := NewMountServiceClient(mockConnError)
resp, err = clientError.MountAzureFile(context.Background(), req)

if err == nil {
t.Error("Expected error from client call")
}

if resp != nil {
t.Error("Expected nil response on error")
}
}
Loading
Loading