Add filter by event type and documentation.

Signed-off-by: David Calavera <david.calavera@gmail.com>
Upstream-commit: 851fe00c64ffafeb27b12f7b0ed8e41f7720b477
Component: engine
This commit is contained in:
David Calavera
2016-01-04 14:58:04 -05:00
parent e32ea4c0f9
commit c2e5fdc393
11 changed files with 551 additions and 279 deletions
@@ -2,7 +2,6 @@ package main
import (
"archive/tar"
"bufio"
"bytes"
"encoding/json"
"fmt"
@@ -1863,90 +1862,6 @@ func (s *DockerSuite) TestBuildForceRm(c *check.C) {
}
// Test that an infinite sleep during a build is killed if the client disconnects.
// This test is fairly hairy because there are lots of ways to race.
// Strategy:
// * Monitor the output of docker events starting from before
// * Run a 1-year-long sleep from a docker build.
// * When docker events sees container start, close the "docker build" command
// * Wait for docker events to emit a dying event.
func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
testRequires(c, DaemonIsLinux)
name := "testbuildcancellation"
observer, err := newEventObserver(c)
c.Assert(err, checker.IsNil)
err = observer.Start()
c.Assert(err, checker.IsNil)
defer observer.Stop()
// (Note: one year, will never finish)
ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
if err != nil {
c.Fatal(err)
}
defer ctx.Close()
buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".")
buildCmd.Dir = ctx.Dir
stdoutBuild, err := buildCmd.StdoutPipe()
if err := buildCmd.Start(); err != nil {
c.Fatalf("failed to run build: %s", err)
}
matchCID := regexp.MustCompile("Running in (.+)")
scanner := bufio.NewScanner(stdoutBuild)
outputBuffer := new(bytes.Buffer)
var buildID string
for scanner.Scan() {
line := scanner.Text()
outputBuffer.WriteString(line)
outputBuffer.WriteString("\n")
if matches := matchCID.FindStringSubmatch(line); len(matches) > 0 {
buildID = matches[1]
break
}
}
if buildID == "" {
c.Fatalf("Unable to find build container id in build output:\n%s", outputBuffer.String())
}
testActions := map[string]chan bool{
"start": make(chan bool),
"die": make(chan bool),
}
go observer.Match(matchEventLine(buildID, "container", testActions))
select {
case <-time.After(10 * time.Second):
c.Fatal(observer.TimeoutError(buildID, "start"))
case <-testActions["start"]:
// ignore, done
}
// Send a kill to the `docker build` command.
// Causes the underlying build to be cancelled due to socket close.
if err := buildCmd.Process.Kill(); err != nil {
c.Fatalf("error killing build command: %s", err)
}
// Get the exit status of `docker build`, check it exited because killed.
if err := buildCmd.Wait(); err != nil && !isKilled(err) {
c.Fatalf("wait failed during build run: %T %s", err, err)
}
select {
case <-time.After(10 * time.Second):
c.Fatal(observer.TimeoutError(buildID, "die"))
case <-testActions["die"]:
// ignore, done
}
}
func (s *DockerSuite) TestBuildRm(c *check.C) {
testRequires(c, DaemonIsLinux)
name := "testbuildrm"
@@ -3,12 +3,16 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/docker/docker/pkg/integration/checker"
"github.com/docker/go-units"
@@ -115,5 +119,89 @@ func (s *DockerSuite) TestBuildAddChangeOwnership(c *check.C) {
if _, err := buildImageFromContext(name, ctx, true); err != nil {
c.Fatalf("build failed to complete for TestBuildAddChangeOwnership: %v", err)
}
}
// Test that an infinite sleep during a build is killed if the client disconnects.
// This test is fairly hairy because there are lots of ways to race.
// Strategy:
// * Monitor the output of docker events starting from before
// * Run a 1-year-long sleep from a docker build.
// * When docker events sees container start, close the "docker build" command
// * Wait for docker events to emit a dying event.
func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
testRequires(c, DaemonIsLinux)
name := "testbuildcancellation"
observer, err := newEventObserver(c)
c.Assert(err, checker.IsNil)
err = observer.Start()
c.Assert(err, checker.IsNil)
defer observer.Stop()
// (Note: one year, will never finish)
ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
if err != nil {
c.Fatal(err)
}
defer ctx.Close()
buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".")
buildCmd.Dir = ctx.Dir
stdoutBuild, err := buildCmd.StdoutPipe()
if err := buildCmd.Start(); err != nil {
c.Fatalf("failed to run build: %s", err)
}
matchCID := regexp.MustCompile("Running in (.+)")
scanner := bufio.NewScanner(stdoutBuild)
outputBuffer := new(bytes.Buffer)
var buildID string
for scanner.Scan() {
line := scanner.Text()
outputBuffer.WriteString(line)
outputBuffer.WriteString("\n")
if matches := matchCID.FindStringSubmatch(line); len(matches) > 0 {
buildID = matches[1]
break
}
}
if buildID == "" {
c.Fatalf("Unable to find build container id in build output:\n%s", outputBuffer.String())
}
testActions := map[string]chan bool{
"start": make(chan bool),
"die": make(chan bool),
}
matcher := matchEventLine(buildID, "container", testActions)
go observer.Match(matcher)
select {
case <-time.After(10 * time.Second):
observer.CheckEventError(c, buildID, "start", matcher)
case <-testActions["start"]:
// ignore, done
}
// Send a kill to the `docker build` command.
// Causes the underlying build to be cancelled due to socket close.
if err := buildCmd.Process.Kill(); err != nil {
c.Fatalf("error killing build command: %s", err)
}
// Get the exit status of `docker build`, check it exited because killed.
if err := buildCmd.Wait(); err != nil && !isKilled(err) {
c.Fatalf("wait failed during build run: %T %s", err, err)
}
select {
case <-time.After(10 * time.Second):
observer.CheckEventError(c, buildID, "die", matcher)
case <-testActions["die"]:
// ignore, done
}
}
@@ -72,13 +72,25 @@ func (s *DockerSuite) TestEventsContainerFailStartDie(c *check.C) {
c.Assert(err, checker.NotNil, check.Commentf("Container run with command blerg should have failed, but it did not, out=%s", out))
out, _ = dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(out, "\n")
events := strings.Split(strings.TrimSpace(out), "\n")
nEvents := len(events)
c.Assert(nEvents, checker.GreaterOrEqualThan, 1) //Missing expected event
c.Assert(parseEventAction(c, events[nEvents-3]), checker.Equals, "start")
c.Assert(parseEventAction(c, events[nEvents-2]), checker.Equals, "die")
actions := eventActionsByIDAndType(c, events, "testeventdie", "container")
var startEvent bool
var dieEvent bool
for _, a := range actions {
switch a {
case "start":
startEvent = true
case "die":
dieEvent = true
}
}
c.Assert(startEvent, checker.True, check.Commentf("Start event not found: %v\n%v", actions, events))
c.Assert(dieEvent, checker.True, check.Commentf("Die event not found: %v\n%v", actions, events))
}
func (s *DockerSuite) TestEventsLimit(c *check.C) {
@@ -150,45 +162,6 @@ func (s *DockerSuite) TestEventsContainerEventsSinceUnixEpoch(c *check.C) {
c.Assert(containerEvents[4], checker.Equals, "destroy", check.Commentf(out))
}
func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) {
testRequires(c, DaemonIsLinux)
observer, err := newEventObserver(c)
c.Assert(err, checker.IsNil)
err = observer.Start()
c.Assert(err, checker.IsNil)
defer observer.Stop()
name := "testimageevents"
imageID, err := buildImage(name,
`FROM scratch
MAINTAINER "docker"`,
true)
c.Assert(err, checker.IsNil)
c.Assert(deleteImages(name), checker.IsNil)
testActions := map[string]chan bool{
"untag": make(chan bool),
"delete": make(chan bool),
}
go observer.Match(matchEventLine(imageID, "image", testActions))
select {
case <-time.After(10 * time.Second):
c.Fatal(observer.TimeoutError(imageID, "untag"))
case <-testActions["untag"]:
// ignore, done
}
select {
case <-time.After(10 * time.Second):
c.Fatal(observer.TimeoutError(imageID, "delete"))
case <-testActions["delete"]:
// ignore, done
}
}
func (s *DockerSuite) TestEventsImageTag(c *check.C) {
testRequires(c, DaemonIsLinux)
time.Sleep(1 * time.Second) // because API has seconds granularity
@@ -205,7 +178,6 @@ func (s *DockerSuite) TestEventsImageTag(c *check.C) {
event := strings.TrimSpace(events[0])
matches := parseEventText(event)
c.Assert(matches, checker.Not(checker.IsNil))
c.Assert(matchEventID(matches, image), checker.True, check.Commentf("matches: %v\nout:\n%s", matches, out))
c.Assert(matches["action"], checker.Equals, "tag")
}
@@ -224,7 +196,6 @@ func (s *DockerSuite) TestEventsImagePull(c *check.C) {
events := strings.Split(strings.TrimSpace(out), "\n")
event := strings.TrimSpace(events[len(events)-1])
matches := parseEventText(event)
c.Assert(matches, checker.Not(checker.IsNil))
c.Assert(matches["id"], checker.Equals, "hello-world:latest")
c.Assert(matches["action"], checker.Equals, "pull")
@@ -348,7 +319,8 @@ func (s *DockerSuite) TestEventsFilterImageLabels(c *check.C) {
"events",
fmt.Sprintf("--since=%d", since),
fmt.Sprintf("--until=%d", daemonTime(c).Unix()),
"--filter", fmt.Sprintf("label=%s", label))
"--filter", fmt.Sprintf("label=%s", label),
"--filter", "type=image")
events := strings.Split(strings.TrimSpace(out), "\n")
@@ -399,46 +371,6 @@ func (s *DockerSuite) TestEventsFilterContainer(c *check.C) {
}
}
func (s *DockerSuite) TestEventsStreaming(c *check.C) {
testRequires(c, DaemonIsLinux)
observer, err := newEventObserver(c)
c.Assert(err, checker.IsNil)
err = observer.Start()
c.Assert(err, checker.IsNil)
defer observer.Stop()
out, _ := dockerCmd(c, "run", "-d", "busybox:latest", "true")
containerID := strings.TrimSpace(out)
testActions := map[string]chan bool{
"create": make(chan bool),
"start": make(chan bool),
"die": make(chan bool),
"destroy": make(chan bool),
}
go observer.Match(matchEventLine(containerID, "container", testActions))
select {
case <-time.After(5 * time.Second):
c.Fatal(observer.TimeoutError(containerID, "create/start/die"))
case <-testActions["create"]:
case <-testActions["start"]:
case <-testActions["die"]:
// ignore, done
}
dockerCmd(c, "rm", containerID)
select {
case <-time.After(5 * time.Second):
c.Fatal(observer.TimeoutError(containerID, "destroy"))
case <-testActions["destroy"]:
// ignore, done
}
}
func (s *DockerSuite) TestEventsCommit(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
@@ -587,3 +519,69 @@ func (s *DockerRegistrySuite) TestEventsImageFilterPush(c *check.C) {
out, _ = dockerCmd(c, "events", "--since=0", "-f", "image="+repoName, "-f", "event=push", "--until="+strconv.Itoa(int(since)))
c.Assert(out, checker.Contains, repoName, check.Commentf("Missing 'push' log event for %s", repoName))
}
func (s *DockerSuite) TestEventsFilterType(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
name := "labelfiltertest"
label := "io.docker.testing=image"
// Build a test image.
_, err := buildImage(name, fmt.Sprintf(`
FROM busybox:latest
LABEL %s`, label), true)
c.Assert(err, checker.IsNil, check.Commentf("Couldn't create image"))
dockerCmd(c, "tag", name, "labelfiltertest:tag1")
dockerCmd(c, "tag", name, "labelfiltertest:tag2")
dockerCmd(c, "tag", "busybox:latest", "labelfiltertest:tag3")
out, _ := dockerCmd(
c,
"events",
fmt.Sprintf("--since=%d", since),
fmt.Sprintf("--until=%d", daemonTime(c).Unix()),
"--filter", fmt.Sprintf("label=%s", label),
"--filter", "type=image")
events := strings.Split(strings.TrimSpace(out), "\n")
// 2 events from the "docker tag" command, another one is from "docker build"
c.Assert(events, checker.HasLen, 3, check.Commentf("Events == %s", events))
for _, e := range events {
c.Assert(e, checker.Contains, "labelfiltertest")
}
out, _ = dockerCmd(
c,
"events",
fmt.Sprintf("--since=%d", since),
fmt.Sprintf("--until=%d", daemonTime(c).Unix()),
"--filter", fmt.Sprintf("label=%s", label),
"--filter", "type=container")
events = strings.Split(strings.TrimSpace(out), "\n")
// Events generated by the container that builds the image
c.Assert(events, checker.HasLen, 3, check.Commentf("Events == %s", events))
out, _ = dockerCmd(
c,
"events",
fmt.Sprintf("--since=%d", since),
fmt.Sprintf("--until=%d", daemonTime(c).Unix()),
"--filter", "type=network")
events = strings.Split(strings.TrimSpace(out), "\n")
c.Assert(len(events), checker.GreaterOrEqualThan, 1, check.Commentf("Events == %s", events))
}
func (s *DockerSuite) TestEventsFilterImageInContainerAction(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
dockerCmd(c, "run", "--name", "test-container", "-d", "busybox", "true")
waitRun("test-container")
out, _ := dockerCmd(c, "events", "--filter", "image=busybox", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(len(events), checker.GreaterThan, 1, check.Commentf(out))
}
@@ -203,3 +203,143 @@ func (s *DockerSuite) TestNetworkEvents(c *check.C) {
c.Assert(netEvents[2], checker.Equals, "disconnect")
c.Assert(netEvents[3], checker.Equals, "destroy")
}
func (s *DockerSuite) TestEventsStreaming(c *check.C) {
testRequires(c, DaemonIsLinux)
observer, err := newEventObserver(c)
c.Assert(err, checker.IsNil)
err = observer.Start()
c.Assert(err, checker.IsNil)
defer observer.Stop()
out, _ := dockerCmd(c, "run", "-d", "busybox:latest", "true")
containerID := strings.TrimSpace(out)
testActions := map[string]chan bool{
"create": make(chan bool),
"start": make(chan bool),
"die": make(chan bool),
"destroy": make(chan bool),
}
matcher := matchEventLine(containerID, "container", testActions)
go observer.Match(matcher)
select {
case <-time.After(5 * time.Second):
observer.CheckEventError(c, containerID, "create", matcher)
case <-testActions["create"]:
// ignore, done
}
select {
case <-time.After(5 * time.Second):
observer.CheckEventError(c, containerID, "start", matcher)
case <-testActions["start"]:
// ignore, done
}
select {
case <-time.After(5 * time.Second):
observer.CheckEventError(c, containerID, "die", matcher)
case <-testActions["die"]:
// ignore, done
}
dockerCmd(c, "rm", containerID)
select {
case <-time.After(5 * time.Second):
observer.CheckEventError(c, containerID, "destroy", matcher)
case <-testActions["destroy"]:
// ignore, done
}
}
func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) {
testRequires(c, DaemonIsLinux)
observer, err := newEventObserver(c)
c.Assert(err, checker.IsNil)
err = observer.Start()
c.Assert(err, checker.IsNil)
defer observer.Stop()
name := "testimageevents"
imageID, err := buildImage(name,
`FROM scratch
MAINTAINER "docker"`,
true)
c.Assert(err, checker.IsNil)
c.Assert(deleteImages(name), checker.IsNil)
testActions := map[string]chan bool{
"untag": make(chan bool),
"delete": make(chan bool),
}
matcher := matchEventLine(imageID, "image", testActions)
go observer.Match(matcher)
select {
case <-time.After(10 * time.Second):
observer.CheckEventError(c, imageID, "untag", matcher)
case <-testActions["untag"]:
// ignore, done
}
select {
case <-time.After(10 * time.Second):
observer.CheckEventError(c, imageID, "delete", matcher)
case <-testActions["delete"]:
// ignore, done
}
}
func (s *DockerSuite) TestEventsFilterVolumeAndNetworkType(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
dockerCmd(c, "network", "create", "test-event-network-type")
dockerCmd(c, "volume", "create", "--name", "test-event-volume-type")
out, _ := dockerCmd(c, "events", "--filter", "type=volume", "--filter", "type=network", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(len(events), checker.GreaterOrEqualThan, 2, check.Commentf(out))
networkActions := eventActionsByIDAndType(c, events, "test-event-network-type", "network")
volumeActions := eventActionsByIDAndType(c, events, "test-event-volume-type", "volume")
c.Assert(volumeActions[0], checker.Equals, "create")
c.Assert(networkActions[0], checker.Equals, "create")
}
func (s *DockerSuite) TestEventsFilterVolumeID(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
dockerCmd(c, "volume", "create", "--name", "test-event-volume-id")
out, _ := dockerCmd(c, "events", "--filter", "volume=test-event-volume-id", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(events, checker.HasLen, 1)
c.Assert(events[0], checker.Contains, "test-event-volume-id")
c.Assert(events[0], checker.Contains, "driver=local")
}
func (s *DockerSuite) TestEventsFilterNetworkID(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
dockerCmd(c, "network", "create", "test-event-network-local")
out, _ := dockerCmd(c, "events", "--filter", "network=test-event-network-local", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(events, checker.HasLen, 1)
c.Assert(events[0], checker.Contains, "test-event-network-local")
c.Assert(events[0], checker.Contains, "type=bridge")
}
@@ -10,6 +10,7 @@ import (
"strconv"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/integration/checker"
"github.com/go-check/check"
)
@@ -27,13 +28,15 @@ var (
)
// eventMatcher is a function that tries to match an event input.
type eventMatcher func(text string)
type eventMatcher func(text string) bool
// eventObserver runs an events commands and observes its output.
type eventObserver struct {
buffer *bytes.Buffer
command *exec.Cmd
stdout io.Reader
buffer *bytes.Buffer
command *exec.Cmd
scanner *bufio.Scanner
startTime string
disconnectionError error
}
// newEventObserver creates the observer and initializes the command
@@ -45,7 +48,8 @@ func newEventObserver(c *check.C, args ...string) (*eventObserver, error) {
// newEventObserverWithBacklog creates a new observer changing the start time of the backlog to return.
func newEventObserverWithBacklog(c *check.C, since int64, args ...string) (*eventObserver, error) {
cmdArgs := []string{"events", "--since", strconv.FormatInt(since, 10)}
startTime := strconv.FormatInt(since, 10)
cmdArgs := []string{"events", "--since", startTime}
if len(args) > 0 {
cmdArgs = append(cmdArgs, args...)
}
@@ -56,9 +60,10 @@ func newEventObserverWithBacklog(c *check.C, since int64, args ...string) (*even
}
return &eventObserver{
buffer: new(bytes.Buffer),
command: eventsCmd,
stdout: stdout,
buffer: new(bytes.Buffer),
command: eventsCmd,
scanner: bufio.NewScanner(stdout),
startTime: startTime,
}, nil
}
@@ -75,43 +80,61 @@ func (e *eventObserver) Stop() {
// Match tries to match the events output with a given matcher.
func (e *eventObserver) Match(match eventMatcher) {
scanner := bufio.NewScanner(e.stdout)
for scanner.Scan() {
text := scanner.Text()
for e.scanner.Scan() {
text := e.scanner.Text()
e.buffer.WriteString(text)
e.buffer.WriteString("\n")
match(text)
}
err := e.scanner.Err()
if err == nil {
err = io.EOF
}
logrus.Debug("EventObserver scanner loop finished: %v", err)
e.disconnectionError = err
}
// TimeoutError generates an error for a given containerID and event type.
// It attaches the events command output to the error.
func (e *eventObserver) TimeoutError(id, event string) error {
return fmt.Errorf("failed to observe event `%s` for %s\n%v", event, id, e.output())
}
func (e *eventObserver) CheckEventError(c *check.C, id, event string, match eventMatcher) {
var foundEvent bool
scannerOut := e.buffer.String()
// output returns the events command output read until now by the Match goroutine.
func (e *eventObserver) output() string {
return e.buffer.String()
if e.disconnectionError != nil {
until := strconv.FormatInt(daemonTime(c).Unix(), 10)
out, _ := dockerCmd(c, "events", "--since", e.startTime, "--until", until)
events := strings.Split(strings.TrimSpace(out), "\n")
for _, e := range events {
if match(e) {
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 action and true if the regular expression matches with the given id and event type.
// It returns an empty string and false if there is no match.
func matchEventLine(id, eventType string, actions map[string]chan bool) eventMatcher {
return func(text string) {
return func(text string) bool {
matches := parseEventText(text)
if matches == nil {
return
if len(matches) == 0 {
return false
}
if matchIDAndEventType(matches, id, eventType) {
if ch, ok := actions[matches["action"]]; ok {
close(ch)
return true
}
}
return false
}
}
@@ -119,12 +142,12 @@ func matchEventLine(id, eventType string, actions map[string]chan bool) eventMat
// the matchers in a map.
func parseEventText(text string) map[string]string {
matches := eventCliRegexp.FindAllStringSubmatch(text, -1)
md := map[string]string{}
if len(matches) == 0 {
return nil
return md
}
names := eventCliRegexp.SubexpNames()
md := map[string]string{}
for i, n := range matches[0] {
md[names[i]] = n
}
@@ -135,7 +158,6 @@ func parseEventText(text string) map[string]string {
// It fails if the text is not in the event format.
func parseEventAction(c *check.C, text string) string {
matches := parseEventText(text)
c.Assert(matches, checker.Not(checker.IsNil))
return matches["action"]
}
@@ -177,10 +199,9 @@ func parseEvents(c *check.C, out, match string) {
events := strings.Split(strings.TrimSpace(out), "\n")
for _, event := range events {
matches := parseEventText(event)
c.Assert(matches, checker.Not(checker.IsNil))
matched, err := regexp.MatchString(match, matches["action"])
c.Assert(err, checker.IsNil)
c.Assert(matched, checker.True)
c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
}
}
@@ -188,11 +209,10 @@ func parseEventsWithID(c *check.C, out, match, id string) {
events := strings.Split(strings.TrimSpace(out), "\n")
for _, event := range events {
matches := parseEventText(event)
c.Assert(matches, checker.Not(checker.IsNil))
c.Assert(matchEventID(matches, id), checker.True)
matched, err := regexp.MatchString(match, matches["action"])
c.Assert(err, checker.IsNil)
c.Assert(matched, checker.True)
c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
}
}