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

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: Rewrote bundle logic (and associated updates). Created 5 years, 4 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 secret := hash(e, "secret")
99 name := key(e, "name")
100 contentType := "test/data"
101
102 be := &protocol.ButlerLogBundle_Entry{
103 Desc: &protocol.LogStreamDescriptor{
104 Prefix: &e,
105 Name: &name,
106 ContentType: &contentType,
107 Timestamp: protoutil.NewTimestamp(testNow),
108 },
109 Secret: secret,
110 Terminal: &t,
111 }
112
113 if len(le) > 0 {
114 be.Logs = make([]*protocol.LogEntry, len(le))
115 for i, l := range le {
116 be.Logs[i] = &protocol.LogEntry{
117 Lines: []string{
118 l,
119 },
120 Data: [][]byte{
121 hash(l, "data0"),
122 hash(l, "data1"),
123 hash(l, "data2"),
124 },
125 }
126 }
127 }
128 return be
129 }
130
131 func logEntryName(le *protocol.LogEntry) string {
132 if len(le.GetLines()) != 1 {
133 return ""
134 }
135 return le.GetLines()[0]
136 }
137
138 // "expected" is a notation to express a bundle entry and its keys:
139 // "a": a bundle entry keyed on "a".
140 // "+a": a terminal bundle entry keyed on "a".
141 // "a:1:2:3": a bundle entry keyed on "a" with three log entries, each keyed o n
142 // "1", "2", and "3" respectively.
143 func shouldHaveBundleEntries(actual interface{}, expected ...interface{}) string {
144 bundle := actual.(*protocol.ButlerLogBundle)
145
146 errors := []string{}
147 fail := func(f string, args ...interface{}) {
148 errors = append(errors, fmt.Sprintf(f, args...))
149 }
150
151 term := make(map[string]bool)
152 exp := make(map[string][]string)
153
154 // Parse expectation strings.
155 for _, e := range expected {
156 s := e.(string)
157 if len(s) == 0 {
158 continue
159 }
160
161 t := false
162 if s[0] == '+' {
163 t = true
164 s = s[1:]
165 }
166
167 parts := strings.Split(s, ":")
168 name := parts[0]
169 term[name] = t
170
171 if len(parts) > 1 {
172 exp[name] = append(exp[name], parts[1:]...)
173 }
174 }
175
176 entries := make(map[string]*protocol.ButlerLogBundle_Entry)
177 for _, be := range bundle.GetEntries() {
178 entries[be.GetDesc().GetPrefix()] = be
179 }
180 for name, t := range term {
181 be := entries[name]
182 if be == nil {
183 fail("No bundle entry for [%s]", name)
184 continue
185 }
186 delete(entries, name)
187
188 if t != be.GetTerminal() {
189 fail("Bundle entry [%s] doesn't match expected terminal state (exp: %v != act: %v)",
190 name, t, be.GetTerminal())
191 }
192
193 logs := exp[name]
194 for i, l := range logs {
195 if i >= len(be.GetLogs()) {
196 fail("Bundle entry [%s] missing log: %s", name, l)
197 continue
198 }
199 le := be.GetLogs()[i]
200
201 if logEntryName(le) != l {
202 fail("Bundle entry [%s] log %d doesn't match exp ected (exp: %s != act: %s)",
203 name, i, l, logEntryName(le))
204 continue
205 }
206 }
207 if len(be.GetLogs()) > len(logs) {
208 for _, le := range be.GetLogs()[len(logs):] {
209 fail("Bundle entry [%s] has extra log entry: %s" , name, logEntryName(le))
210 }
211 }
212 }
213 for k := range entries {
214 fail("Unexpected bundle entry present: [%s]", k)
215 }
216 return strings.Join(errors, "\n")
217 }
218
219 func TestBundler(t *testing.T) {
220 Convey(`An empty Bundler`, t, func() {
221 b := New(Config{}).(*bundlerImpl)
222
223 Convey(`Has a size of 0 and nil GetBundles() return value.`, fun c() {
224 So(b.Size(), ShouldEqual, 0)
225 So(b.Empty(), ShouldBeTrue)
226 So(b.GetBundles(), ShouldBeNil)
227 })
228
229 Convey(`When adding an empty entry, still has size 0 and nil Get Bundles() return value.`, func() {
230 b.Append(gen("a", false))
231 So(b.Size(), ShouldEqual, 0)
232 So(b.Empty(), ShouldBeTrue)
233 So(b.GetBundles(), ShouldBeNil)
234 })
235
236 Convey(`Bundles a terminal entry with no logs.`, func() {
237 b.Append(gen("a", true))
238
239 size, empty, bundles := b.Size(), b.Empty(), b.GetBundle s()
240 So(empty, ShouldBeFalse)
241
242 So(len(bundles), ShouldEqual, 1)
243 So(size, ShouldBeGreaterThanOrEqualTo, proto.Size(bundle s[0]))
244 So(bundles[0], shouldHaveBundleEntries, "+a")
245 })
246
247 Convey(`Bundles an entry with 3 logs.`, func() {
248 b.Append(gen("a", false, "1", "2"))
249 b.Append(gen("a", false, "3"))
250
251 size, empty, bundles := b.Size(), b.Empty(), b.GetBundle s()
252 So(empty, ShouldBeFalse)
253
254 So(len(bundles), ShouldEqual, 1)
255 So(size, ShouldBeGreaterThanOrEqualTo, proto.Size(bundle s[0]))
256 So(bundles[0], shouldHaveBundleEntries, "a:1:2:3")
257 })
258
259 Convey(`Bundles 2 entries with 2 logs each and one terminal entr y with no logs.`, func() {
260 b.Append(gen("a", false, "1", "2"))
261 b.Append(gen("b", false, "3", "4"))
262 b.Append(gen("c", true))
263 b.Append(gen("d", false))
264
265 size, empty, bundles := b.Size(), b.Empty(), b.GetBundle s()
266 So(empty, ShouldBeFalse)
267
268 So(len(bundles), ShouldEqual, 1)
269 So(size, ShouldBeGreaterThanOrEqualTo, proto.Size(bundle s[0]))
270 So(bundles[0], shouldHaveBundleEntries, "a:1:2", "b:3:4" , "+c")
271 })
272 })
273
274 Convey(`A Bundler with a fake Sizer`, t, func() {
275 source := "test suite"
276 b := New(Config{
277 Threshold: 20,
278 TemplateBundle: protocol.ButlerLogBundle{
279 Source: &source,
280 },
281 NewSizer: func(*protocol.ButlerLogBundle) Sizer {
282 return &fakeSizer{
283 Bundle: 8,
284 BundleEntry: 2,
285 LogEntry: 5,
286 }
287 },
288 }).(*bundlerImpl)
289 So(b.Size(), ShouldEqual, 8)
290
291 Convey(`Adding an entry with 5 log messages outputs three bundle s.`, func() {
292 b.Append(gen("a", false, "1", "2", "3", "4", "5"))
293
294 bundles := b.GetBundles()
295 So(len(bundles), ShouldEqual, 3)
296
297 Convey(`All bundles use the template bundle's fields.`, func() {
298 So(bundles[0].GetSource(), ShouldEqual, source)
299 So(bundles[1].GetSource(), ShouldEqual, source)
300 So(bundles[2].GetSource(), ShouldEqual, source)
301 })
302
303 Convey(`Have the right entries: {1,2}, {3,4}, {5}.`, fun c() {
304 So(bundles[0], shouldHaveBundleEntries, "a:1:2")
305 So(bundles[1], shouldHaveBundleEntries, "a:3:4")
306 So(bundles[2], shouldHaveBundleEntries, "a:5")
307 })
308 })
309
310 Convey(`Adding two entries with 2 log messages each outputs firs t, then second.`, func() {
311 b.Append(gen("a", false, "1", "2"))
312 b.Append(gen("b", false, "3", "4"))
313
314 bundles := b.GetBundles()
315 So(len(bundles), ShouldEqual, 2)
316 So(bundles[0], shouldHaveBundleEntries, "a:1:2")
317 So(bundles[1], shouldHaveBundleEntries, "b:3:4")
318 })
319
320 Convey(`A non-terminal entry followed by a terminal version gets output as terminal.`, func() {
321 b.Append(gen("a", false, "1"))
322 b.Append(gen("a", true, "2"))
323
324 bundles := b.GetBundles()
325 So(len(bundles), ShouldEqual, 1)
326 So(bundles[0], shouldHaveBundleEntries, "+a:1:2")
327 })
328
329 Convey(`A terminal entry followed by a non-terminal version gets output as terminal.`, func() {
330 b.Append(gen("a", true, "1"))
331 b.Append(gen("a", false, "2"))
332 b.Append(gen("a", false))
333
334 bundles := b.GetBundles()
335 So(len(bundles), ShouldEqual, 1)
336 So(bundles[0], shouldHaveBundleEntries, "+a:1:2")
337 })
338
339 Convey(`When the base bundle is above threshold, clears logs and returns nil.`, func() {
340 b.Append(gen("a", true))
341
342 So(b.Size(), ShouldEqual, 10)
343 So(b.getBundlesImpl(7), ShouldBeNil)
344
345 So(b.Size(), ShouldEqual, 8)
346 So(b.getBundlesImpl(0), ShouldBeNil)
347 })
348
349 Convey(`When the bundle entry size is above threshold, clears lo gs and returns nil.`, func() {
350 b.Append(gen("a", true))
351
352 So(b.Size(), ShouldEqual, 10)
353 So(b.getBundlesImpl(9), ShouldBeNil)
354
355 So(b.Size(), ShouldEqual, 8)
356 So(b.getBundlesImpl(0), ShouldBeNil)
357 })
358
359 Convey(`When the bundle has a log entry larger than threshold, i t discards it.`, func() {
360 b.Append(gen("a", false, "a", "bbb", "cc", "d"))
361 b.Append(gen("b", false, "1", "2", "3"))
362
363 bundles := b.GetBundles()
364 So(len(bundles), ShouldEqual, 4)
365 So(bundles[0], shouldHaveBundleEntries, "a:a:d")
366 So(bundles[1], shouldHaveBundleEntries, "a:cc")
367 So(bundles[2], shouldHaveBundleEntries, "b:1:2")
368 So(bundles[3], shouldHaveBundleEntries, "b:3")
369
370 So(b.getBundlesImpl(0), ShouldBeNil)
371 })
372 })
373 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698