Repository: kubernetes-sigs/security-profiles-operator
Pull request #527 (2021-07-06 09:25:08)

Add more enricher unit tests
#### What type of PR is this?


/kind cleanup


#### What this PR does / why we need it:
The enricher now features more unit tests also covering failure cases.

#### Which issue(s) this PR fixes:

<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.

Fixes #

or

None
-->
None
#### Does this PR have test?
Yes
<!--
If tests aren't applicable just write N/A.
-->

#### Special notes for your reviewer:
None
#### Does this PR introduce a user-facing change?

<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
-->

```release-note
None
```


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/527#" title="Author self-approved">saschagrunert</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/security-profiles-operator/blob/master/OWNERS)~~ [saschagrunert]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
codecov-commenter: # [Codecov](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/527?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#527](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/527?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (fcc7f35) into [master](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/commit/14a57bebc6eba11660d8ed390a952ee2c215265f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (14a57be) will **increase** coverage by `1.12%`.
> The diff coverage is `66.66%`.


```diff
@@            Coverage Diff             @@
##           master     #527      +/-   ##
==========================================
+ Coverage   42.81%   43.93%   +1.12%     
==========================================
  Files          24       24              
  Lines        1294     1295       +1     
==========================================
+ Hits          554      569      +15     
+ Misses        699      691       -8     
+ Partials       41       35       -6     
```

saschagrunert: Had to rebase to resolve the conflicts.
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/527#pullrequestreview-701411401" title="LGTM">jhrozek</a>*, *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/527#" title="Author self-approved">saschagrunert</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/security-profiles-operator/blob/master/OWNERS)~~ [saschagrunert]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/internal/pkg/daemon/enricher/enricher.go b/internal/pkg/daemon/enricher/enricher.go
index 99050afb1..dacf4d197 100644
--- a/internal/pkg/daemon/enricher/enricher.go
+++ b/internal/pkg/daemon/enricher/enricher.go
@@ -59,7 +59,7 @@ func (e *Enricher) Run() error {
 	for _, cache := range []ttlcache.SimpleCache{
 		e.containerIDCache, e.infoCache,
 	} {
-		if err := cache.SetTTL(defaultCacheTimeout); err != nil {
+		if err := e.impl.SetTTL(cache, defaultCacheTimeout); err != nil {
 			return errors.Wrap(err, "set cache timeout")
 		}
 		defer cache.Close()
@@ -195,5 +195,5 @@ func (e *Enricher) Run() error {
 		}
 	}
 
-	return errors.Wrap(tailFile.Err(), "enricher failed")
+	return errors.New("enricher failed")
 }
diff --git a/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go b/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go
index e00da7c3d..4c6d9381e 100644
--- a/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go
+++ b/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go
@@ -20,7 +20,9 @@ package enricherfakes
 import (
 	"os"
 	"sync"
+	"time"
 
+	ttlcache "github.com/ReneKroon/ttlcache/v2"
 	"github.com/nxadm/tail"
 	"google.golang.org/grpc"
 	v1 "k8s.io/api/core/v1"
@@ -177,6 +179,18 @@ type FakeImpl struct {
 	sendSyscallReturnsOnCall map[int]struct {
 		result1 error
 	}
+	SetTTLStub        func(ttlcache.SimpleCache, time.Duration) error
+	setTTLMutex       sync.RWMutex
+	setTTLArgsForCall []struct {
+		arg1 ttlcache.SimpleCache
+		arg2 time.Duration
+	}
+	setTTLReturns struct {
+		result1 error
+	}
+	setTTLReturnsOnCall map[int]struct {
+		result1 error
+	}
 	TailFileStub        func(string, tail.Config) (*tail.Tail, error)
 	tailFileMutex       sync.RWMutex
 	tailFileArgsForCall []struct {
@@ -935,6 +949,68 @@ func (fake *FakeImpl) SendSyscallReturnsOnCall(i int, result1 error) {
 	}{result1}
 }
 
+func (fake *FakeImpl) SetTTL(arg1 ttlcache.SimpleCache, arg2 time.Duration) error {
+	fake.setTTLMutex.Lock()
+	ret, specificReturn := fake.setTTLReturnsOnCall[len(fake.setTTLArgsForCall)]
+	fake.setTTLArgsForCall = append(fake.setTTLArgsForCall, struct {
+		arg1 ttlcache.SimpleCache
+		arg2 time.Duration
+	}{arg1, arg2})
+	stub := fake.SetTTLStub
+	fakeReturns := fake.setTTLReturns
+	fake.recordInvocation("SetTTL", []interface{}{arg1, arg2})
+	fake.setTTLMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *FakeImpl) SetTTLCallCount() int {
+	fake.setTTLMutex.RLock()
+	defer fake.setTTLMutex.RUnlock()
+	return len(fake.setTTLArgsForCall)
+}
+
+func (fake *FakeImpl) SetTTLCalls(stub func(ttlcache.SimpleCache, time.Duration) error) {
+	fake.setTTLMutex.Lock()
+	defer fake.setTTLMutex.Unlock()
+	fake.SetTTLStub = stub
+}
+
+func (fake *FakeImpl) SetTTLArgsForCall(i int) (ttlcache.SimpleCache, time.Duration) {
+	fake.setTTLMutex.RLock()
+	defer fake.setTTLMutex.RUnlock()
+	argsForCall := fake.setTTLArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *FakeImpl) SetTTLReturns(result1 error) {
+	fake.setTTLMutex.Lock()
+	defer fake.setTTLMutex.Unlock()
+	fake.SetTTLStub = nil
+	fake.setTTLReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *FakeImpl) SetTTLReturnsOnCall(i int, result1 error) {
+	fake.setTTLMutex.Lock()
+	defer fake.setTTLMutex.Unlock()
+	fake.SetTTLStub = nil
+	if fake.setTTLReturnsOnCall == nil {
+		fake.setTTLReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.setTTLReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
 func (fake *FakeImpl) TailFile(arg1 string, arg2 tail.Config) (*tail.Tail, error) {
 	fake.tailFileMutex.Lock()
 	ret, specificReturn := fake.tailFileReturnsOnCall[len(fake.tailFileArgsForCall)]
@@ -1027,6 +1103,8 @@ func (fake *FakeImpl) Invocations() map[string][][]interface{} {
 	defer fake.sendMetricMutex.RUnlock()
 	fake.sendSyscallMutex.RLock()
 	defer fake.sendSyscallMutex.RUnlock()
+	fake.setTTLMutex.RLock()
+	defer fake.setTTLMutex.RUnlock()
 	fake.tailFileMutex.RLock()
 	defer fake.tailFileMutex.RUnlock()
 	copiedInvocations := map[string][][]interface{}{}
diff --git a/internal/pkg/daemon/enricher/impl.go b/internal/pkg/daemon/enricher/impl.go
index bbd49f761..3cf3fe436 100644
--- a/internal/pkg/daemon/enricher/impl.go
+++ b/internal/pkg/daemon/enricher/impl.go
@@ -19,7 +19,9 @@ package enricher
 import (
 	"context"
 	"os"
+	"time"
 
+	"github.com/ReneKroon/ttlcache/v2"
 	"github.com/nxadm/tail"
 	"google.golang.org/grpc"
 	v1 "k8s.io/api/core/v1"
@@ -36,6 +38,7 @@ type defaultImpl struct{}
 //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
 //counterfeiter:generate . impl
 type impl interface {
+	SetTTL(cache ttlcache.SimpleCache, ttl time.Duration) error
 	Getenv(key string) string
 	Dial() (*grpc.ClientConn, error)
 	Close(*grpc.ClientConn) error
@@ -61,6 +64,10 @@ type impl interface {
 	) error
 }
 
+func (d *defaultImpl) SetTTL(cache ttlcache.SimpleCache, ttl time.Duration) error {
+	return cache.SetTTL(ttl)
+}
+
 func (d *defaultImpl) Getenv(key string) string {
 	return os.Getenv(key)
 }


Test patch:
diff --git a/internal/pkg/daemon/enricher/enricher_test.go b/internal/pkg/daemon/enricher/enricher_test.go
index e3e23d507..09edacb3f 100644
--- a/internal/pkg/daemon/enricher/enricher_test.go
+++ b/internal/pkg/daemon/enricher/enricher_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package enricher
 
 import (
+	"errors"
 	"os"
 	"testing"
 	"time"
@@ -48,10 +49,11 @@ const (
 		"crio-" + containerID + "\n"
 )
 
+var errTest = errors.New("test")
+
 func TestRun(t *testing.T) {
 	t.Parallel()
 
-	lineChan := make(chan *tail.Line)
 	f, err := os.CreateTemp("", "cgroup-")
 	require.Nil(t, err)
 	_, err = f.WriteString(cgroupLine)
@@ -61,11 +63,13 @@ func TestRun(t *testing.T) {
 	defer os.Remove(cgroupFilePath)
 
 	for _, tc := range []struct {
-		prepare func(*enricherfakes.FakeImpl)
-		assert  func(*enricherfakes.FakeImpl, error)
+		runAsync bool
+		prepare  func(*enricherfakes.FakeImpl, chan *tail.Line)
+		assert   func(*enricherfakes.FakeImpl, chan *tail.Line, error)
 	}{
 		{ // success
-			prepare: func(mock *enricherfakes.FakeImpl) {
+			runAsync: true,
+			prepare: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line) {
 				mock.GetenvReturns(node)
 				mock.LinesReturns(lineChan)
 				mock.OpenCalls(func(string) (*os.File, error) {
@@ -83,7 +87,7 @@ func TestRun(t *testing.T) {
 					},
 				}}}, nil)
 			},
-			assert: func(mock *enricherfakes.FakeImpl, err error) {
+			assert: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line, err error) {
 				for mock.LinesCallCount() != 1 {
 					// Wait for Lines() to be called
 				}
@@ -104,18 +108,120 @@ func TestRun(t *testing.T) {
 				require.Equal(t, executable, res.Executable)
 				require.Equal(t, syscall, res.Syscall)
 
+				require.Nil(t, err)
+			},
+		},
+		{ // failure on SetTTL
+			runAsync: false,
+			prepare: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line) {
+				mock.SetTTLReturns(errTest)
+			},
+			assert: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line, err error) {
+				require.NotNil(t, err)
+			},
+		},
+		{ // failure on Getenv
+			runAsync: false,
+			prepare: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line) {
+				mock.GetenvReturns("")
+			},
+			assert: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line, err error) {
+				require.NotNil(t, err)
+			},
+		},
+		{ // failure on Dial
+			runAsync: false,
+			prepare: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line) {
+				mock.GetenvReturns(node)
+				mock.DialReturns(nil, errTest)
+			},
+			assert: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line, err error) {
+				require.NotNil(t, err)
+			},
+		},
+		{ // failure on MetricsAuditInc
+			runAsync: false,
+			prepare: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line) {
+				mock.GetenvReturns(node)
+				mock.MetricsAuditIncReturns(nil, errTest)
+			},
+			assert: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line, err error) {
+				require.NotNil(t, err)
+			},
+		},
+		{ // failure on Tail
+			runAsync: false,
+			prepare: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line) {
+				mock.GetenvReturns(node)
+				mock.TailFileReturns(nil, errTest)
+			},
+			assert: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line, err error) {
+				require.NotNil(t, err)
+			},
+		},
+		{ // failure on Lines
+			runAsync: false,
+			prepare: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line) {
+				mock.GetenvReturns(node)
+				close(lineChan)
+				mock.LinesReturns(lineChan)
+			},
+			assert: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line, err error) {
+				require.NotNil(t, err)
+			},
+		},
+		{ // success, but metrics send failed
+			runAsync: true,
+			prepare: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line) {
+				mock.GetenvReturns(node)
+				mock.LinesReturns(lineChan)
+				mock.OpenCalls(func(string) (*os.File, error) {
+					return os.Open(cgroupFilePath)
+				})
+				mock.ListPodsReturns(&v1.PodList{Items: []v1.Pod{{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      pod,
+						Namespace: namespace,
+					},
+					Status: v1.PodStatus{
+						ContainerStatuses: []v1.ContainerStatus{{
+							ContainerID: crioPrefix + containerID,
+						}},
+					},
+				}}}, nil)
+				mock.SendMetricReturns(errTest)
+			},
+			assert: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line, err error) {
+				for mock.LinesCallCount() != 1 {
+					// Wait for Lines() to be called
+				}
+
+				lineChan <- &tail.Line{
+					Text: seccompLine,
+					Time: time.Now(),
+				}
+
+				for mock.SendMetricCallCount() != 1 {
+					// Wait for MetricsAuditIncCallCount to be called
+				}
+
 				require.Nil(t, err)
 			},
 		},
 	} {
+		lineChan := make(chan *tail.Line)
 		mock := &enricherfakes.FakeImpl{}
-		tc.prepare(mock)
+		tc.prepare(mock, lineChan)
 
 		sut := New(logr.DiscardLogger{})
 		sut.impl = mock
 
 		var err error
-		go func() { err = sut.Run() }()
-		tc.assert(mock, err)
+		if tc.runAsync {
+			go func() { err = sut.Run() }()
+		} else {
+			err = sut.Run()
+		}
+		tc.assert(mock, lineChan, err)
 	}
 }