Skip to content

Commit

Permalink
Merge pull request #283 from atc0005/i251-clarify-handling-of-empty-i…
Browse files Browse the repository at this point in the history
…nput

Clarify handling of empty payload input
  • Loading branch information
atc0005 authored Nov 1, 2024
2 parents 009ee3e + 5befd5d commit cc43651
Show file tree
Hide file tree
Showing 2 changed files with 292 additions and 5 deletions.
31 changes: 26 additions & 5 deletions nagios.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,8 @@ func (p *Plugin) SkipOSExit() {

// SetPayloadBytes uses the given input in bytes to overwrite any existing
// content in the payload buffer. It returns the length of input and a
// potential error.
// potential error. If given empty input the payload buffer is reset without
// adding any content.
//
// The contents of this buffer will be included in the plugin's output as an
// encoded payload suitable for later retrieval/decoding.
Expand All @@ -591,12 +592,17 @@ func (p *Plugin) SetPayloadBytes(input []byte) (int, error) {

p.encodedPayloadBuffer.Reset()

if len(input) == 0 {
return 0, nil
}

return p.encodedPayloadBuffer.Write(input)
}

// SetPayloadString uses the given input string to overwrite any existing
// content in the payload buffer. It returns the length of input and a
// potential error.
// potential error. If given empty input the payload buffer is reset without
// adding any content.
//
// The contents of this buffer will be included in the plugin's output as an
// encoded payload suitable for later retrieval/decoding.
Expand All @@ -608,15 +614,24 @@ func (p *Plugin) SetPayloadString(input string) (int, error) {

p.encodedPayloadBuffer.Reset()

if len(input) == 0 {
return 0, nil
}

return p.encodedPayloadBuffer.WriteString(input)
}

// AddPayloadBytes appends the given input in bytes to the payload buffer. It
// returns the length of input and a potential error.
// returns the length of input and a potential error. Empty input is silently
// ignored.
//
// The contents of this buffer will be included in the plugin's output as an
// encoded payload suitable for later retrieval/decoding.
func (p *Plugin) AddPayloadBytes(input []byte) (int, error) {
if len(input) == 0 {
return 0, nil
}

p.logAction(fmt.Sprintf(
"Appending %d bytes input to payload buffer",
len(input),
Expand All @@ -626,11 +641,16 @@ func (p *Plugin) AddPayloadBytes(input []byte) (int, error) {
}

// AddPayloadString appends the given input string to the payload buffer. It
// returns the length of input and a potential error.
// returns the length of input and a potential error. Empty input is silently
// ignored.
//
// The contents of this buffer will be included in the plugin's output as an
// encoded payload suitable for later retrieval/decoding.
func (p *Plugin) AddPayloadString(input string) (int, error) {
if len(input) == 0 {
return 0, nil
}

p.logAction(fmt.Sprintf(
"Appending %d bytes input to payload buffer",
len(input),
Expand All @@ -640,7 +660,8 @@ func (p *Plugin) AddPayloadString(input string) (int, error) {
}

// UnencodedPayload returns the payload buffer contents in string format as-is
// without encoding applied.
// without encoding applied. If the payload buffer is empty an empty string is
// returned.
func (p *Plugin) UnencodedPayload() string {
p.logAction(fmt.Sprintf(
"Returning %d bytes from payload buffer",
Expand Down
266 changes: 266 additions & 0 deletions unexported_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,72 @@ func TestSetPayloadString_SetsInputSuccessfullyWhenCalledMultipleTimes(t *testin
}
}

// TestSetPayloadString_SetsInputSuccessfullyWhenCalledWithEmptyInput asserts
// that when given input the `SetPayloadString` method (with overwrite
// behavior) resets the payload buffer without returning an error or producing
// any output.
func TestSetPayloadString_SetsInputSuccessfullyWhenCalledWithEmptyInput(t *testing.T) {
t.Parallel()

plugin := NewPlugin()

tests := map[string]struct {
// This represents data as given before it is written to the
// payload buffer (unencoded).
input string
}{
"empty string": {
input: "",
},
}

for name, tt := range tests {
// Guard against referencing the loop iterator variable directly.
//
// https://stackoverflow.com/questions/68559574/using-the-variable-on-range-scope-x-in-function-literal-scopelint
// https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables
tt := tt

t.Run(name, func(t *testing.T) {
t.Logf("Evaluating input %q", tt.input)

// We skip explicitly resetting the buffer as we do not expect it
// to change.
//
// plugin.encodedPayloadBuffer.Reset()

repeat := 2
for i := 0; i < repeat+1; i++ {
written, err := plugin.SetPayloadString(tt.input)

if err != nil {
t.Fatalf("Error occurred processing given empty input: %v", err)
}

if written > 0 {
t.Fatalf("Unexpected write of %d bytes to payload buffer", written)
} else {
t.Logf("Successfully handled %d bytes given input", written)
}
}

// Repeat function call (with overwrite behavior) should normally
// produce non-repeating output. In our case we processed empty
// input; the buffer should remain empty no matter how many times
// we provided empty input.
want := tt.input

got := plugin.encodedPayloadBuffer.String()

if d := cmp.Diff(want, got); d != "" {
t.Errorf("(-want, +got)\n:%s", d)
} else {
t.Logf("OK: Payload buffer matches given empty input.")
}
})
}
}

// TestSetPayloadBytes_SetsInputSuccessfullyWhenCalledOnce asserts that a
// payload buffer populated via the `SetPayloadBytes` method (overwrite
// behavior) with non-repeating valid input produces valid output.
Expand Down Expand Up @@ -541,6 +607,80 @@ func TestSetPayloadBytes_SetsInputSuccessfullyWhenCalledMultipleTimes(t *testing
}
}

// TestSetPayloadBytes_SetsInputSuccessfullyWhenCalledWithEmptyInput asserts
// that when given input the `SetPayloadBytes` method (with overwrite
// behavior) resets the payload buffer without returning an error or producing
// any output.
func TestSetPayloadBytes_SetsInputSuccessfullyWhenCalledWithEmptyInput(t *testing.T) {
t.Parallel()

plugin := NewPlugin()

tests := map[string]struct {
// This represents data as given before it is written to the
// payload buffer (unencoded).
input []byte
}{
"empty string": {
input: []byte(""),
},
"empty byte slice": {
input: []byte{},
},
"nil": {
input: nil,
},
}

for name, tt := range tests {
// Guard against referencing the loop iterator variable directly.
//
// https://stackoverflow.com/questions/68559574/using-the-variable-on-range-scope-x-in-function-literal-scopelint
// https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables
tt := tt

t.Run(name, func(t *testing.T) {
t.Logf("Evaluating input %#v", tt.input)

// We skip explicitly resetting the buffer as we do not expect it
// to change.
//
// plugin.encodedPayloadBuffer.Reset()

repeat := 2
for i := 0; i < repeat+1; i++ {
written, err := plugin.SetPayloadBytes(tt.input)

if err != nil {
t.Fatalf("Error occurred processing given empty input: %v", err)
}

if written > 0 {
t.Fatalf("Unexpected write of %d bytes to payload buffer", written)
} else {
t.Logf("Successfully handled %d bytes given input", written)
}
}

// Repeat function call (with overwrite behavior) should normally
// produce non-repeating output. In our case we processed empty
// input; the buffer should remain empty no matter how many times
// we provided empty input.
want := tt.input
got := plugin.encodedPayloadBuffer.Bytes()

// We compare by length to handle comparison of empty byte slice
// to nil.
if len(want) != len(got) {
d := cmp.Diff(want, got)
t.Errorf("(-want, +got)\n:%s", d)
} else {
t.Logf("OK: Payload buffer length reflects given empty input.")
}
})
}
}

// TestAddPayloadString_AppendsInputSuccessfullyWhenCalledOnce asserts that a
// payload buffer populated via the `AddPayloadString` method with
// non-repeating valid input produces valid output.
Expand Down Expand Up @@ -656,6 +796,66 @@ func TestAddPayloadString_AppendsInputSuccessfullyWhenCalledMultipleTimes(t *tes
}
}

// TestAddPayloadString_AppendsNothingWhenCalledWithEmptyInput asserts that
// when given empty input the `AddPayloadString` method does not return an
// error and produces no output.
func TestAddPayloadString_AppendsNothingWhenCalledWithEmptyInput(t *testing.T) {
t.Parallel()

plugin := NewPlugin()

tests := map[string]struct {
input string
}{
"empty string": {
input: "",
},
}

for name, tt := range tests {
// Guard against referencing the loop iterator variable directly.
//
// https://stackoverflow.com/questions/68559574/using-the-variable-on-range-scope-x-in-function-literal-scopelint
// https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables
tt := tt

t.Run(name, func(t *testing.T) {
t.Logf("Evaluating input %#v", tt.input)

plugin.encodedPayloadBuffer.Reset()

repeat := 2
for i := 0; i < repeat+1; i++ {
written, err := plugin.AddPayloadString(tt.input)

if err != nil {
t.Fatalf("Error occurred appending given empty input to payload buffer: %v", err)
}

if written > 0 {
t.Fatalf("Unexpected append of %d bytes to payload buffer", written)
} else {
t.Logf("Successfully handled %d bytes given input", written)
}
}

// Repeat function call should produce no collected output.
var want string
for i := 0; i < repeat+1; i++ {
want += tt.input
}

got := plugin.encodedPayloadBuffer.String()

if d := cmp.Diff(want, got); d != "" {
t.Errorf("(-want, +got)\n:%s", d)
} else {
t.Logf("OK: Payload buffer matches total given input.")
}
})
}
}

// TestAddPayloadBytes_AppendsInputSuccessfullyWhenCalledOnce asserts that a
// payload buffer populated via the `AddPayloadBytes` method with
// non-repeating valid input produces valid output.
Expand Down Expand Up @@ -771,6 +971,72 @@ func TestAddPayloadBytes_AppendsInputSuccessfullyWhenCalledMultipleTimes(t *test
}
}

// TestAddPayloadBytes_AppendsNothingWhenCalledWithEmptyInput asserts that
// when given empty input the `AddPayloadBytes` method does not return an
// error and produces no output.
func TestAddPayloadBytes_AppendsNothingWhenCalledWithEmptyInput(t *testing.T) {
t.Parallel()

plugin := NewPlugin()

tests := map[string]struct {
input []byte
}{
"empty string": {
input: []byte(""),
},
"empty byte slice": {
input: []byte{},
},
"nil": {
input: nil,
},
}

for name, tt := range tests {
// Guard against referencing the loop iterator variable directly.
//
// https://stackoverflow.com/questions/68559574/using-the-variable-on-range-scope-x-in-function-literal-scopelint
// https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables
tt := tt

t.Run(name, func(t *testing.T) {
t.Logf("Evaluating input %#v", tt.input)

plugin.encodedPayloadBuffer.Reset()

repeat := 2
for i := 0; i < repeat+1; i++ {
written, err := plugin.AddPayloadBytes(tt.input)

if err != nil {
t.Fatalf("Error occurred appending given empty input to payload buffer: %v", err)
}

if written > 0 {
t.Fatalf("Unexpected append of %d bytes to payload buffer", written)
} else {
t.Logf("Successfully handled %d bytes given input", written)
}
}

// Repeat function call should produce no collected output.
var want string
for i := 0; i < repeat+1; i++ {
want += string(tt.input)
}

got := plugin.encodedPayloadBuffer.String()

if d := cmp.Diff(want, got); d != "" {
t.Errorf("(-want, +got)\n:%s", d)
} else {
t.Logf("OK: Payload buffer matches total given input.")
}
})
}
}

// TestEmptyPerfDataAndEmptyServiceOutputProducesNoOutput asserts that an
// empty Performance Data metrics collection AND empty ServiceOutput produces
// no output.
Expand Down

0 comments on commit cc43651

Please sign in to comment.