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

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: Addressed code review comments. 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/smartystreets/goconvey/convey"
19 )
20
21 var (
22 testNow = time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)
23 )
24
25 // fakeSizer is a Sizer implementation that counts (obviously incorrect) fixed
26 // sizes for each entry type.
27 type fakeSizer struct {
28 Bundle int64
29 BundleEntry int64
30 LogEntry int64
31
32 size int64
33 }
34
35 func (s *fakeSizer) Size() int64 {
36 return s.Bundle + s.size
37 }
38
39 func (s *fakeSizer) AppendBundleEntry(*protocol.ButlerLogBundle_Entry) {
40 s.size += s.BundleEntry
41 }
42
43 func (s *fakeSizer) AppendLogEntry(*protocol.ButlerLogBundle_Entry, *protocol.Lo gEntry) {
44 s.size += s.LogEntry
45 }
46
47 func hash(s, t string) []byte {
48 sum := md5.Sum([]byte(fmt.Sprintf("%s::%s", s, t)))
49 return sum[:]
50 }
51
52 func key(s, t string) string {
53 return hex.EncodeToString(hash(s, t))
54 }
55
56 // addEntry generates a ButlerLogBundle_Entry and appends it to our Bundler via
57 // one or more calls to Append.
58 //
59 // If "le" strings are supplied, those will create generated LogEntry for that
60 // ButlerLogBundle_Entry.
61 func gen(e string, t bool, le ...string) *protocol.ButlerLogBundle_Entry {
62 secret := hash(e, "secret")
63 name := key(e, "name")
64 contentType := "test/data"
65
66 be := &protocol.ButlerLogBundle_Entry{
67 Desc: &protocol.LogStreamDescriptor{
68 Prefix: &e,
69 Name: &name,
70 ContentType: &contentType,
71 Timestamp: protoutil.NewTimestamp(testNow),
72 },
73 Secret: secret,
74 Terminal: &t,
75 }
76
77 if len(le) > 0 {
78 be.Logs = make([]*protocol.LogEntry, len(le))
79 for i, l := range le {
80 be.Logs[i] = &protocol.LogEntry{
81 Lines: []string{
82 l,
83 },
84 Data: [][]byte{
85 hash(l, "data0"),
86 hash(l, "data1"),
87 hash(l, "data2"),
88 },
89 }
90 }
91 }
92 return be
93 }
94
95 func logEntryName(le *protocol.LogEntry) string {
96 if len(le.GetLines()) != 1 {
97 return ""
98 }
99 return le.GetLines()[0]
100 }
101
102 // "expected" is a notation to express a bundle entry and its keys:
103 // "a": a bundle entry keyed on "a".
104 // "+a": a terminal bundle entry keyed on "a".
105 // "a:1:2:3": a bundle entry keyed on "a" with three log entries, each keyed o n
106 // "1", "2", and "3" respectively.
107 func shouldHaveBundleEntries(actual interface{}, expected ...interface{}) string {
108 bundle := actual.(*protocol.ButlerLogBundle)
109
110 errors := []string{}
111 fail := func(f string, args ...interface{}) {
112 errors = append(errors, fmt.Sprintf(f, args...))
113 }
114
115 term := make(map[string]bool)
116 exp := make(map[string][]string)
117
118 // Parse expectation strings.
119 for _, e := range expected {
120 s := e.(string)
121 if len(s) == 0 {
122 continue
123 }
124
125 t := false
126 if s[0] == '+' {
127 t = true
128 s = s[1:]
129 }
130
131 parts := strings.Split(s, ":")
132 name := parts[0]
133 term[name] = t
134
135 if len(parts) > 1 {
136 exp[name] = append(exp[name], parts[1:]...)
137 }
138 }
139
140 entries := make(map[string]*protocol.ButlerLogBundle_Entry)
141 for _, be := range bundle.GetEntries() {
142 entries[be.GetDesc().GetPrefix()] = be
143 }
144 for name, t := range term {
145 be := entries[name]
146 if be == nil {
147 fail("No bundle entry for [%s]", name)
148 continue
149 }
150 delete(entries, name)
151
152 if t != be.GetTerminal() {
153 fail("Bundle entry [%s] doesn't match expected terminal state (exp: %v != act: %v)",
154 name, t, be.GetTerminal())
155 }
156
157 logs := exp[name]
158 for i, l := range logs {
159 if i >= len(be.GetLogs()) {
160 fail("Bundle entry [%s] missing log: %s", name, l)
161 continue
162 }
163 le := be.GetLogs()[i]
164
165 if logEntryName(le) != l {
166 fail("Bundle entry [%s] log %d doesn't match exp ected (exp: %s != act: %s)",
167 name, i, l, logEntryName(le))
168 continue
169 }
170 }
171 if len(be.GetLogs()) > len(logs) {
172 for _, le := range be.GetLogs()[len(logs):] {
173 fail("Bundle entry [%s] has extra log entry: %s" , name, logEntryName(le))
174 }
175 }
176 }
177 for k := range entries {
178 fail("Unexpected bundle entry present: [%s]", k)
179 }
180 return strings.Join(errors, "\n")
181 }
182
183 func TestBundler(t *testing.T) {
184 Convey(`An empty Bundler`, t, func() {
185 b := New(Config{}).(*bundlerImpl)
186
187 Convey(`Has a size of 0 and nil GetBundles() return value.`, fun c() {
188 So(b.Size(), ShouldEqual, 0)
189 So(b.Empty(), ShouldBeTrue)
190 So(b.GetBundles(0), ShouldBeNil)
191 })
192
193 Convey(`When adding an empty entry, still has size 0 and nil Get Bundles() return value.`, func() {
194 b.Append(gen("a", false))
195 So(b.Size(), ShouldEqual, 0)
196 So(b.Empty(), ShouldBeTrue)
197 So(b.GetBundles(0), ShouldBeNil)
198 })
199
200 Convey(`Bundles a terminal entry with no logs.`, func() {
201 b.Append(gen("a", true))
202
203 size, empty, bundles := b.Size(), b.Empty(), b.GetBundle s(0)
204 So(empty, ShouldBeFalse)
205
206 So(len(bundles), ShouldEqual, 1)
207 So(size, ShouldBeGreaterThanOrEqualTo, proto.Size(bundle s[0]))
208 So(bundles[0], shouldHaveBundleEntries, "+a")
209 })
210
211 Convey(`Bundles an entry with 3 logs.`, func() {
212 b.Append(gen("a", false, "1", "2"))
213 b.Append(gen("a", false, "3"))
214
215 size, empty, bundles := b.Size(), b.Empty(), b.GetBundle s(0)
216 So(empty, ShouldBeFalse)
217
218 So(len(bundles), ShouldEqual, 1)
219 So(size, ShouldBeGreaterThanOrEqualTo, proto.Size(bundle s[0]))
220 So(bundles[0], shouldHaveBundleEntries, "a:1:2:3")
221 })
222
223 Convey(`Bundles 2 entries with 2 logs each and one terminal entr y with no logs.`, func() {
224 b.Append(gen("a", false, "1", "2"))
225 b.Append(gen("b", false, "3", "4"))
226 b.Append(gen("c", true))
227 b.Append(gen("d", false))
228
229 size, empty, bundles := b.Size(), b.Empty(), b.GetBundle s(0)
230 So(empty, ShouldBeFalse)
231
232 So(len(bundles), ShouldEqual, 1)
233 So(size, ShouldBeGreaterThanOrEqualTo, proto.Size(bundle s[0]))
234 So(bundles[0], shouldHaveBundleEntries, "a:1:2", "b:3:4" , "+c")
235 })
236 })
237
238 Convey(`A Bundler with a fake Sizer`, t, func() {
239 source := "test suite"
240 b := New(Config{
241 TemplateBundle: protocol.ButlerLogBundle{
242 Source: &source,
243 },
244 NewSizer: func(*protocol.ButlerLogBundle) Sizer {
245 return &fakeSizer{
246 Bundle: 8,
247 BundleEntry: 2,
248 LogEntry: 5,
249 }
250 },
251 })
252 So(b.Size(), ShouldEqual, 8)
253
254 Convey(`Adding an entry with 5 log messages outputs three bundle s.`, func() {
255 b.Append(gen("a", false, "1", "2", "3", "4", "5"))
256
257 bundles := b.GetBundles(20)
258 So(len(bundles), ShouldEqual, 3)
259
260 Convey(`All bundles use the template bundle's fields.`, func() {
261 So(bundles[0].GetSource(), ShouldEqual, source)
262 So(bundles[1].GetSource(), ShouldEqual, source)
263 So(bundles[2].GetSource(), ShouldEqual, source)
264 })
265
266 Convey(`Have the right entries: {1,2}, {3,4}, {5}.`, fun c() {
267 So(bundles[0], shouldHaveBundleEntries, "a:1:2")
268 So(bundles[1], shouldHaveBundleEntries, "a:3:4")
269 So(bundles[2], shouldHaveBundleEntries, "a:5")
270 })
271 })
272
273 Convey(`Adding two entries with 2 log messages each outputs firs t, then second.`, func() {
274 b.Append(gen("a", false, "1", "2"))
275 b.Append(gen("b", false, "3", "4"))
276
277 bundles := b.GetBundles(20)
278 So(len(bundles), ShouldEqual, 2)
279 So(bundles[0], shouldHaveBundleEntries, "a:1:2")
280 So(bundles[1], shouldHaveBundleEntries, "b:3:4")
281 })
282
283 Convey(`A non-terminal entry followed by a terminal version gets output as terminal.`, func() {
284 b.Append(gen("a", false, "1"))
285 b.Append(gen("a", true, "2"))
286
287 bundles := b.GetBundles(20)
288 So(len(bundles), ShouldEqual, 1)
289 So(bundles[0], shouldHaveBundleEntries, "+a:1:2")
290 })
291
292 Convey(`A terminal entry followed by a non-terminal version gets output as terminal.`, func() {
293 b.Append(gen("a", true, "1"))
294 b.Append(gen("a", false, "2"))
295
296 bundles := b.GetBundles(20)
297 So(len(bundles), ShouldEqual, 1)
298 So(bundles[0], shouldHaveBundleEntries, "+a:1:2")
299 })
300
301 Convey(`When the base bundle is above threshold, clear logs and returns nil.`, func() {
tandrii(chromium) 2015/08/11 17:32:22 real nit: s/clear/clears here and on line #311. :)
dnj 2015/08/11 18:14:28 Done.
302 b.Append(gen("a", true))
303
304 So(b.Size(), ShouldEqual, 10)
305 So(b.GetBundles(7), ShouldBeNil)
306
307 So(b.Size(), ShouldEqual, 8)
308 So(b.GetBundles(0), ShouldBeNil)
309 })
310
311 Convey(`When the bundle entry size is above threshold, clear log s and returns nil.`, func() {
312 b.Append(gen("a", true))
313
314 So(b.Size(), ShouldEqual, 10)
315 So(b.GetBundles(9), ShouldBeNil)
316
317 So(b.Size(), ShouldEqual, 8)
318 So(b.GetBundles(0), ShouldBeNil)
319 })
320 })
321 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698