| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package bundler |
| 6 |
| 7 import ( |
| 8 "fmt" |
| 9 "strconv" |
| 10 "strings" |
| 11 "testing" |
| 12 "time" |
| 13 |
| 14 "github.com/luci/luci-go/common/clock/testclock" |
| 15 "github.com/luci/luci-go/common/logdog/protocol" |
| 16 "github.com/luci/luci-go/common/proto/google" |
| 17 . "github.com/smartystreets/goconvey/convey" |
| 18 ) |
| 19 |
| 20 func parse(desc string) (*protocol.ButlerLogBundle_Entry, []*protocol.LogEntry)
{ |
| 21 comp := strings.Split(desc, ":") |
| 22 name, entries := comp[0], comp[1:] |
| 23 |
| 24 be := &protocol.ButlerLogBundle_Entry{ |
| 25 Desc: &protocol.LogStreamDescriptor{ |
| 26 Name: name, |
| 27 }, |
| 28 } |
| 29 |
| 30 logs := make([]*protocol.LogEntry, len(entries)) |
| 31 for idx, l := range entries { |
| 32 comp := strings.SplitN(l, "@", 2) |
| 33 key, size := comp[0], 0 |
| 34 if len(comp) == 2 { |
| 35 size, _ = strconv.Atoi(comp[1]) |
| 36 } |
| 37 |
| 38 le := &protocol.LogEntry{ |
| 39 Content: &protocol.LogEntry_Text{Text: &protocol.Text{ |
| 40 Lines: []*protocol.Text_Line{ |
| 41 {Value: key}, |
| 42 }, |
| 43 }}, |
| 44 } |
| 45 |
| 46 // Pad missing data, if requested. |
| 47 if size > 0 { |
| 48 missing := size - protoSize(le) |
| 49 if missing > 0 { |
| 50 le.GetText().Lines = append(le.GetText().Lines,
&protocol.Text_Line{ |
| 51 Value: strings.Repeat("!", missing), |
| 52 }) |
| 53 } |
| 54 } |
| 55 logs[idx] = le |
| 56 } |
| 57 return be, logs |
| 58 } |
| 59 |
| 60 // gen generates a ButlerLogBundle_Entry based on a description string. |
| 61 // |
| 62 // The string goes: "a:1:2:3", where "a" is the name of the stream and |
| 63 // "1", "2", and "3", are different LogEntry within the stream. |
| 64 // |
| 65 // Note that the generated values are not valid, as they will be missing |
| 66 // several fields. This is for bundling tests only :) |
| 67 // |
| 68 // Optionally, the string can include a size, e.g., "a:1@1024:...". This will |
| 69 // cause additional data to be generated to pad the LogEntry out to the desired |
| 70 // size. This is currently an approximation, as it doesn't take into account |
| 71 // tag/array size overhead of the additional data. |
| 72 func gen(desc string) *protocol.ButlerLogBundle_Entry { |
| 73 be, logs := parse(desc) |
| 74 be.Logs = logs |
| 75 return be |
| 76 } |
| 77 |
| 78 func logEntryName(le *protocol.LogEntry) string { |
| 79 t := le.GetText() |
| 80 if t == nil || len(t.Lines) == 0 { |
| 81 return "" |
| 82 } |
| 83 return t.Lines[0].Value |
| 84 } |
| 85 |
| 86 // "expected" is a notation to express a bundle entry and its keys: |
| 87 // "a": a bundle entry keyed on "a". |
| 88 // "+a": a terminal bundle entry keyed on "a". |
| 89 // "a:1:2:3": a bundle entry keyed on "a" with three log entries, each keyed o
n |
| 90 // "1", "2", and "3" respectively. |
| 91 func shouldHaveBundleEntries(actual interface{}, expected ...interface{}) string
{ |
| 92 bundle := actual.(*protocol.ButlerLogBundle) |
| 93 |
| 94 errors := []string{} |
| 95 fail := func(f string, args ...interface{}) { |
| 96 errors = append(errors, fmt.Sprintf(f, args...)) |
| 97 } |
| 98 |
| 99 term := make(map[string]bool) |
| 100 exp := make(map[string][]string) |
| 101 |
| 102 // Parse expectation strings. |
| 103 for _, e := range expected { |
| 104 s := e.(string) |
| 105 if len(s) == 0 { |
| 106 continue |
| 107 } |
| 108 |
| 109 t := false |
| 110 if s[0] == '+' { |
| 111 t = true |
| 112 s = s[1:] |
| 113 } |
| 114 |
| 115 parts := strings.Split(s, ":") |
| 116 name := parts[0] |
| 117 term[name] = t |
| 118 |
| 119 if len(parts) > 1 { |
| 120 exp[name] = append(exp[name], parts[1:]...) |
| 121 } |
| 122 } |
| 123 |
| 124 entries := make(map[string]*protocol.ButlerLogBundle_Entry) |
| 125 for _, be := range bundle.Entries { |
| 126 entries[be.Desc.Name] = be |
| 127 } |
| 128 for name, t := range term { |
| 129 be := entries[name] |
| 130 if be == nil { |
| 131 fail("No bundle entry for [%s]", name) |
| 132 continue |
| 133 } |
| 134 delete(entries, name) |
| 135 |
| 136 if t != be.Terminal { |
| 137 fail("Bundle entry [%s] doesn't match expected terminal
state (exp: %v != act: %v)", |
| 138 name, t, be.Terminal) |
| 139 } |
| 140 |
| 141 logs := exp[name] |
| 142 for i, l := range logs { |
| 143 if i >= len(be.Logs) { |
| 144 fail("Bundle entry [%s] missing log: %s", name,
l) |
| 145 continue |
| 146 } |
| 147 le := be.Logs[i] |
| 148 |
| 149 if logEntryName(le) != l { |
| 150 fail("Bundle entry [%s] log %d doesn't match exp
ected (exp: %s != act: %s)", |
| 151 name, i, l, logEntryName(le)) |
| 152 continue |
| 153 } |
| 154 } |
| 155 if len(be.Logs) > len(logs) { |
| 156 for _, le := range be.Logs[len(logs):] { |
| 157 fail("Bundle entry [%s] has extra log entry: %s"
, name, logEntryName(le)) |
| 158 } |
| 159 } |
| 160 } |
| 161 for k := range entries { |
| 162 fail("Unexpected bundle entry present: [%s]", k) |
| 163 } |
| 164 return strings.Join(errors, "\n") |
| 165 } |
| 166 |
| 167 func TestBuilder(t *testing.T) { |
| 168 Convey(`A builder`, t, func() { |
| 169 tc := testclock.New(time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)) |
| 170 b := &builder{ |
| 171 template: protocol.ButlerLogBundle{ |
| 172 Source: "Test Source", |
| 173 Timestamp: google.NewTimestamp(tc.Now()), |
| 174 }, |
| 175 } |
| 176 templateSize := protoSize(&b.template) |
| 177 |
| 178 Convey(`Is not ready by default, and has no content.`, func() { |
| 179 b.size = templateSize + 1 |
| 180 So(b.ready(), ShouldBeFalse) |
| 181 So(b.hasContent(), ShouldBeFalse) |
| 182 |
| 183 Convey(`When exceeding the desired size with content, is
ready.`, func() { |
| 184 be, _ := parse("a") |
| 185 b.size = 1 |
| 186 b.setStreamTerminal(be, 0) |
| 187 So(b.ready(), ShouldBeTrue) |
| 188 }) |
| 189 }) |
| 190 |
| 191 Convey(`Has a bundleSize() and remaining value of the template.`
, func() { |
| 192 b.size = 1024 |
| 193 |
| 194 So(b.bundleSize(), ShouldEqual, templateSize) |
| 195 So(b.remaining(), ShouldEqual, 1024-templateSize) |
| 196 }) |
| 197 |
| 198 Convey(`With a size of 1024 and a 512-byte LogEntry, has content
, but is not ready.`, func() { |
| 199 b.size = 1024 |
| 200 be, logs := parse("a:1@512") |
| 201 b.add(be, logs[0]) |
| 202 So(b.hasContent(), ShouldBeTrue) |
| 203 So(b.ready(), ShouldBeFalse) |
| 204 |
| 205 Convey(`After adding another 512-byte LogEntry, is ready
.`, func() { |
| 206 be, logs := parse("a:2@512") |
| 207 b.add(be, logs[0]) |
| 208 So(b.ready(), ShouldBeTrue) |
| 209 }) |
| 210 }) |
| 211 |
| 212 Convey(`Has content after adding a terminal entry.`, func() { |
| 213 So(b.hasContent(), ShouldBeFalse) |
| 214 be, _ := parse("a") |
| 215 b.setStreamTerminal(be, 1024) |
| 216 So(b.hasContent(), ShouldBeTrue) |
| 217 }) |
| 218 |
| 219 for _, test := range []struct { |
| 220 title string |
| 221 |
| 222 streams []string |
| 223 terminal bool |
| 224 expected []string |
| 225 }{ |
| 226 {`Empty terminal entry`, |
| 227 []string{"a"}, true, []string{"+a"}}, |
| 228 {`Single non-terminal entry`, |
| 229 []string{"a:1"}, false, []string{"a:1"}}, |
| 230 {`Multiple non-terminal entries`, |
| 231 []string{"a:1:2:3:4"}, false, []string{"a:1:2:3:
4"}}, |
| 232 {`Single large entry`, |
| 233 []string{"a:1@1024"}, false, []string{"a:1"}}, |
| 234 {`Multiple terminal streams.`, |
| 235 []string{"a:1", "b:1", "a:2", "c:1"}, true, []st
ring{"+a:1:2", "+b:1", "+c:1"}}, |
| 236 {`Multiple large non-terminal streams.`, |
| 237 []string{"a:1@1024", "b:1@8192", "a:2@4096", "c:
1"}, false, []string{"a:1:2", "b:1", "c:1"}}, |
| 238 } { |
| 239 Convey(fmt.Sprintf(`Test Case: %q`, test.title), func()
{ |
| 240 for _, s := range test.streams { |
| 241 be, logs := parse(s) |
| 242 for _, le := range logs { |
| 243 b.add(be, le) |
| 244 } |
| 245 |
| 246 if test.terminal { |
| 247 b.setStreamTerminal(be, 1) |
| 248 } |
| 249 } |
| 250 |
| 251 Convey(`Constructed bundle matches expected.`, f
unc() { |
| 252 islice := make([]interface{}, len(test.e
xpected)) |
| 253 for i, exp := range test.expected { |
| 254 islice[i] = exp |
| 255 } |
| 256 So(b.bundle(), shouldHaveBundleEntries,
islice...) |
| 257 }) |
| 258 |
| 259 Convey(`Calculated size matches actual.`, func()
{ |
| 260 So(b.bundleSize(), ShouldEqual, protoSiz
e(b.bundle())) |
| 261 }) |
| 262 }) |
| 263 } |
| 264 }) |
| 265 } |
| OLD | NEW |