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

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

Issue 1412063008: logdog: Add bundler library. (Closed) Base URL: https://github.com/luci/luci-go@logdog-review-streamserver
Patch Set: Updated from comments. Created 5 years, 1 month 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 "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 }
OLDNEW
« no previous file with comments | « client/internal/logdog/butler/bundler/builder.go ('k') | client/internal/logdog/butler/bundler/bundler.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698