| 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 streamserver |
| 6 |
| 7 import ( |
| 8 "bytes" |
| 9 "encoding/binary" |
| 10 "fmt" |
| 11 "io" |
| 12 "testing" |
| 13 "time" |
| 14 |
| 15 "github.com/luci/luci-go/client/logdog/butlerlib/streamproto" |
| 16 "github.com/luci/luci-go/common/clock/testclock" |
| 17 "github.com/luci/luci-go/common/logdog/protocol" |
| 18 "github.com/luci/luci-go/common/logdog/types" |
| 19 "github.com/luci/luci-go/common/proto/google" |
| 20 ta "github.com/luci/luci-go/common/testing/assertions" |
| 21 . "github.com/smartystreets/goconvey/convey" |
| 22 "golang.org/x/net/context" |
| 23 ) |
| 24 |
| 25 func writePanic(w io.Writer, d []byte) { |
| 26 amt, err := w.Write(d) |
| 27 if err != nil { |
| 28 panic(err) |
| 29 } |
| 30 if amt != len(d) { |
| 31 panic("failed to write full buffer") |
| 32 } |
| 33 } |
| 34 |
| 35 type handshakeBuilder struct { |
| 36 magic []byte // The frame header. If empty, don't include a frame header
. |
| 37 size uint64 // The size. If zero, calculate the size. |
| 38 } |
| 39 |
| 40 func (b *handshakeBuilder) writeTo(w io.Writer, handshake string, data []byte) { |
| 41 // Frame header |
| 42 if len(b.magic) > 0 { |
| 43 writePanic(w, b.magic) |
| 44 } |
| 45 |
| 46 // Size |
| 47 size := b.size |
| 48 if size == 0 { |
| 49 size = uint64(len(handshake)) |
| 50 } |
| 51 sizeBuf := make([]byte, binary.MaxVarintLen64) |
| 52 count := binary.PutUvarint(sizeBuf, uint64(size)) |
| 53 writePanic(w, sizeBuf[:count]) |
| 54 |
| 55 if handshake != "" { |
| 56 writePanic(w, []byte(handshake)) |
| 57 } |
| 58 writePanic(w, data) |
| 59 } |
| 60 |
| 61 // Generate a reader from the configuration. |
| 62 func (b *handshakeBuilder) reader(handshake string, data []byte) io.Reader { |
| 63 r := bytes.Buffer{} |
| 64 b.writeTo(&r, handshake, data) |
| 65 return &r |
| 66 } |
| 67 |
| 68 // Tests for handshakeProtocol |
| 69 func testHandshakeProtocol(t *testing.T, verbose bool) { |
| 70 Convey(fmt.Sprintf(`A handshakeProtocol instance (verbose=%v)`, verbose)
, t, func() { |
| 71 hb := handshakeBuilder{ |
| 72 magic: streamproto.ProtocolFrameHeaderMagic, |
| 73 } |
| 74 |
| 75 ctx, tc := testclock.UseTime(context.Background(), testclock.Tes
tTimeUTC) |
| 76 p := &handshakeProtocol{} |
| 77 p.forceVerbose = verbose |
| 78 |
| 79 Convey(`Will fail if no handshake data is provided.`, func() { |
| 80 hb.magic = []byte{} |
| 81 |
| 82 _, err := p.Handshake(ctx, hb.reader("{}", nil)) |
| 83 So(err, ShouldNotBeNil) |
| 84 }) |
| 85 |
| 86 Convey(`Will fail with an invalid handshake protocol.`, func() { |
| 87 hb.magic = []byte{0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA} |
| 88 |
| 89 _, err := p.Handshake(ctx, hb.reader("{}", nil)) |
| 90 So(err, ShouldNotBeNil) |
| 91 }) |
| 92 |
| 93 Convey(`Loading a handshake frame starting with an invalid size
varint value must fail.`, func() { |
| 94 // This exceeds the maximum 64-bit varint size (10 bytes
) and never |
| 95 // terminates (no MSB). |
| 96 _, err := p.Handshake(ctx, bytes.NewReader([]byte{ |
| 97 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, |
| 98 })) |
| 99 So(err, ShouldNotBeNil) |
| 100 }) |
| 101 |
| 102 Convey(`Loading a handshake frame larger than the maximum header
size must fail.`, func() { |
| 103 hb.size = maxHeaderSize + 1 |
| 104 _, err := p.Handshake(ctx, hb.reader("", nil)) |
| 105 So(err, ShouldNotBeNil) |
| 106 }) |
| 107 |
| 108 Convey(`Loading an JSON object with just a name`, func() { |
| 109 props, err := p.Handshake(ctx, hb.reader(`{"name": "test
"}`, nil)) |
| 110 So(err, ShouldBeNil) |
| 111 |
| 112 Convey(`Should produce a valid stream configuration.`, f
unc() { |
| 113 So(props, ta.ShouldResembleV, &streamproto.Prope
rties{ |
| 114 LogStreamDescriptor: protocol.LogStreamD
escriptor{ |
| 115 Name: "test", |
| 116 Timestamp: google.NewTimestamp
(tc.Now()), |
| 117 StreamType: protocol.LogStreamD
escriptor_TEXT, |
| 118 ContentType: string(types.Conten
tTypeText), |
| 119 }, |
| 120 }) |
| 121 }) |
| 122 }) |
| 123 |
| 124 Convey(`Loading a fully-specified configuration`, func() { |
| 125 data := `{ |
| 126 "name": "test", "tee": "stdout", "timestamp": "2
015-05-07T01:29:51+00:00", |
| 127 "contentType": "text/plain", |
| 128 "tags": {"foo": "bar", "baz": "qux"} |
| 129 }` |
| 130 props, err := p.Handshake(ctx, hb.reader(data, nil)) |
| 131 So(err, ShouldBeNil) |
| 132 |
| 133 Convey(`Should produce a specific configuration.`, func(
) { |
| 134 So(props, ta.ShouldResembleV, &streamproto.Prope
rties{ |
| 135 LogStreamDescriptor: protocol.LogStreamD
escriptor{ |
| 136 Name: "test", |
| 137 ContentType: "text/plain", |
| 138 Timestamp: google.NewTimestamp
(time.Date(2015, 05, 07, 1, 29, 51, 0, time.UTC)), |
| 139 Tags: []*protocol.LogStreamDescr
iptor_Tag{ |
| 140 {Key: "baz", Value: "qux
"}, |
| 141 {Key: "foo", Value: "bar
"}, |
| 142 }, |
| 143 }, |
| 144 Tee: streamproto.TeeStdout, |
| 145 }) |
| 146 }) |
| 147 }) |
| 148 |
| 149 Convey(`Loading a (valid) JSON array should fail to load.`, func
() { |
| 150 data := `["This is an array!"]` |
| 151 _, err := p.Handshake(ctx, hb.reader(data, nil)) |
| 152 So(err, ShouldNotBeNil) |
| 153 }) |
| 154 |
| 155 Convey(`Loading a JSON descriptor with just a name should succee
d.`, func() { |
| 156 data := `{"name": "test"}` |
| 157 props, err := p.Handshake(ctx, hb.reader(data, nil)) |
| 158 So(err, ShouldBeNil) |
| 159 So(props, ShouldNotBeNil) |
| 160 }) |
| 161 |
| 162 Convey(`Loading an empty JSON object with a larger-than-necessar
y header size should fail.`, func() { |
| 163 data := `{}` |
| 164 hb.size = uint64(len(data) + 10) |
| 165 _, err := p.Handshake(ctx, hb.reader(data, nil)) |
| 166 So(err, ShouldNotBeNil) |
| 167 }) |
| 168 |
| 169 Convey(`Loading an JSON with an erroneous config should fail.`,
func() { |
| 170 data := `{"timestamp": "text-for-some-reason"}` |
| 171 _, err := p.Handshake(ctx, hb.reader(data, nil)) |
| 172 So(err, ShouldNotBeNil) |
| 173 }) |
| 174 |
| 175 Convey(`Loading an invalid JSON descriptor should fail.`, func()
{ |
| 176 data := `invalid` |
| 177 _, err := p.Handshake(ctx, hb.reader(data, nil)) |
| 178 So(err, ShouldNotBeNil) |
| 179 }) |
| 180 |
| 181 for idx, v := range []string{"none", "stdout", "stderr", "clearl
y invalid"} { |
| 182 Convey(fmt.Sprintf(`A protocol with a tee type of: %s`,
v), func() { |
| 183 ctx := context.Background() |
| 184 data := fmt.Sprintf(`{"name": "test", "tee": "%s
"}`, v) |
| 185 _, err := p.Handshake(ctx, hb.reader(data, nil)) |
| 186 if idx <= 2 { |
| 187 Convey(`Should successfully parse.`, fun
c() { |
| 188 So(err, ShouldBeNil) |
| 189 }) |
| 190 } else { |
| 191 Convey(`Should fail to parse.`, func() { |
| 192 So(err, ShouldNotBeNil) |
| 193 }) |
| 194 } |
| 195 }) |
| 196 } |
| 197 }) |
| 198 } |
| 199 |
| 200 func TestHandshakeProtocol(t *testing.T) { |
| 201 testHandshakeProtocol(t, false) |
| 202 } |
| 203 |
| 204 // As an optimization, we buffer data differently for verbose output. This |
| 205 // creates a separate code path that we have to take if logging verbose function |
| 206 // is set. |
| 207 func TestHandshakeProtocolVerbose(t *testing.T) { |
| 208 testHandshakeProtocol(t, true) |
| 209 } |
| OLD | NEW |