Chromium Code Reviews| 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 "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 } | |
| OLD | NEW |