Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(173)

Unified Diff: client/internal/logdog/butler/bundler/builder_test.go

Issue 1412063008: logdog: Add bundler library. (Closed) Base URL: https://github.com/luci/luci-go@logdog-review-streamserver
Patch Set: Enhanced doc.go. Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: client/internal/logdog/butler/bundler/builder_test.go
diff --git a/client/internal/logdog/butler/bundler/builder_test.go b/client/internal/logdog/butler/bundler/builder_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..95b11facfd0b1d3a8ce548cd0322e6c3ec553ddc
--- /dev/null
+++ b/client/internal/logdog/butler/bundler/builder_test.go
@@ -0,0 +1,265 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package bundler
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/luci/luci-go/common/clock/testclock"
+ "github.com/luci/luci-go/common/logdog/protocol"
+ "github.com/luci/luci-go/common/proto/google"
+ . "github.com/smartystreets/goconvey/convey"
+)
+
+func parse(desc string) (*protocol.ButlerLogBundle_Entry, []*protocol.LogEntry) {
+ comp := strings.Split(desc, ":")
+ name, entries := comp[0], comp[1:]
+
+ be := &protocol.ButlerLogBundle_Entry{
+ Desc: &protocol.LogStreamDescriptor{
+ Name: name,
+ },
+ }
+
+ logs := make([]*protocol.LogEntry, len(entries))
+ for idx, l := range entries {
+ comp := strings.SplitN(l, "@", 2)
+ key, size := comp[0], 0
+ if len(comp) == 2 {
+ size, _ = strconv.Atoi(comp[1])
+ }
+
+ le := &protocol.LogEntry{
+ Content: &protocol.LogEntry_Text{Text: &protocol.Text{
+ Lines: []*protocol.Text_Line{
+ {Value: key},
+ },
+ }},
+ }
+
+ // Pad missing data, if requested.
+ if size > 0 {
+ missing := size - protoSize(le)
+ if missing > 0 {
+ le.GetText().Lines = append(le.GetText().Lines, &protocol.Text_Line{
+ Value: strings.Repeat("!", missing),
+ })
+ }
+ }
+ logs[idx] = le
+ }
+ return be, logs
+}
+
+// gen generates a ButlerLogBundle_Entry based on a description string.
+//
+// The string goes: "a:1:2:3", where "a" is the name of the stream and
+// "1", "2", and "3", are different LogEntry within the stream.
+//
+// Note that the generated values are not valid, as they will be missing
+// several fields. This is for bundling tests only :)
+//
+// Optionally, the string can include a size, e.g., "a:1@1024:...". This will
+// cause additional data to be generated to pad the LogEntry out to the desired
+// size. This is currently an approximation, as it doesn't take into account
+// tag/array size overhead of the additional data.
+func gen(desc string) *protocol.ButlerLogBundle_Entry {
+ be, logs := parse(desc)
+ be.Logs = logs
+ return be
+}
+
+func logEntryName(le *protocol.LogEntry) string {
+ t := le.GetText()
+ if t == nil || len(t.Lines) == 0 {
+ return ""
+ }
+ return t.Lines[0].Value
+}
+
+// "expected" is a notation to express a bundle entry and its keys:
+// "a": a bundle entry keyed on "a".
+// "+a": a terminal bundle entry keyed on "a".
+// "a:1:2:3": a bundle entry keyed on "a" with three log entries, each keyed on
+// "1", "2", and "3" respectively.
+func shouldHaveBundleEntries(actual interface{}, expected ...interface{}) string {
+ bundle := actual.(*protocol.ButlerLogBundle)
+
+ errors := []string{}
+ fail := func(f string, args ...interface{}) {
+ errors = append(errors, fmt.Sprintf(f, args...))
+ }
+
+ term := make(map[string]bool)
+ exp := make(map[string][]string)
+
+ // Parse expectation strings.
+ for _, e := range expected {
+ s := e.(string)
+ if len(s) == 0 {
+ continue
+ }
+
+ t := false
+ if s[0] == '+' {
+ t = true
+ s = s[1:]
+ }
+
+ parts := strings.Split(s, ":")
+ name := parts[0]
+ term[name] = t
+
+ if len(parts) > 1 {
+ exp[name] = append(exp[name], parts[1:]...)
+ }
+ }
+
+ entries := make(map[string]*protocol.ButlerLogBundle_Entry)
+ for _, be := range bundle.Entries {
+ entries[be.Desc.Name] = be
+ }
+ for name, t := range term {
+ be := entries[name]
+ if be == nil {
+ fail("No bundle entry for [%s]", name)
+ continue
+ }
+ delete(entries, name)
+
+ if t != be.Terminal {
+ fail("Bundle entry [%s] doesn't match expected terminal state (exp: %v != act: %v)",
+ name, t, be.Terminal)
+ }
+
+ logs := exp[name]
+ for i, l := range logs {
+ if i >= len(be.Logs) {
+ fail("Bundle entry [%s] missing log: %s", name, l)
+ continue
+ }
+ le := be.Logs[i]
+
+ if logEntryName(le) != l {
+ fail("Bundle entry [%s] log %d doesn't match expected (exp: %s != act: %s)",
+ name, i, l, logEntryName(le))
+ continue
+ }
+ }
+ if len(be.Logs) > len(logs) {
+ for _, le := range be.Logs[len(logs):] {
+ fail("Bundle entry [%s] has extra log entry: %s", name, logEntryName(le))
+ }
+ }
+ }
+ for k := range entries {
+ fail("Unexpected bundle entry present: [%s]", k)
+ }
+ return strings.Join(errors, "\n")
+}
+
+func TestBuilder(t *testing.T) {
+ Convey(`A builder`, t, func() {
+ tc := testclock.New(time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC))
+ b := &builder{
+ template: protocol.ButlerLogBundle{
+ Source: "Test Source",
+ Timestamp: google.NewTimestamp(tc.Now()),
+ },
+ }
+ templateSize := protoSize(&b.template)
+
+ Convey(`Is not ready by default, and has no content.`, func() {
+ b.size = templateSize + 1
+ So(b.ready(), ShouldBeFalse)
+ So(b.hasContent(), ShouldBeFalse)
+
+ Convey(`When exceeding the desired size with content, is ready.`, func() {
+ be, _ := parse("a")
+ b.size = 1
+ b.setStreamTerminal(be, 0)
+ So(b.ready(), ShouldBeTrue)
+ })
+ })
+
+ Convey(`Has a bundleSize() and remaining value of the template.`, func() {
+ b.size = 1024
+
+ So(b.bundleSize(), ShouldEqual, templateSize)
+ So(b.remaining(), ShouldEqual, 1024-templateSize)
+ })
+
+ Convey(`With a size of 1024 and a 512-byte LogEntry, has content, but is not ready.`, func() {
+ b.size = 1024
+ be, logs := parse("a:1@512")
+ b.add(be, logs[0])
+ So(b.hasContent(), ShouldBeTrue)
+ So(b.ready(), ShouldBeFalse)
+
+ Convey(`After adding another 512-byte LogEntry, is ready.`, func() {
+ be, logs := parse("a:2@512")
+ b.add(be, logs[0])
+ So(b.ready(), ShouldBeTrue)
+ })
+ })
+
+ Convey(`Has content after adding a terminal entry.`, func() {
+ So(b.hasContent(), ShouldBeFalse)
+ be, _ := parse("a")
+ b.setStreamTerminal(be, 1024)
+ So(b.hasContent(), ShouldBeTrue)
+ })
+
+ for _, test := range []struct {
+ title string
+
+ streams []string
+ terminal bool
+ expected []string
+ }{
+ {`Empty terminal entry`,
+ []string{"a"}, true, []string{"+a"}},
+ {`Single non-terminal entry`,
+ []string{"a:1"}, false, []string{"a:1"}},
+ {`Multiple non-terminal entries`,
+ []string{"a:1:2:3:4"}, false, []string{"a:1:2:3:4"}},
+ {`Single large entry`,
+ []string{"a:1@1024"}, false, []string{"a:1"}},
+ {`Multiple terminal streams.`,
+ []string{"a:1", "b:1", "a:2", "c:1"}, true, []string{"+a:1:2", "+b:1", "+c:1"}},
+ {`Multiple large non-terminal streams.`,
+ []string{"a:1@1024", "b:1@8192", "a:2@4096", "c:1"}, false, []string{"a:1:2", "b:1", "c:1"}},
+ } {
+ Convey(fmt.Sprintf(`Test Case: %q`, test.title), func() {
+ for _, s := range test.streams {
+ be, logs := parse(s)
+ for _, le := range logs {
+ b.add(be, le)
+ }
+
+ if test.terminal {
+ b.setStreamTerminal(be, 1)
+ }
+ }
+
+ Convey(`Constructed bundle matches expected.`, func() {
+ islice := make([]interface{}, len(test.expected))
+ for i, exp := range test.expected {
+ islice[i] = exp
+ }
+ So(b.bundle(), shouldHaveBundleEntries, islice...)
+ })
+
+ Convey(`Calculated size matches actual.`, func() {
+ So(b.bundleSize(), ShouldEqual, protoSize(b.bundle()))
+ })
+ })
+ }
+ })
+}

Powered by Google App Engine
This is Rietveld 408576698