Skip to content

Commit 83c5908

Browse files
authored
Cover patch_linux.go by unit tests (#932)
* Cover patch_linux.go by unit tests * Fix review comments
1 parent 5a84d79 commit 83c5908

4 files changed

Lines changed: 215 additions & 22 deletions

File tree

agentendpoint/patch_linux.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,15 @@ import (
2929
"cloud.google.com/go/osconfig/agentendpoint/apiv1/agentendpointpb"
3030
)
3131

32+
var (
33+
runAptGetUpgrade = ospatch.RunAptGetUpgrade
34+
runYumUpdate = ospatch.RunYumUpdate
35+
runZypperPatch = ospatch.RunZypperPatch
36+
retryPeriod = 3 * time.Minute
37+
)
38+
3239
func (r *patchTask) runUpdates(ctx context.Context) error {
3340
var errs []string
34-
const retryPeriod = 3 * time.Minute
3541
// Check for both apt-get and dpkg-query to give us a clean signal.
3642
if packages.AptExists && packages.DpkgQueryExists {
3743
excludes, err := convertInputToExcludes(r.Task.GetPatchConfig().GetApt().GetExcludes())
@@ -48,7 +54,7 @@ func (r *patchTask) runUpdates(ctx context.Context) error {
4854
opts = append(opts, ospatch.AptGetUpgradeType(packages.AptGetDistUpgrade))
4955
}
5056
clog.Debugf(ctx, "Installing APT package updates.")
51-
if err := retryutil.RetryFunc(ctx, retryPeriod, "installing APT package updates", func() error { return ospatch.RunAptGetUpgrade(ctx, opts...) }); err != nil {
57+
if err := retryutil.RetryFunc(ctx, retryPeriod, "installing APT package updates", func() error { return runAptGetUpgrade(ctx, opts...) }); err != nil {
5258
errs = append(errs, err.Error())
5359
}
5460
}
@@ -65,7 +71,7 @@ func (r *patchTask) runUpdates(ctx context.Context) error {
6571
ospatch.YumDryRun(r.Task.GetDryRun()),
6672
}
6773
clog.Debugf(ctx, "Installing YUM package updates.")
68-
if err := retryutil.RetryFunc(ctx, retryPeriod, "installing YUM package updates", func() error { return ospatch.RunYumUpdate(ctx, opts...) }); err != nil {
74+
if err := retryutil.RetryFunc(ctx, retryPeriod, "installing YUM package updates", func() error { return runYumUpdate(ctx, opts...) }); err != nil {
6975
errs = append(errs, err.Error())
7076
}
7177
}
@@ -84,7 +90,7 @@ func (r *patchTask) runUpdates(ctx context.Context) error {
8490
ospatch.ZypperUpdateDryrun(r.Task.GetDryRun()),
8591
}
8692
clog.Debugf(ctx, "Installing Zypper updates.")
87-
if err := retryutil.RetryFunc(ctx, retryPeriod, "installing Zypper updates", func() error { return ospatch.RunZypperPatch(ctx, opts...) }); err != nil {
93+
if err := retryutil.RetryFunc(ctx, retryPeriod, "installing Zypper updates", func() error { return runZypperPatch(ctx, opts...) }); err != nil {
8894
errs = append(errs, err.Error())
8995
}
9096
}

agentendpoint/patch_linux_test.go

Lines changed: 202 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,233 @@
1515
package agentendpoint
1616

1717
import (
18+
"context"
19+
"errors"
1820
"reflect"
1921
"regexp"
2022
"strings"
2123
"testing"
24+
"time"
2225

26+
"cloud.google.com/go/osconfig/agentendpoint/apiv1/agentendpointpb"
2327
"github.com/GoogleCloudPlatform/osconfig/ospatch"
28+
"github.com/GoogleCloudPlatform/osconfig/packages"
29+
"github.com/GoogleCloudPlatform/osconfig/util/utiltest"
2430
)
2531

2632
func TestExcludeConversion(t *testing.T) {
2733
regex, _ := regexp.Compile("PackageName")
2834
emptyRegex, _ := regexp.Compile("")
35+
_, regexpErr := regexp.Compile("[a-z")
2936

3037
tests := []struct {
31-
name string
32-
input []string
33-
want []*ospatch.Exclude
38+
name string
39+
input []string
40+
want []*ospatch.Exclude
41+
wantErr error
3442
}{
35-
{name: "StrictStringConversion", input: []string{"PackageName"}, want: CreateStringExcludes("PackageName")},
36-
{name: "MultipleStringConversion", input: []string{"PackageName1", "PackageName2"}, want: CreateStringExcludes("PackageName1", "PackageName2")},
37-
{name: "RegexConversion", input: []string{"/PackageName/"}, want: []*ospatch.Exclude{ospatch.CreateRegexExclude(regex)}},
38-
{name: "CornerCaseRegex", input: []string{"//"}, want: []*ospatch.Exclude{ospatch.CreateRegexExclude(emptyRegex)}},
39-
{name: "CornerCaseStrictString", input: []string{"/"}, want: CreateStringExcludes("/")},
40-
{name: "CornerCaseEmptyString", input: []string{""}, want: CreateStringExcludes("")},
43+
{name: "Single package name, want one string exclude", input: []string{"PackageName"}, want: createStringExcludes("PackageName")},
44+
{name: "Multiple package names, want multiple string excludes", input: []string{"PackageName1", "PackageName2"}, want: createStringExcludes("PackageName1", "PackageName2")},
45+
{name: "Slash-wrapped value, want regex exclude", input: []string{"/PackageName/"}, want: []*ospatch.Exclude{ospatch.CreateRegexExclude(regex)}},
46+
{name: "Empty regex //, want empty regex exclude", input: []string{"//"}, want: []*ospatch.Exclude{ospatch.CreateRegexExclude(emptyRegex)}},
47+
{name: "Single slash, want string exclude", input: []string{"/"}, want: createStringExcludes("/")},
48+
{name: "Empty string, want string exclude", input: []string{""}, want: createStringExcludes("")},
49+
{name: "Invalid regex, want regex compile error", input: []string{"/[a-z/"}, wantErr: regexpErr},
4150
}
4251

4352
for _, tt := range tests {
4453
t.Run(tt.name, func(t *testing.T) {
4554
excludes, err := convertInputToExcludes(tt.input)
46-
if err != nil {
47-
t.Errorf("err = %v, want %v", err, nil)
48-
}
55+
4956
if !reflect.DeepEqual(excludes, tt.want) {
5057
t.Errorf("convertInputToExcludes() = %s, want = %s", toString(excludes), toString(tt.want))
5158
}
59+
utiltest.AssertErrorMatch(t, err, tt.wantErr)
60+
})
61+
}
62+
}
63+
64+
func TestRunUpdates(t *testing.T) {
65+
utiltest.OverrideVariable(t, &retryPeriod, 1*time.Millisecond)
66+
67+
mockAptSuccess := func(ctx context.Context, opts ...ospatch.AptGetUpgradeOption) error { return nil }
68+
mockYumSuccess := func(ctx context.Context, opts ...ospatch.YumUpdateOption) error { return nil }
69+
mockZypperSuccess := func(ctx context.Context, opts ...ospatch.ZypperPatchOption) error { return nil }
70+
mockAptErr := func(ctx context.Context, opts ...ospatch.AptGetUpgradeOption) error { return errors.New("apt err") }
71+
mockYumErr := func(ctx context.Context, opts ...ospatch.YumUpdateOption) error { return errors.New("yum err") }
72+
mockZypperErr := func(ctx context.Context, opts ...ospatch.ZypperPatchOption) error { return errors.New("zypper err") }
73+
_, regexpErr := regexp.Compile("[a-z")
74+
75+
tests := []struct {
76+
name string
77+
setup func(t *testing.T)
78+
taskConfig *agentendpointpb.PatchConfig
79+
wantErr error
80+
}{
81+
{
82+
name: "No package managers detected, want no patch operations attempted and no error",
83+
setup: func(t *testing.T) {
84+
disableAllPackageManagers(t)
85+
},
86+
taskConfig: &agentendpointpb.PatchConfig{},
87+
wantErr: nil,
88+
},
89+
{
90+
name: "Apt available, want apt-get dist-upgrade attempted and no error",
91+
setup: func(t *testing.T) {
92+
disableAllPackageManagers(t)
93+
enableApt(t, mockAptSuccess)
94+
},
95+
taskConfig: &agentendpointpb.PatchConfig{
96+
Apt: &agentendpointpb.AptSettings{Type: agentendpointpb.AptSettings_DIST},
97+
},
98+
wantErr: nil,
99+
},
100+
{
101+
name: "Apt available with invalid regex in excludes, want regex compile error and no upgrade attempted",
102+
setup: func(t *testing.T) {
103+
disableAllPackageManagers(t)
104+
enableApt(t, nil)
105+
},
106+
taskConfig: &agentendpointpb.PatchConfig{
107+
Apt: &agentendpointpb.AptSettings{Excludes: []string{"/[a-z/"}},
108+
},
109+
wantErr: regexpErr,
110+
},
111+
{
112+
name: "Yum available, want yum update attempted and no error",
113+
setup: func(t *testing.T) {
114+
disableAllPackageManagers(t)
115+
enableYum(t, mockYumSuccess)
116+
},
117+
taskConfig: &agentendpointpb.PatchConfig{
118+
Yum: &agentendpointpb.YumSettings{
119+
Security: true,
120+
Minimal: true,
121+
},
122+
},
123+
wantErr: nil,
124+
},
125+
{
126+
name: "Zypper available, want zypper patch attempted and no error",
127+
setup: func(t *testing.T) {
128+
disableAllPackageManagers(t)
129+
enableZypper(t, mockZypperSuccess)
130+
},
131+
taskConfig: &agentendpointpb.PatchConfig{
132+
Zypper: &agentendpointpb.ZypperSettings{
133+
WithUpdate: true,
134+
WithOptional: true,
135+
},
136+
},
137+
wantErr: nil,
138+
},
139+
{
140+
name: "Yum available with invalid regex in excludes, want regex compile error and no update attempted",
141+
setup: func(t *testing.T) {
142+
disableAllPackageManagers(t)
143+
enableYum(t, nil)
144+
},
145+
taskConfig: &agentendpointpb.PatchConfig{
146+
Yum: &agentendpointpb.YumSettings{Excludes: []string{"/[a-z/"}},
147+
},
148+
wantErr: regexpErr,
149+
},
150+
{
151+
name: "Zypper available with invalid regex in excludes, want regex compile error and no patch attempted",
152+
setup: func(t *testing.T) {
153+
disableAllPackageManagers(t)
154+
enableZypper(t, nil)
155+
},
156+
taskConfig: &agentendpointpb.PatchConfig{
157+
Zypper: &agentendpointpb.ZypperSettings{Excludes: []string{"/[a-z/"}},
158+
},
159+
wantErr: regexpErr,
160+
},
161+
{
162+
name: "Yum available and yum update returns error, want that error returned",
163+
setup: func(t *testing.T) {
164+
disableAllPackageManagers(t)
165+
enableYum(t, mockYumErr)
166+
},
167+
taskConfig: &agentendpointpb.PatchConfig{
168+
Yum: &agentendpointpb.YumSettings{},
169+
},
170+
wantErr: errors.New("yum err"),
171+
},
172+
{
173+
name: "All package managers available and each fails, want all three errors aggregated",
174+
setup: func(t *testing.T) {
175+
disableAllPackageManagers(t)
176+
enableApt(t, mockAptErr)
177+
enableYum(t, mockYumErr)
178+
enableZypper(t, mockZypperErr)
179+
},
180+
taskConfig: &agentendpointpb.PatchConfig{
181+
Apt: &agentendpointpb.AptSettings{},
182+
Yum: &agentendpointpb.YumSettings{},
183+
Zypper: &agentendpointpb.ZypperSettings{},
184+
},
185+
wantErr: errors.New("apt err,\nyum err,\nzypper err"),
186+
},
187+
}
188+
189+
for _, tt := range tests {
190+
t.Run(tt.name, func(t *testing.T) {
191+
tt.setup(t)
192+
task := initializePatchTask(tt.taskConfig)
193+
194+
err := task.runUpdates(context.Background())
195+
utiltest.AssertErrorMatch(t, err, tt.wantErr)
52196
})
53197
}
54198
}
55199

200+
func initializePatchTask(config *agentendpointpb.PatchConfig) *patchTask {
201+
return &patchTask{
202+
Task: &applyPatchesTask{
203+
ApplyPatchesTask: &agentendpointpb.ApplyPatchesTask{
204+
PatchConfig: config,
205+
},
206+
},
207+
}
208+
}
209+
210+
func disableAllPackageManagers(t *testing.T) {
211+
t.Helper()
212+
utiltest.OverrideVariable(t, &packages.DpkgQueryExists, false)
213+
utiltest.OverrideVariable(t, &packages.YumExists, false)
214+
utiltest.OverrideVariable(t, &packages.RPMQueryExists, false)
215+
utiltest.OverrideVariable(t, &packages.ZypperExists, false)
216+
}
217+
218+
func enableApt(t *testing.T, run func(ctx context.Context, opts ...ospatch.AptGetUpgradeOption) error) {
219+
t.Helper()
220+
utiltest.OverrideVariable(t, &packages.AptExists, true)
221+
utiltest.OverrideVariable(t, &packages.DpkgQueryExists, true)
222+
if run != nil {
223+
utiltest.OverrideVariable(t, &runAptGetUpgrade, run)
224+
}
225+
}
226+
227+
func enableYum(t *testing.T, run func(ctx context.Context, opts ...ospatch.YumUpdateOption) error) {
228+
t.Helper()
229+
utiltest.OverrideVariable(t, &packages.YumExists, true)
230+
utiltest.OverrideVariable(t, &packages.RPMQueryExists, true)
231+
if run != nil {
232+
utiltest.OverrideVariable(t, &runYumUpdate, run)
233+
}
234+
}
235+
236+
func enableZypper(t *testing.T, run func(ctx context.Context, opts ...ospatch.ZypperPatchOption) error) {
237+
t.Helper()
238+
utiltest.OverrideVariable(t, &packages.ZypperExists, true)
239+
utiltest.OverrideVariable(t, &packages.RPMQueryExists, true)
240+
if run != nil {
241+
utiltest.OverrideVariable(t, &runZypperPatch, run)
242+
}
243+
}
244+
56245
func toString(excludes []*ospatch.Exclude) string {
57246
results := make([]string, len(excludes))
58247
for i, exc := range excludes {
@@ -62,7 +251,7 @@ func toString(excludes []*ospatch.Exclude) string {
62251
return strings.Join(results, ",")
63252
}
64253

65-
func CreateStringExcludes(pkgs ...string) []*ospatch.Exclude {
254+
func createStringExcludes(pkgs ...string) []*ospatch.Exclude {
66255
excludes := make([]*ospatch.Exclude, len(pkgs))
67256
for i := 0; i < len(pkgs); i++ {
68257
pkg := pkgs[i]

policies/apt_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ func TestShouldUseSignedBy(t *testing.T) {
301301

302302
for _, tt := range tests {
303303
t.Run(tt.name, func(t *testing.T) {
304-
defer utiltest.OverrideVariable(&osInfoProvider, tt.provider)()
304+
utiltest.OverrideVariable(t, &osInfoProvider, tt.provider)
305305
utiltest.AssertEquals(t, shouldUseSignedBy(ctx), tt.want)
306306
})
307307
}

util/utiltest/utiltest.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,8 @@ func AssertFileContents(t *testing.T, filePath string, wantContents string) {
137137
}
138138

139139
// OverrideVariable overrides the value of a variable and returns a function to restore it.
140-
func OverrideVariable[T any](ptr *T, val T) func() {
140+
func OverrideVariable[T any](t *testing.T, ptr *T, val T) {
141141
original := *ptr
142142
*ptr = val
143-
return func() {
144-
*ptr = original
145-
}
143+
t.Cleanup(func() { *ptr = original })
146144
}

0 commit comments

Comments
 (0)