123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- package main
- import (
- "bufio"
- "bytes"
- "io"
- "os/exec"
- "regexp"
- "strconv"
- "strings"
- "testing"
- eventstestutils "github.com/docker/docker/daemon/events/testutils"
- "github.com/sirupsen/logrus"
- "gotest.tools/v3/assert"
- )
- // eventMatcher is a function that tries to match an event input.
- // It returns true if the event matches and a map with
- // a set of key/value to identify the match.
- type eventMatcher func(text string) (map[string]string, bool)
- // eventMatchProcessor is a function to handle an event match.
- // It receives a map of key/value with the information extracted in a match.
- type eventMatchProcessor func(matches map[string]string)
- // eventObserver runs an events commands and observes its output.
- type eventObserver struct {
- buffer *bytes.Buffer
- command *exec.Cmd
- scanner *bufio.Scanner
- startTime string
- disconnectionError error
- }
- // newEventObserver creates the observer and initializes the command
- // without running it. Users must call `eventObserver.Start` to start the command.
- func newEventObserver(c *testing.T, args ...string) (*eventObserver, error) {
- since := daemonTime(c).Unix()
- return newEventObserverWithBacklog(c, since, args...)
- }
- // newEventObserverWithBacklog creates a new observer changing the start time of the backlog to return.
- func newEventObserverWithBacklog(c *testing.T, since int64, args ...string) (*eventObserver, error) {
- startTime := strconv.FormatInt(since, 10)
- cmdArgs := []string{"events", "--since", startTime}
- if len(args) > 0 {
- cmdArgs = append(cmdArgs, args...)
- }
- eventsCmd := exec.Command(dockerBinary, cmdArgs...)
- stdout, err := eventsCmd.StdoutPipe()
- if err != nil {
- return nil, err
- }
- return &eventObserver{
- buffer: new(bytes.Buffer),
- command: eventsCmd,
- scanner: bufio.NewScanner(stdout),
- startTime: startTime,
- }, nil
- }
- // Start starts the events command.
- func (e *eventObserver) Start() error {
- return e.command.Start()
- }
- // Stop stops the events command.
- func (e *eventObserver) Stop() {
- e.command.Process.Kill()
- e.command.Wait()
- }
- // Match tries to match the events output with a given matcher.
- func (e *eventObserver) Match(match eventMatcher, process eventMatchProcessor) {
- for e.scanner.Scan() {
- text := e.scanner.Text()
- e.buffer.WriteString(text)
- e.buffer.WriteString("\n")
- if matches, ok := match(text); ok {
- process(matches)
- }
- }
- err := e.scanner.Err()
- if err == nil {
- err = io.EOF
- }
- logrus.Debugf("EventObserver scanner loop finished: %v", err)
- e.disconnectionError = err
- }
- func (e *eventObserver) CheckEventError(c *testing.T, id, event string, match eventMatcher) {
- var foundEvent bool
- scannerOut := e.buffer.String()
- if e.disconnectionError != nil {
- until := daemonUnixTime(c)
- out, _ := dockerCmd(c, "events", "--since", e.startTime, "--until", until)
- events := strings.Split(strings.TrimSpace(out), "\n")
- for _, e := range events {
- if _, ok := match(e); ok {
- foundEvent = true
- break
- }
- }
- scannerOut = out
- }
- if !foundEvent {
- c.Fatalf("failed to observe event `%s` for %s. Disconnection error: %v\nout:\n%v", event, id, e.disconnectionError, scannerOut)
- }
- }
- // matchEventLine matches a text with the event regular expression.
- // It returns the matches and true if the regular expression matches with the given id and event type.
- // It returns an empty map and false if there is no match.
- func matchEventLine(id, eventType string, actions map[string]chan bool) eventMatcher {
- return func(text string) (map[string]string, bool) {
- matches := eventstestutils.ScanMap(text)
- if len(matches) == 0 {
- return matches, false
- }
- if matchIDAndEventType(matches, id, eventType) {
- if _, ok := actions[matches["action"]]; ok {
- return matches, true
- }
- }
- return matches, false
- }
- }
- // processEventMatch closes an action channel when an event line matches the expected action.
- func processEventMatch(actions map[string]chan bool) eventMatchProcessor {
- return func(matches map[string]string) {
- if ch, ok := actions[matches["action"]]; ok {
- ch <- true
- }
- }
- }
- // parseEventAction parses an event text and returns the action.
- // It fails if the text is not in the event format.
- func parseEventAction(c *testing.T, text string) string {
- matches := eventstestutils.ScanMap(text)
- return matches["action"]
- }
- // eventActionsByIDAndType returns the actions for a given id and type.
- // It fails if the text is not in the event format.
- func eventActionsByIDAndType(c *testing.T, events []string, id, eventType string) []string {
- var filtered []string
- for _, event := range events {
- matches := eventstestutils.ScanMap(event)
- assert.Assert(c, matches != nil)
- if matchIDAndEventType(matches, id, eventType) {
- filtered = append(filtered, matches["action"])
- }
- }
- return filtered
- }
- // matchIDAndEventType returns true if an event matches a given id and type.
- // It also resolves names in the event attributes if the id doesn't match.
- func matchIDAndEventType(matches map[string]string, id, eventType string) bool {
- return matchEventID(matches, id) && matches["eventType"] == eventType
- }
- func matchEventID(matches map[string]string, id string) bool {
- matchID := matches["id"] == id || strings.HasPrefix(matches["id"], id)
- if !matchID && matches["attributes"] != "" {
- // try matching a name in the attributes
- attributes := map[string]string{}
- for _, a := range strings.Split(matches["attributes"], ", ") {
- kv := strings.Split(a, "=")
- attributes[kv[0]] = kv[1]
- }
- matchID = attributes["name"] == id
- }
- return matchID
- }
- func parseEvents(c *testing.T, out, match string) {
- events := strings.Split(strings.TrimSpace(out), "\n")
- for _, event := range events {
- matches := eventstestutils.ScanMap(event)
- matched, err := regexp.MatchString(match, matches["action"])
- assert.NilError(c, err)
- assert.Assert(c, matched, "Matcher: %s did not match %s", match, matches["action"])
- }
- }
|