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

Side by Side Diff: client/internal/logdog/butler/bundler/bundler_test.go

Issue 1276923003: logdog: Add bundler library. (Closed) Base URL: https://github.com/luci/luci-go@logdog-review-streamserver
Patch Set: Updated from review. Created 5 years, 3 months 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 unified diff | Download patch
OLDNEW
(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 "crypto/md5"
9 "encoding/hex"
10 "fmt"
11 "strings"
12 "testing"
13 "time"
14
15 "github.com/golang/protobuf/proto"
16 "github.com/luci/luci-go/common/logdog/protocol"
17 "github.com/luci/luci-go/common/logdog/protocol/protoutil"
18 "github.com/luci/luci-go/common/logdog/types"
19 . "github.com/smartystreets/goconvey/convey"
20 )
21
22 var (
23 testNow = time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)
24 )
25
26 // fakeSizer is a Sizer implementation that counts (obviously incorrect) fixed
27 // sizes for each entry type.
28 type fakeSizer struct {
29 Bundle int64
30 BundleEntry int64
31 LogEntry int64
32
33 size int64
34 seen map[types.StreamPath]bool
35
36 lastSize int64
37 lastSeen types.StreamPath
38 }
39
40 func (s *fakeSizer) Size() int64 {
41 return s.Bundle + s.size
42 }
43
44 func (s *fakeSizer) Append(be *protocol.ButlerLogBundle_Entry, e *protocol.LogEn try) {
45 size := int64(0)
46
47 // Add the ButlerLogBundle_Entry code if we haven't seen it before.
48 path := protoutil.DescriptorPath(be.GetDesc())
49 if seen := s.seen[path]; !seen {
50 if s.seen == nil {
51 s.seen = map[types.StreamPath]bool{
52 path: true,
53 }
54 } else {
55 s.seen[path] = true
56 }
57 s.lastSeen = path
58 size += s.BundleEntry
59 } else {
60 s.lastSeen = ""
61 }
62
63 if e != nil {
64 // Each character in the line gets LogEntry space.
65 if len(e.GetLines()) == 1 {
66 size += s.LogEntry * int64(len(e.GetLines()[0]))
67 } else {
68 size += s.LogEntry
69 }
70 }
71
72 s.lastSize = size
73 s.size += size
74 }
75
76 func (s *fakeSizer) Undo() {
77 s.size -= s.lastSize
78 if s.lastSeen != "" {
79 s.seen[s.lastSeen] = false
80 }
81 }
82
83 func hash(s, t string) []byte {
84 sum := md5.Sum([]byte(fmt.Sprintf("%s::%s", s, t)))
85 return sum[:]
86 }
87
88 func key(s, t string) string {
89 return hex.EncodeToString(hash(s, t))
90 }
91
92 // addEntry generates a ButlerLogBundle_Entry and appends it to our Bundler via
93 // one or more calls to Append.
94 //
95 // If "le" strings are supplied, those will create generated LogEntry for that
96 // ButlerLogBundle_Entry.
97 func gen(e string, t bool, le ...string) *protocol.ButlerLogBundle_Entry {
98 name := key(e, "name")
99 contentType := "test/data"
100
101 be := &protocol.ButlerLogBundle_Entry{
102 Desc: &protocol.LogStreamDescriptor{
103 Prefix: &e,
104 Name: &name,
105 ContentType: &contentType,
106 Timestamp: protoutil.NewTimestamp(testNow),
107 },
108 Terminal: &t,
109 }
110
111 if len(le) > 0 {
112 be.Logs = make([]*protocol.LogEntry, len(le))
113 for i, l := range le {
114 be.Logs[i] = &protocol.LogEntry{
115 Lines: []string{
116 l,
117 },
118 Data: []*protocol.LogEntry_Data{
119 {
120 Value: hash(l, "data0"),
121 },
122 {
123 Value: hash(l, "data1"),
124 },
125 {
126 Value: hash(l, "data2"),
127 },
128 },
129 }
130 }
131 }
132 return be
133 }
134
135 func logEntryName(le *protocol.LogEntry) string {
136 if len(le.GetLines()) != 1 {
137 return ""
138 }
139 return le.GetLines()[0]
140 }
141
142 // "expected" is a notation to express a bundle entry and its keys:
143 // "a": a bundle entry keyed on "a".
144 // "+a": a terminal bundle entry keyed on "a".
145 // "a:1:2:3": a bundle entry keyed on "a" with three log entries, each keyed o n
146 // "1", "2", and "3" respectively.
147 func shouldHaveBundleEntries(actual interface{}, expected ...interface{}) string {
148 bundle := actual.(*protocol.ButlerLogBundle)
149
150 errors := []string{}
151 fail := func(f string, args ...interface{}) {
152 errors = append(errors, fmt.Sprintf(f, args...))
153 }
154
155 term := make(map[string]bool)
156 exp := make(map[string][]string)
157
158 // Parse expectation strings.
159 for _, e := range expected {
160 s := e.(string)
161 if len(s) == 0 {
162 continue
163 }
164
165 t := false
166 if s[0] == '+' {
167 t = true
168 s = s[1:]
169 }
170
171 parts := strings.Split(s, ":")
172 name := parts[0]
173 term[name] = t
174
175 if len(parts) > 1 {
176 exp[name] = append(exp[name], parts[1:]...)
177 }
178 }
179
180 entries := make(map[string]*protocol.ButlerLogBundle_Entry)
181 for _, be := range bundle.GetEntries() {
182 entries[be.GetDesc().GetPrefix()] = be
183 }
184 for name, t := range term {
185 be := entries[name]
186 if be == nil {
187 fail("No bundle entry for [%s]", name)
188 continue
189 }
190 delete(entries, name)
191
192 if t != be.GetTerminal() {
193 fail("Bundle entry [%s] doesn't match expected terminal state (exp: %v != act: %v)",
194 name, t, be.GetTerminal())
195 }
196
197 logs := exp[name]
198 for i, l := range logs {
199 if i >= len(be.GetLogs()) {
200 fail("Bundle entry [%s] missing log: %s", name, l)
201 continue
202 }
203 le := be.GetLogs()[i]
204
205 if logEntryName(le) != l {
206 fail("Bundle entry [%s] log %d doesn't match exp ected (exp: %s != act: %s)",
207 name, i, l, logEntryName(le))
208 continue
209 }
210 }
211 if len(be.GetLogs()) > len(logs) {
212 for _, le := range be.GetLogs()[len(logs):] {
213 fail("Bundle entry [%s] has extra log entry: %s" , name, logEntryName(le))
214 }
215 }
216 }
217 for k := range entries {
218 fail("Unexpected bundle entry present: [%s]", k)
219 }
220 return strings.Join(errors, "\n")
221 }
222
223 func TestBundler(t *testing.T) {
224 Convey(`An empty Bundler`, t, func() {
225 b := New(Config{}).(*bundlerImpl)
226
227 Convey(`Has a size of 0 and nil GetBundles() return value.`, fun c() {
228 So(b.Size(), ShouldEqual, 0)
229 So(b.Empty(), ShouldBeTrue)
230 So(b.GetBundles(), ShouldBeNil)
231 })
232
233 Convey(`When adding an empty entry, still has size 0 and nil Get Bundles() return value.`, func() {
234 b.Append(gen("a", false))
235 So(b.Size(), ShouldEqual, 0)
236 So(b.Empty(), ShouldBeTrue)
237 So(b.GetBundles(), ShouldBeNil)
238 })
239
240 Convey(`Bundles a terminal entry with no logs.`, func() {
241 b.Append(gen("a", true))
242
243 size, empty, bundles := b.Size(), b.Empty(), b.GetBundle s()
244 So(empty, ShouldBeFalse)
245
246 So(len(bundles), ShouldEqual, 1)
247 So(size, ShouldBeGreaterThanOrEqualTo, proto.Size(bundle s[0]))
248 So(bundles[0], shouldHaveBundleEntries, "+a")
249 })
250
251 Convey(`Bundles an entry with 3 logs.`, func() {
252 b.Append(gen("a", false, "1", "2"))
253 b.Append(gen("a", false, "3"))
254
255 size, empty, bundles := b.Size(), b.Empty(), b.GetBundle s()
256 So(empty, ShouldBeFalse)
257
258 So(len(bundles), ShouldEqual, 1)
259 So(size, ShouldBeGreaterThanOrEqualTo, proto.Size(bundle s[0]))
260 So(bundles[0], shouldHaveBundleEntries, "a:1:2:3")
261 })
262
263 Convey(`Bundles 2 entries with 2 logs each and one terminal entr y with no logs.`, func() {
264 b.Append(gen("a", false, "1", "2"))
265 b.Append(gen("b", false, "3", "4"))
266 b.Append(gen("c", true))
267 b.Append(gen("d", false))
268
269 size, empty, bundles := b.Size(), b.Empty(), b.GetBundle s()
270 So(empty, ShouldBeFalse)
271
272 So(len(bundles), ShouldEqual, 1)
273 So(size, ShouldBeGreaterThanOrEqualTo, proto.Size(bundle s[0]))
274 So(bundles[0], shouldHaveBundleEntries, "a:1:2", "b:3:4" , "+c")
275 })
276 })
277
278 Convey(`A Bundler with a fake Sizer`, t, func() {
279 source := "test suite"
280 dropped := []string(nil)
281 b := New(Config{
282 Threshold: 20,
283 TemplateBundle: protocol.ButlerLogBundle{
284 Source: &source,
285 },
286 Deterministic: true,
287 NewSizer: func(*protocol.ButlerLogBundle) Sizer {
288 return &fakeSizer{
289 Bundle: 8,
290 BundleEntry: 2,
291 LogEntry: 5,
292 }
293 },
294 DropCallback: func(e *protocol.ButlerLogBundle_Entry) {
295 trm := ""
296 if e.GetTerminal() {
297 trm = "+"
298 }
299 prefix := e.GetDesc().GetPrefix()
300 if len(e.GetLogs()) > 0 {
301 for _, le := range e.GetLogs() {
302 dropped = append(dropped, fmt.Sp rintf("%s%s:%s", trm, prefix, le.GetLines()[0]))
303 }
304 } else {
305 dropped = append(dropped, fmt.Sprintf("% s%s", trm, prefix))
306 }
307 },
308 }).(*bundlerImpl)
309 So(b.Size(), ShouldEqual, 8)
310
311 Convey(`Adding an entry with 5 log messages outputs three bundle s.`, func() {
312 b.Append(gen("a", false, "1", "2", "3", "4", "5"))
313
314 bundles := b.GetBundles()
315 So(len(bundles), ShouldEqual, 3)
316
317 Convey(`All bundles use the template bundle's fields.`, func() {
318 So(bundles[0].GetSource(), ShouldEqual, source)
319 So(bundles[1].GetSource(), ShouldEqual, source)
320 So(bundles[2].GetSource(), ShouldEqual, source)
321 })
322
323 Convey(`Have the right entries: {1,2}, {3,4}, {5}.`, fun c() {
324 So(bundles[0], shouldHaveBundleEntries, "a:1:2")
325 So(bundles[1], shouldHaveBundleEntries, "a:3:4")
326 So(bundles[2], shouldHaveBundleEntries, "a:5")
327 })
328 })
329
330 Convey(`Adding two entries with 2 log messages each outputs firs t, then second.`, func() {
331 b.Append(gen("a", false, "1", "2"))
332 b.Append(gen("b", false, "3", "4"))
333
334 bundles := b.GetBundles()
335 So(len(bundles), ShouldEqual, 2)
336 So(bundles[0], shouldHaveBundleEntries, "a:1:2")
337 So(bundles[1], shouldHaveBundleEntries, "b:3:4")
338 })
339
340 Convey(`A non-terminal entry followed by a terminal version gets output as terminal.`, func() {
341 b.Append(gen("a", false, "1"))
342 b.Append(gen("a", true, "2"))
343
344 bundles := b.GetBundles()
345 So(len(bundles), ShouldEqual, 1)
346 So(bundles[0], shouldHaveBundleEntries, "+a:1:2")
347 })
348
349 Convey(`A terminal entry followed by a non-terminal version gets output as terminal.`, func() {
350 b.Append(gen("a", true, "1"))
351 b.Append(gen("a", false, "2"))
352 b.Append(gen("a", false))
353
354 bundles := b.GetBundles()
355 So(len(bundles), ShouldEqual, 1)
356 So(bundles[0], shouldHaveBundleEntries, "+a:1:2")
357 })
358
359 Convey(`When the base bundle is above threshold, clears logs and returns nil.`, func() {
360 b.Append(gen("a", true))
361
362 So(b.Size(), ShouldEqual, 10)
363 So(b.getBundlesImpl(7), ShouldBeNil)
364
365 So(b.Size(), ShouldEqual, 8)
366 So(b.getBundlesImpl(0), ShouldBeNil)
367 })
368
369 Convey(`When the bundle entry size is above threshold, clears lo gs and returns nil.`, func() {
370 b.Append(gen("a", true))
371
372 So(b.Size(), ShouldEqual, 10)
373 So(b.getBundlesImpl(9), ShouldBeNil)
374
375 So(b.Size(), ShouldEqual, 8)
376 So(b.getBundlesImpl(0), ShouldBeNil)
377 })
378
379 Convey(`When the bundle has a log entry larger than threshold, i t discards it.`, func() {
380 b.Append(gen("a", false, "a", "bbb", "cc", "d"))
381 b.Append(gen("b", false, "1", "2", "3"))
382
383 bundles := b.GetBundles()
384 So(len(bundles), ShouldEqual, 4)
385 So(bundles[0], shouldHaveBundleEntries, "a:a:d")
386 So(bundles[1], shouldHaveBundleEntries, "a:cc")
387 So(bundles[2], shouldHaveBundleEntries, "b:1:2")
388 So(bundles[3], shouldHaveBundleEntries, "b:3")
389 So(dropped, ShouldResemble, []string{"a:bbb"})
390
391 So(b.getBundlesImpl(0), ShouldBeNil)
392 })
393 })
394 }
OLDNEW
« no previous file with comments | « client/internal/logdog/butler/bundler/bundler_impl.go ('k') | client/internal/logdog/butler/bundler/doc.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698