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  }