go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/bq/eventupload_test.go (about) 1 // Copyright 2018 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package bq 16 17 import ( 18 "testing" 19 "time" 20 21 "cloud.google.com/go/bigquery" 22 "google.golang.org/protobuf/types/known/durationpb" 23 "google.golang.org/protobuf/types/known/emptypb" 24 "google.golang.org/protobuf/types/known/structpb" 25 "google.golang.org/protobuf/types/known/timestamppb" 26 27 "go.chromium.org/luci/common/bq/testdata" 28 "go.chromium.org/luci/common/clock/testclock" 29 30 . "github.com/smartystreets/goconvey/convey" 31 ) 32 33 func TestMetric(t *testing.T) { 34 t.Parallel() 35 u := Uploader{} 36 u.UploadsMetricName = "fakeCounter" 37 Convey("Test metric creation", t, func() { 38 Convey("Expect uploads metric was created", func() { 39 _ = u.getCounter() // To actually create the metric 40 So(u.uploads.Info().Name, ShouldEqual, "fakeCounter") 41 }) 42 }) 43 } 44 45 func TestSave(t *testing.T) { 46 Convey("filled", t, func() { 47 recentTime := testclock.TestRecentTimeUTC 48 ts := timestamppb.New(recentTime) 49 50 r := &Row{ 51 Message: &testdata.TestMessage{ 52 Name: "testname", 53 Timestamp: ts, 54 Nested: &testdata.NestedTestMessage{ 55 Name: "nestedname", 56 }, 57 RepeatedNested: []*testdata.NestedTestMessage{ 58 {Name: "repeated_one"}, 59 {Name: "repeated_two"}, 60 }, 61 Foo: testdata.TestMessage_Y, 62 FooRepeated: []testdata.TestMessage_FOO{ 63 testdata.TestMessage_Y, 64 testdata.TestMessage_X, 65 }, 66 67 Empty: &emptypb.Empty{}, 68 Empties: []*emptypb.Empty{{}, {}}, 69 Duration: durationpb.New(2*time.Second + 3*time.Millisecond), 70 OneOf: &testdata.TestMessage_First{First: &testdata.NestedTestMessage{ 71 Name: "first", 72 }}, 73 StringMap: map[string]string{ 74 "map_key_1": "map_value_1", 75 "map_key_2": "map_value_2", 76 }, 77 BqTypeOverride: 1234, 78 StringEnumMap: map[string]testdata.TestMessage_FOO{ 79 "e_map_key_1": testdata.TestMessage_Y, 80 "e_map_key_2": testdata.TestMessage_X, 81 }, 82 StringDurationMap: map[string]*durationpb.Duration{ 83 "d_map_key_1": durationpb.New(1*time.Second + 1*time.Millisecond), 84 "d_map_key_2": durationpb.New(2*time.Second + 2*time.Millisecond), 85 }, 86 StringTimestampMap: map[string]*timestamppb.Timestamp{ 87 "t_map_key": ts, 88 }, 89 StringProtoMap: map[string]*testdata.NestedTestMessage{ 90 "p_map_key": {Name: "nestedname"}, 91 }, 92 }, 93 InsertID: "testid", 94 } 95 row, id, err := r.Save() 96 So(err, ShouldBeNil) 97 So(id, ShouldEqual, "testid") 98 So(row, ShouldResemble, map[string]bigquery.Value{ 99 "name": "testname", 100 "timestamp": recentTime, 101 "nested": map[string]bigquery.Value{"name": "nestedname"}, 102 "repeated_nested": []any{ 103 map[string]bigquery.Value{"name": "repeated_one"}, 104 map[string]bigquery.Value{"name": "repeated_two"}, 105 }, 106 "foo": "Y", 107 "foo_repeated": []any{"Y", "X"}, 108 "duration": 2.003, 109 "first": map[string]bigquery.Value{"name": "first"}, 110 "string_map": []any{ 111 map[string]bigquery.Value{"key": "map_key_1", "value": "map_value_1"}, 112 map[string]bigquery.Value{"key": "map_key_2", "value": "map_value_2"}, 113 }, 114 "bq_type_override": int64(1234), 115 "string_enum_map": []any{ 116 map[string]bigquery.Value{"key": "e_map_key_1", "value": "Y"}, 117 map[string]bigquery.Value{"key": "e_map_key_2", "value": "X"}, 118 }, 119 "string_duration_map": []any{ 120 map[string]bigquery.Value{"key": "d_map_key_1", "value": 1.001}, 121 map[string]bigquery.Value{"key": "d_map_key_2", "value": 2.002}, 122 }, 123 "string_timestamp_map": []any{ 124 map[string]bigquery.Value{"key": "t_map_key", "value": recentTime}, 125 }, 126 "string_proto_map": []any{ 127 map[string]bigquery.Value{"key": "p_map_key", "value": map[string]bigquery.Value{"name": "nestedname"}}, 128 }, 129 }) 130 }) 131 132 Convey("known proto types", t, func() { 133 recentTime := testclock.TestRecentTimeUTC 134 ts := timestamppb.New(recentTime) 135 inputStruct := &structpb.Struct{ 136 Fields: map[string]*structpb.Value{ 137 "num": {Kind: &structpb.Value_NumberValue{NumberValue: 1}}, 138 "str": {Kind: &structpb.Value_StringValue{StringValue: "a"}}, 139 }, 140 } 141 142 r := &Row{ 143 Message: &testdata.TestMessage{ 144 Name: "testname", 145 Timestamp: ts, 146 Struct: inputStruct, 147 Duration: durationpb.New(2*time.Second + 3*time.Millisecond), 148 }, 149 InsertID: "testid", 150 } 151 row, id, err := r.Save() 152 So(err, ShouldBeNil) 153 So(id, ShouldEqual, "testid") 154 155 So(row, ShouldContainKey, "name") 156 So(row, ShouldContainKey, "timestamp") 157 So(row, ShouldContainKey, "struct") 158 So(row, ShouldContainKey, "duration") 159 160 So(row["name"], ShouldEqual, "testname") 161 So(row["timestamp"], ShouldEqual, recentTime) 162 So(row["duration"], ShouldEqual, 2.003) 163 164 outputStruct := &structpb.Struct{} 165 bytesString, ok := row["struct"].(string) 166 So(ok, ShouldBeTrue) 167 err = outputStruct.UnmarshalJSON([]byte(bytesString)) 168 So(err, ShouldBeNil) 169 So(outputStruct, ShouldResemble, inputStruct) 170 }) 171 172 Convey("empty", t, func() { 173 r := &Row{ 174 Message: &testdata.TestMessage{}, 175 InsertID: "testid", 176 } 177 row, id, err := r.Save() 178 So(err, ShouldBeNil) 179 So(id, ShouldEqual, "testid") 180 So(row, ShouldResemble, map[string]bigquery.Value{ 181 // only scalar proto fields 182 // because for them, proto3 does not distinguish empty and unset 183 // values. 184 "bq_type_override": int64(0), 185 "foo": "X", // enums are always set 186 "name": "", // in proto3, empty string and unset are indistinguishable 187 }) 188 }) 189 190 Convey("empty optional enum", t, func() { 191 r := &Row{ 192 Message: &testdata.TestOptionalEnumMessage{}, 193 InsertID: "testid", 194 } 195 row, id, err := r.Save() 196 So(err, ShouldBeNil) 197 So(id, ShouldEqual, "testid") 198 So(row, ShouldResemble, map[string]bigquery.Value{ 199 // only scalar proto fields 200 // because for them, proto3 does not distinguish empty and unset 201 // values. 202 "bar": "Q", // enums are always set 203 "name": "", // in proto3, empty string and unset are indistinguishable 204 }) 205 }) 206 207 Convey("repeated fields", t, func() { 208 Convey("empty message", func() { 209 r := &Row{ 210 Message: &testdata.TestRepeatedFieldMessage{}, 211 InsertID: "testid", 212 } 213 row, id, err := r.Save() 214 So(err, ShouldBeNil) 215 So(id, ShouldEqual, "testid") 216 So(row, ShouldResemble, map[string]bigquery.Value{ 217 // only scalar proto fields (ie. Name) 218 "name": "", 219 }) 220 }) 221 222 Convey("arrays of empty values", func() { 223 r := &Row{ 224 Message: &testdata.TestRepeatedFieldMessage{ 225 Name: "", 226 Strings: []string{"", ""}, 227 Bar: []testdata.BAR{}, 228 Nested: []*testdata.NestedTestMessage{{}, {}}, 229 Empties: []*emptypb.Empty{{}, {}}, 230 }, 231 InsertID: "testid", 232 } 233 row, id, err := r.Save() 234 So(err, ShouldBeNil) 235 So(id, ShouldEqual, "testid") 236 So(row, ShouldResemble, map[string]bigquery.Value{ 237 // only scalar proto fields (ie. strings) 238 "name": "", 239 "strings": []any{"", ""}, 240 "nested": []any{ 241 map[string]bigquery.Value{"name": ""}, 242 map[string]bigquery.Value{"name": ""}, 243 }, 244 }) 245 }) 246 247 Convey("arrays of filled values", func() { 248 r := &Row{ 249 Message: &testdata.TestRepeatedFieldMessage{ 250 Name: "Repeated Fields", 251 Strings: []string{"string1", "string2"}, 252 Bar: []testdata.BAR{ 253 testdata.BAR_Q, 254 testdata.BAR_R, 255 }, 256 Nested: []*testdata.NestedTestMessage{ 257 {Name: "nested1"}, 258 {Name: "nested2"}, 259 }, 260 Empties: []*emptypb.Empty{{}, {}}, 261 }, 262 InsertID: "testid", 263 } 264 row, id, err := r.Save() 265 So(err, ShouldBeNil) 266 So(id, ShouldEqual, "testid") 267 So(row, ShouldResemble, map[string]bigquery.Value{ 268 "name": "Repeated Fields", 269 "strings": []any{"string1", "string2"}, 270 "bar": []any{"Q", "R"}, 271 "nested": []any{ 272 map[string]bigquery.Value{"name": "nested1"}, 273 map[string]bigquery.Value{"name": "nested2"}, 274 }, 275 }) 276 }) 277 }) 278 } 279 280 func TestBatch(t *testing.T) { 281 t.Parallel() 282 283 Convey("Test batch", t, func() { 284 rowLimit := 2 285 rows := make([]*Row, 3) 286 for i := 0; i < 3; i++ { 287 rows[i] = &Row{} 288 } 289 290 want := [][]*Row{ 291 {{}, {}}, 292 {{}}, 293 } 294 rowSets := batch(rows, rowLimit) 295 So(rowSets, ShouldResemble, want) 296 }) 297 }