Skip to content

Commit

Permalink
[EBPF] PatternScaner testutil - support matching finished log (#31564)
Browse files Browse the repository at this point in the history
Co-authored-by: Guy Arbitman <[email protected]>
  • Loading branch information
val06 and guyarb authored Nov 29, 2024
1 parent a9e81b3 commit bdf4917
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 18 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@
/pkg/util/pdhutil/ @DataDog/windows-agent
/pkg/util/winutil/ @DataDog/windows-agent
/pkg/util/testutil/flake @DataDog/agent-devx-loops
/pkg/util/testutil/patternscanner.go @DataDog/universal-service-monitoring @DataDog/ebpf-platform
/pkg/util/testutil/docker @DataDog/universal-service-monitoring @DataDog/ebpf-platform
/pkg/util/trie @DataDog/container-integrations
/pkg/languagedetection @DataDog/processes @DataDog/universal-service-monitoring
Expand Down
3 changes: 2 additions & 1 deletion pkg/network/usm/sharedlibraries/testutil/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ var mux sync.Mutex
// handle to the given paths.
func OpenFromProcess(t *testing.T, programExecutable string, paths ...string) (*exec.Cmd, error) {
cmd := exec.Command(programExecutable, paths...)
patternScanner := protocolstestutil.NewScanner(regexp.MustCompile("awaiting signal"), make(chan struct{}, 1))
patternScanner, err := protocolstestutil.NewScanner(regexp.MustCompile("awaiting signal"), protocolstestutil.NoPattern, make(chan struct{}, 1))
require.NoError(t, err, "failed to create pattern scanner")
cmd.Stdout = patternScanner
cmd.Stderr = patternScanner

Expand Down
7 changes: 6 additions & 1 deletion pkg/util/testutil/docker/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/DataDog/datadog-agent/pkg/util/testutil"
)

Expand All @@ -24,12 +26,15 @@ import (
func Run(t testing.TB, cfg LifecycleConfig) error {
var err error
var ctx context.Context
var scanner *testutil.PatternScanner
for i := 0; i < cfg.Retries(); i++ {
t.Helper()
// Ensuring no previous instances exists.
killPreviousInstances(cfg)

scanner := testutil.NewScanner(cfg.LogPattern(), make(chan struct{}, 1))
//TODO: in the following PR move the scanner to be a field of the LifecycleConfig
scanner, err = testutil.NewScanner(cfg.LogPattern(), testutil.NoPattern, make(chan struct{}, 1))
require.NoError(t, err, "failed to create pattern scanner")
// attempt to start the container/s
ctx, err = run(t, cfg, scanner)
if err != nil {
Expand Down
72 changes: 56 additions & 16 deletions pkg/util/testutil/patternscanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,53 @@
package testutil

import (
"errors"
"regexp"
"strings"
"sync"
"testing"
)

// NoPattern is a sugar syntax for empty pattern
var NoPattern *regexp.Regexp

// PatternScanner is a helper to scan logs for a given pattern.
type PatternScanner struct {
// The log pattern to match on
pattern *regexp.Regexp
// The log pattern to match on validating successful start
startPattern *regexp.Regexp
// The log pattern to match on validating successful finish. This is optional
finishPattern *regexp.Regexp
// Once we've found the correct log, we should notify the caller.
DoneChan chan struct{}
// A sync.Once instance to ensure we notify the caller only once, and stop the operation.
stopOnce sync.Once
// A helper to spare redundant calls to the analyzer once we've found the relevant log.

// flag to indicate that start was found, and we should look for finishPattern now
startPatternFound bool
// A helper to spare redundant calls to the analyzer once we've found both start and finish
stopped bool

// keep the stdout/err in case of failure
buffers []string

//Buffer for accumulating partial lines
lineBuf string
}

// NewScanner returns a new instance of PatternScanner.
func NewScanner(pattern *regexp.Regexp, doneChan chan struct{}) *PatternScanner {
return &PatternScanner{
pattern: pattern,
DoneChan: doneChan,
stopOnce: sync.Once{},
stopped: false,
// at least one of the startPattern/finishPattern should be provided.
func NewScanner(startPattern, finishPattern *regexp.Regexp, doneChan chan struct{}) (*PatternScanner, error) {
if startPattern == nil && finishPattern == nil {
return nil, errors.New("at least one pattern should be provided")
}
return &PatternScanner{
startPattern: startPattern,
finishPattern: finishPattern,
DoneChan: doneChan,
stopOnce: sync.Once{},
// skip looking for start pattern if not provided
startPatternFound: startPattern == nil,
stopped: false,
}, nil
}

// Write implemented io.Writer to be used as a callback for log/string writing.
Expand All @@ -61,17 +76,42 @@ func (ps *PatternScanner) Write(p []byte) (n int, err error) {
// Process all complete lines.
for _, line := range lines[:len(lines)-1] {
ps.buffers = append(ps.buffers, line) // Save the log line.
if !ps.stopped && ps.pattern.MatchString(line) {
ps.stopOnce.Do(func() {
ps.stopped = true
close(ps.DoneChan) // Notify the caller about the match.
})
}

// Check if we've met the scanning criteria
ps.matchPatterns(line)
}

return len(p), nil
}

// matchPatterns checks if the current line matches the scanning requirements
func (ps *PatternScanner) matchPatterns(line string) {
switch {
// startPatternFound pattern not found yet, look for it
case !ps.startPatternFound:
if ps.startPattern.MatchString(line) {
// found start pattern, flip the flag to start looking for finish pattern for following iterations
ps.startPatternFound = true

// no finishPattern provided, we can stop here
if ps.finishPattern == nil {
ps.notifyAndStop()
}
}
// startPatternFound pattern found, look for finish pattern if provided
case ps.finishPattern != nil && ps.finishPattern.MatchString(line):
ps.notifyAndStop()
}
}

func (ps *PatternScanner) notifyAndStop() {
ps.stopOnce.Do(func() {
ps.buffers = append(ps.buffers, ps.lineBuf) // flush the last line
ps.stopped = true
close(ps.DoneChan)
})
}

// PrintLogs writes the captured logs into the test logger.
func (ps *PatternScanner) PrintLogs(t testing.TB) {
t.Log(strings.Join(ps.buffers, "\n"))
Expand Down

0 comments on commit bdf4917

Please sign in to comment.