Skip to content

Commit

Permalink
feat(flakybot): notify users when no logs are found (#4679)
Browse files Browse the repository at this point in the history
* feat(flakybot): notify users when no logs are found

* fix: exit 1 and minor style fixes

Co-authored-by: Jeff Ching <chingor@google.com>
  • Loading branch information
tbpg and chingor13 committed Nov 21, 2022
1 parent d14ad27 commit bc6fdd9
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 62 deletions.
128 changes: 77 additions & 51 deletions packages/flakybot/flakybot.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,24 @@ func main() {
log.Println("Sending logs to Flaky Bot...")
log.Println("See https://github.com/googleapis/repo-automation-bots/tree/main/packages/flakybot.")

if ok := publish(cfg); !ok {
logs, err := findLogs(cfg.logsDir)
if err != nil {
log.Printf("Error searching for logs: %v", err)
os.Exit(1)
}
if len(logs) == 0 {
log.Printf("No sponge_log.xml files found in %s. Did you forget to generate sponge_log.xml?", cfg.logsDir)
os.Exit(1)
}

p, err := pubSubPublisher(context.Background(), cfg)
if err != nil {
log.Printf("Could not connect to Pub/Sub: %v", err)
os.Exit(1)
}

if err := publish(context.Background(), cfg, p, logs); err != nil {
log.Printf("Could not publish: %v", err)
os.Exit(1)
}

Expand Down Expand Up @@ -164,11 +181,7 @@ See https://github.com/apps/flaky-bot/.`)
return true
}

// publish searches for sponge_log.xml files and publishes them to Pub/Sub.
// publish logs a message and returns false if there was an error.
func publish(cfg *config) (ok bool) {
ctx := context.Background()

func pubSubPublisher(ctx context.Context, cfg *config) (*publisher, error) {
opts := []option.ClientOption{}

if cfg.serviceAccount != "" {
Expand All @@ -177,19 +190,40 @@ func publish(cfg *config) (ok bool) {

client, err := pubsub.NewClient(ctx, cfg.projectID, opts...)
if err != nil {
log.Printf("Unable to connect to Pub/Sub: %v", err)
return false
return nil, fmt.Errorf("unable to connect to Pub/Sub: %v", err)
}
topic := client.Topic(cfg.topicID)
p := &publisher{topic: topic}
return &publisher{topic: topic}, nil
}

// Handle logs in the current directory.
if err := filepath.WalkDir(cfg.logsDir, processLog(ctx, cfg, p)); err != nil {
log.Printf("Error publishing logs: %v", err)
return false
// findLogs searches dir for sponge_log.xml files and returns their paths.
func findLogs(dir string) ([]string, error) {
var paths []string
walk := func(path string, dirEntry fs.DirEntry, err error) error {
if err != nil {
return err
}
if !strings.HasSuffix(dirEntry.Name(), "sponge_log.xml") {
return nil
}
paths = append(paths, path)
return nil
}
if err := filepath.WalkDir(dir, walk); err != nil {
return nil, err
}
return paths, nil
}

return true
// publish publishes the given log files with the given publisher.
func publish(ctx context.Context, cfg *config, p messagePublisher, logs []string) error {
for _, path := range logs {
if err := processLog(ctx, cfg, p, path); err != nil {
return fmt.Errorf("publishing logs: %v", err)
}
}

return nil
}

// detectRepo tries to detect the repo from the environment.
Expand Down Expand Up @@ -236,42 +270,34 @@ func (p *publisher) publish(ctx context.Context, msg *pubsub.Message) (serverID
return p.topic.Publish(ctx, msg).Get(ctx)
}

// processLog is used to process log files and publish them to Pub/Sub.
func processLog(ctx context.Context, cfg *config, p messagePublisher) fs.WalkDirFunc {
return func(path string, dirEntry fs.DirEntry, err error) error {
if err != nil {
return err
}
if !strings.HasSuffix(dirEntry.Name(), "sponge_log.xml") {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("os.ReadFile(%q): %v", path, err)
}
enc := base64.StdEncoding.EncodeToString(data)
msg := message{
Name: "flakybot",
Type: "function",
Location: "us-central1",
Installation: githubInstallation{ID: cfg.installationID},
Repo: cfg.repo,
Commit: cfg.commit,
BuildURL: cfg.buildURL,
XUnitXML: enc,
}
data, err = json.Marshal(msg)
if err != nil {
return fmt.Errorf("json.Marshal: %v", err)
}
pubsubMsg := &pubsub.Message{
Data: data,
}
id, err := p.publish(ctx, pubsubMsg)
if err != nil {
return fmt.Errorf("Pub/Sub Publish.Get: %v", err)
}
log.Printf("Published %s (%v)!", path, id)
return nil
// processLog is used to process log files and publish them with the given publisher.
func processLog(ctx context.Context, cfg *config, p messagePublisher, path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("os.ReadFile(%q): %v", path, err)
}
enc := base64.StdEncoding.EncodeToString(data)
msg := message{
Name: "flakybot",
Type: "function",
Location: "us-central1",
Installation: githubInstallation{ID: cfg.installationID},
Repo: cfg.repo,
Commit: cfg.commit,
BuildURL: cfg.buildURL,
XUnitXML: enc,
}
data, err = json.Marshal(msg)
if err != nil {
return fmt.Errorf("json.Marshal: %v", err)
}
pubsubMsg := &pubsub.Message{
Data: data,
}
id, err := p.publish(ctx, pubsubMsg)
if err != nil {
return fmt.Errorf("Pub/Sub Publish.Get: %v", err)
}
log.Printf("Published %s (%v)!", path, id)
return nil
}
31 changes: 20 additions & 11 deletions packages/flakybot/flakybot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"log"
"os"
"path/filepath"
Expand Down Expand Up @@ -88,7 +87,7 @@ func TestSetDefaults(t *testing.T) {
repo: "GoogleCloudPlatform/golang-samples",
installationID: "5943459",
commit: "abc123",
buildURL: fmt.Sprintf("[Build Status](https://source.cloud.google.com/results/invocations/test), [Sponge](http://sponge2/test)"),
buildURL: "[Build Status](https://source.cloud.google.com/results/invocations/test), [Sponge](http://sponge2/test)",
},
wantOK: true,
},
Expand Down Expand Up @@ -131,7 +130,7 @@ func TestSetDefaults(t *testing.T) {
}

func TestDetectRepo(t *testing.T) {
log.SetOutput(ioutil.Discard)
log.SetOutput(io.Discard)
defer log.SetOutput(os.Stderr)
tests := []struct {
envVar string
Expand Down Expand Up @@ -230,8 +229,9 @@ func (p *fakePublisher) publish(_ context.Context, msg *pubsub.Message) (serverI
return "", nil
}

func TestProcessLog(t *testing.T) {
func TestFindAndPublish(t *testing.T) {
filesToCreate := []string{"sponge_log.xml", "hello/sponge_log.xml", "unused.txt"}
numLogFiles := 2

tmpdir, err := os.MkdirTemp(os.TempDir(), "flakybot-")
if err != nil {
Expand All @@ -254,23 +254,32 @@ func TestProcessLog(t *testing.T) {

cfg := &config{
installationID: "installation-id",
repo: "googleapis/repo-automation-bogs",
repo: "googleapis/repo-automation-bots",
commit: "abc123",
buildURL: "https://google.com",
logsDir: tmpdir,
}

p := &fakePublisher{}
logs, err := findLogs(cfg.logsDir)
if err != nil {
t.Fatalf("Error finding logs in %q: %v", cfg.logsDir, err)
}

if err := filepath.WalkDir(tmpdir, processLog(context.Background(), cfg, p)); err != nil {
if got := len(logs); got != numLogFiles {
t.Errorf("findLogs found %d files, want %d", got, numLogFiles)
}

p := &fakePublisher{}
if err := publish(context.Background(), cfg, p, logs); err != nil {
t.Fatalf("Error publishing logs: %v", err)
}

if got := len(p.called); got != 2 {
t.Errorf("processLog called %d times, want %d", got, 2)
if got := len(p.called); got != numLogFiles {
t.Errorf("publish called %d times, want %d", got, numLogFiles)
}
for _, got := range p.called {
if !strings.Contains(got, wantEnc) {
t.Errorf("processLog published message %v, want to contain %q", got, wantEnc)
t.Errorf("published message %v, want to contain %q", got, wantEnc)
}
}
}

0 comments on commit bc6fdd9

Please sign in to comment.