github.com/m3db/m3@v1.5.0/src/dbnode/encoding/proto/round_trip_prop_test.go (about) 1 // +build big 2 // 3 // Copyright (c) 2019 Uber Technologies, Inc. 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining a copy 6 // of this software and associated documentation files (the "Software"), to deal 7 // in the Software without restriction, including without limitation the rights 8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 // copies of the Software, and to permit persons to whom the Software is 10 // furnished to do so, subject to the following conditions: 11 // 12 // The above copyright notice and this permission notice shall be included in 13 // all copies or substantial portions of the Software. 14 // 15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 // THE SOFTWARE. 22 23 package proto 24 25 import ( 26 "bytes" 27 "fmt" 28 "os" 29 "reflect" 30 "runtime/debug" 31 "testing" 32 "time" 33 34 "github.com/m3db/m3/src/dbnode/namespace" 35 "github.com/m3db/m3/src/dbnode/ts" 36 "github.com/m3db/m3/src/dbnode/x/xio" 37 "github.com/m3db/m3/src/x/context" 38 xtime "github.com/m3db/m3/src/x/time" 39 40 dpb "github.com/golang/protobuf/protoc-gen-go/descriptor" 41 "github.com/jhump/protoreflect/desc" 42 "github.com/jhump/protoreflect/desc/builder" 43 "github.com/jhump/protoreflect/dynamic" 44 "github.com/leanovate/gopter" 45 "github.com/leanovate/gopter/gen" 46 "github.com/leanovate/gopter/prop" 47 "github.com/stretchr/testify/require" 48 ) 49 50 var ( 51 // Generated from mapProtoTypeToCustomFieldType by init(). 52 allowedProtoTypesSliceIface = []interface{}{} 53 54 validMapKeyTypes = []interface{}{ 55 // https://developers.google.com/protocol-buffers/docs/proto3#maps 56 dpb.FieldDescriptorProto_TYPE_INT32, 57 dpb.FieldDescriptorProto_TYPE_INT64, 58 dpb.FieldDescriptorProto_TYPE_UINT32, 59 dpb.FieldDescriptorProto_TYPE_UINT64, 60 dpb.FieldDescriptorProto_TYPE_SINT32, 61 dpb.FieldDescriptorProto_TYPE_SINT64, 62 dpb.FieldDescriptorProto_TYPE_FIXED32, 63 dpb.FieldDescriptorProto_TYPE_FIXED64, 64 dpb.FieldDescriptorProto_TYPE_SFIXED32, 65 dpb.FieldDescriptorProto_TYPE_SFIXED64, 66 dpb.FieldDescriptorProto_TYPE_BOOL, 67 dpb.FieldDescriptorProto_TYPE_STRING, 68 } 69 ) 70 71 func init() { 72 for key := range mapProtoTypeToCustomFieldType { 73 allowedProtoTypesSliceIface = append(allowedProtoTypesSliceIface, key) 74 } 75 } 76 77 const ( 78 maxNumFields = 10 79 maxNumMessages = 100 80 maxNumEnumValues = 10 81 82 debugLogs = false 83 ) 84 85 type fieldModifierProp int 86 87 const ( 88 fieldModifierRegular fieldModifierProp = iota 89 fieldModifierReserved 90 // Maps can't be repeated so its ok for these to be mutally exclusive. 91 fieldModifierRepeated 92 fieldModifierMap 93 ) 94 95 func TestRoundTripProp(t *testing.T) { 96 var ( 97 parameters = gopter.DefaultTestParameters() 98 seed = time.Now().UnixNano() 99 props = gopter.NewProperties(parameters) 100 reporter = gopter.NewFormatedReporter(true, 160, os.Stdout) 101 ) 102 parameters.MinSuccessfulTests = 300 103 parameters.Rng.Seed(seed) 104 105 enc := NewEncoder(0, testEncodingOptions) 106 iter := NewIterator(nil, nil, testEncodingOptions).(*iterator) 107 props.Property("Encoded data should be readable", prop.ForAll(func(input propTestInput) (bool, error) { 108 if debugLogs { 109 fmt.Println("---------------------------------------------------") 110 } 111 112 // Make panics debuggable. 113 defer func() { 114 if r := recover(); r != nil { 115 fmt.Printf( 116 "recovered with err: %v,schema: %s and messages: %#v", 117 r, 118 input.schema.String(), 119 input.messages) 120 debug.PrintStack() 121 panic("prop test encountered a panic!") 122 } 123 }() 124 125 times := make([]xtime.UnixNano, 0, len(input.messages)) 126 currTime := xtime.Now() 127 for i, m := range input.messages { 128 duration, err := m.timeUnit.Value() 129 if err != nil { 130 return false, fmt.Errorf("error getting duration from xtime.Unit: %v", err) 131 } 132 133 currTime = currTime.Add(time.Duration(i) * duration).Truncate(duration) 134 times = append(times, currTime) 135 } 136 137 schemaDescr := namespace.GetTestSchemaDescr(input.schema) 138 enc.Reset(currTime, 0, schemaDescr) 139 140 for i, m := range input.messages { 141 // The encoder will mutate the message so make sure we clone it first. 142 clone := dynamic.NewMessage(input.schema) 143 clone.MergeFrom(m.message) 144 cloneBytes, err := clone.Marshal() 145 if err != nil { 146 return false, fmt.Errorf("error marshalling proto message: %v", err) 147 } 148 149 if debugLogs { 150 printMessage(fmt.Sprintf("encoding %d", i), m.message) 151 } 152 err = enc.Encode(ts.Datapoint{TimestampNanos: times[i]}, xtime.Nanosecond, cloneBytes) 153 if err != nil { 154 return false, fmt.Errorf( 155 "error encoding message: %v, schema: %s", err, input.schema.String()) 156 } 157 158 // Ensure that the schema can be set inbetween writes without interfering with the stream. 159 // A new deployID is created each time to bypass the quick check in the SetSchema method 160 // and force the encoder to perform all of the state updates it has to do when a schema 161 // changes but without actually changing the schema (which would make asserting on the 162 // correct output difficult). 163 setSchemaDescr := namespace.GetTestSchemaDescrWithDeployID( 164 input.schema, fmt.Sprintf("deploy_id_%d", i)) 165 enc.SetSchema(setSchemaDescr) 166 } 167 168 // Ensure that the Len() method always returns the length of the final stream that would 169 // be returned by a call to Stream(). 170 encLen := enc.Len() 171 172 ctx := context.NewBackground() 173 defer ctx.Close() 174 175 stream, ok := enc.Stream(ctx) 176 if !ok { 177 if len(input.messages) == 0 { 178 return true, nil 179 } 180 return false, fmt.Errorf("encoder returned empty stream") 181 } 182 segment, err := stream.Segment() 183 if err != nil { 184 return false, fmt.Errorf("error getting segment: %v", err) 185 } 186 if segment.Len() != encLen { 187 return false, fmt.Errorf( 188 "expected segment len (%d) to match encoder len (%d)", 189 segment.Len(), encLen) 190 } 191 192 iter.Reset(stream, schemaDescr) 193 194 i := 0 195 for iter.Next() { 196 var ( 197 m = input.messages[i].message 198 dp, unit, annotation = iter.Current() 199 ) 200 decodedM := dynamic.NewMessage(input.schema) 201 require.NoError(t, decodedM.Unmarshal(annotation)) 202 if debugLogs { 203 printMessage(fmt.Sprintf("decoding %d", i), decodedM) 204 } 205 206 require.Equal(t, unit, xtime.Nanosecond) 207 require.True(t, 208 times[i].Equal(dp.TimestampNanos), 209 "%s does not match %s", times[i], dp.TimestampNanos) 210 211 if !dynamic.MessagesEqual(m, decodedM) { 212 for _, field := range m.GetKnownFields() { 213 var ( 214 fieldNum = int(field.GetNumber()) 215 expectedVal = m.GetFieldByNumber(fieldNum) 216 actualVal = decodedM.GetFieldByNumber(fieldNum) 217 ) 218 219 if !fieldsEqual(expectedVal, actualVal) { 220 return false, fmt.Errorf( 221 "expected %v but got %v on iteration number %d and fieldNum %d, schema %s", 222 expectedVal, actualVal, i, fieldNum, input.schema) 223 } 224 } 225 } 226 i++ 227 } 228 229 if iter.Err() != nil { 230 return false, fmt.Errorf( 231 "iteration error: %v, schema: %s", iter.Err(), input.schema.String()) 232 } 233 234 if i != len(input.messages) { 235 return false, fmt.Errorf("expected %d messages but got %d", len(input.messages), i) 236 } 237 238 return true, nil 239 }, genPropTestInputs())) 240 241 if !props.Run(reporter) { 242 t.Errorf("failed with initial seed: %d", seed) 243 } 244 } 245 246 // TestBijectivityProp ensures that the protobuf encoding format is bijective. I.E if the same messages are 247 // provided in the same order then the resulting stream should always be the same and similarly that decoding 248 // and re-encoding should always generate the same stream as the original. 249 func TestBijectivityProp(t *testing.T) { 250 var ( 251 parameters = gopter.DefaultTestParameters() 252 seed = time.Now().UnixNano() 253 props = gopter.NewProperties(parameters) 254 reporter = gopter.NewFormatedReporter(true, 160, os.Stdout) 255 ) 256 parameters.MinSuccessfulTests = 100 257 parameters.Rng.Seed(seed) 258 259 enc := NewEncoder(0, testEncodingOptions) 260 iter := NewIterator(nil, nil, testEncodingOptions).(*iterator) 261 props.Property("Encoded data should be readable", prop.ForAll(func(input propTestInput) (bool, error) { 262 if len(input.messages) == 0 { 263 return true, nil 264 } 265 if debugLogs { 266 fmt.Println("---------------------------------------------------") 267 } 268 269 var ( 270 start = xtime.Now() 271 schemaDescr = namespace.GetTestSchemaDescr(input.schema) 272 273 originalStream xio.SegmentReader 274 originalSegment ts.Segment 275 messageTimes []xtime.UnixNano 276 messageBytes [][]byte 277 ) 278 279 // Unfortunately the current implementation is only guaranteed to generate the exact stream 280 // for the same input Protobuf messages if the marshaled protobuf bytes are the same. The reason 281 // for that is that the Protobuf encoding format is not bijective (for example, fields do not have 282 // to be encoded in sorted order by field number and map values can appear in arbitrary orders). 283 // 284 // The protobuf encoder/iterator compensates for some of these limitations (like the lack of sorting 285 // on field order by performing the sort itself during "custom unmarshaling") but does not compensate 286 // for others (like the fact that maps are not sorted). Because of these limitations the current encoder 287 // may generate different streams (even when provided the same messages) if the messages are not first 288 // marshaled into the same exact byte streams. 289 for i, m := range input.messages { 290 if debugLogs { 291 printMessage(fmt.Sprintf("encoding %d", i), m.message) 292 } 293 clone := dynamic.NewMessage(input.schema) 294 clone.MergeFrom(m.message) 295 mBytes, err := clone.Marshal() 296 if err != nil { 297 return false, fmt.Errorf("error marshalling proto message: %v", err) 298 } 299 // Generate times up-front so they're the same for each iteration. 300 messageTimes = append(messageTimes, xtime.Now()) 301 messageBytes = append(messageBytes, mBytes) 302 } 303 304 ctx := context.NewBackground() 305 defer ctx.Close() 306 307 // First verify that if the same input byte slices are passed then the same stream will always 308 // be generated. 309 for i := 0; i < 10; i++ { 310 enc.Reset(start, 0, schemaDescr) 311 for j, mBytes := range messageBytes { 312 err := enc.Encode(ts.Datapoint{TimestampNanos: messageTimes[j]}, xtime.Nanosecond, mBytes) 313 if err != nil { 314 return false, fmt.Errorf( 315 "error encoding message: %v, schema: %s", err, input.schema.String()) 316 } 317 } 318 319 currStream, ok := enc.Stream(ctx) 320 if !ok { 321 return false, fmt.Errorf("encoder returned empty stream") 322 } 323 currSegment, err := currStream.Segment() 324 if err != nil { 325 return false, fmt.Errorf("error getting segment: %v", err) 326 } 327 328 if originalStream == nil { 329 originalStream = currStream 330 originalSegment = currSegment 331 } else { 332 if err := compareSegments(originalSegment, currSegment); err != nil { 333 return false, fmt.Errorf("error comparing segments for re-encoding original stream: %v", err) 334 } 335 } 336 } 337 338 // Next verify that re-encoding a stream (after decoding/iterating it) will also always generate the 339 // same original stream. 340 for i := 0; i < 10; i++ { 341 originalStream.Reset(originalSegment) 342 iter.Reset(originalStream, schemaDescr) 343 enc.Reset(start, 0, schemaDescr) 344 j := 0 345 for iter.Next() { 346 dp, unit, annotation := iter.Current() 347 if debugLogs { 348 fmt.Println("iterating", dp, unit, annotation) 349 } 350 if err := enc.Encode(dp, unit, annotation); err != nil { 351 return false, fmt.Errorf("error encoding current value") 352 } 353 j++ 354 } 355 if iter.Err() != nil { 356 return false, fmt.Errorf( 357 "iteration error: %v, schema: %s", iter.Err(), input.schema.String()) 358 } 359 360 if j != len(input.messages) { 361 return false, fmt.Errorf("expected %d messages but got %d", len(input.messages), j) 362 } 363 364 currStream, ok := enc.Stream(ctx) 365 if !ok { 366 return false, fmt.Errorf("encoder returned empty stream") 367 } 368 currSegment, err := currStream.Segment() 369 if err != nil { 370 return false, fmt.Errorf("error getting segment: %v", err) 371 } 372 373 if err := compareSegments(originalSegment, currSegment); err != nil { 374 return false, fmt.Errorf("error comparing segments for re-encoding original stream after iterating: %v", err) 375 } 376 } 377 378 return true, nil 379 }, genPropTestInputs())) 380 381 if !props.Run(reporter) { 382 t.Errorf("failed with initial seed: %d", seed) 383 } 384 } 385 386 type propTestInput struct { 387 schema *desc.MessageDescriptor 388 messages []messageAndTimeUnit 389 } 390 391 type messageAndTimeUnit struct { 392 message *dynamic.Message 393 timeUnit xtime.Unit 394 } 395 396 // generatedWrite contains numFields values for every type of ProtoBuf 397 // field. This allows us to generate data independent of the schema itself 398 // which we may not have seen in the input generation section yet. For example, 399 // if the maximum number of fields that a message can contain is 10, then this 400 // struct will generate a slice of size 10 for each of the scalar types and populate 401 // them with random values. 402 // 403 // If we receive a schema where the message has 5 boolean fields and then 5 string 404 // fields, then we will populate the first 5 booleans fields with generatedWrite.bools[:5] 405 // and the next 5 string fields with generatedWrite.strings[5:]. 406 type generatedWrite struct { 407 timeUnit xtime.Unit 408 409 // Whether we should use one of the randomly generated values in the slice below, 410 // or just the default value for the given type. 411 useDefaultValue []bool 412 413 bools []bool 414 enums []int32 415 strings []string 416 float32s []float32 417 float64s []float64 418 int8s []int8 419 int16s []int16 420 int32s []int32 421 int64s []int64 422 uint8s []uint8 423 uint16s []uint16 424 uint32s []uint32 425 uint64s []uint64 426 } 427 428 func genPropTestInputs() gopter.Gen { 429 curriedGenPropTestInput := func(input interface{}) gopter.Gen { 430 var ( 431 inputs = input.([]interface{}) 432 numFields = inputs[0].(int) 433 numMessages = inputs[1].(int) 434 ) 435 436 return genSchema(numFields).FlatMap( 437 func(input interface{}) gopter.Gen { 438 schema := input.(*desc.MessageDescriptor) 439 return genPropTestInput(schema, numMessages) 440 }, reflect.TypeOf(propTestInput{})) 441 } 442 443 return gopter.CombineGens( 444 gen.IntRange(0, maxNumFields), 445 gen.IntRange(0, maxNumMessages), 446 ).FlatMap(curriedGenPropTestInput, reflect.TypeOf(propTestInput{})) 447 } 448 449 func genPropTestInput(schema *desc.MessageDescriptor, numMessages int) gopter.Gen { 450 return gopter.CombineGens( 451 // Messages to write for the given schema. 452 gen.SliceOfN(numMessages, genMessage(schema)), 453 // [][]bool that indicates on a field-by-field basis for each message whether the 454 // value should be the same as the previous message for that field to ensure we 455 // aggressively exercise that codepath. 456 gen.SliceOfN(numMessages, gen.SliceOfN(len(schema.GetFields()), gen.Bool())), 457 ).Map(func(input []interface{}) propTestInput { 458 459 messages := input[0].([]messageAndTimeUnit) 460 perMessageShouldBeSameAsPrevWrite := input[1].([][]bool) 461 for i, messageAndUnit := range messages { 462 m := messageAndUnit.message 463 if i == 0 { 464 // Can't make the same as previous if there is no previous. 465 continue 466 } 467 468 perFieldShouldBeSameAsPrevWrite := perMessageShouldBeSameAsPrevWrite[i] 469 fields := m.GetKnownFields() 470 for j, field := range fields { 471 if perFieldShouldBeSameAsPrevWrite[j] { 472 fieldNumInt := int(field.GetNumber()) 473 prevFieldVal := messages[i-1].message.GetFieldByNumber(fieldNumInt) 474 m.SetFieldByNumber(fieldNumInt, prevFieldVal) 475 } 476 } 477 } 478 479 return propTestInput{ 480 schema: schema, 481 messages: messages, 482 } 483 }) 484 } 485 486 func genMessage(schema *desc.MessageDescriptor) gopter.Gen { 487 return genWrite().Map(func(input generatedWrite) messageAndTimeUnit { 488 return newMessageWithValues(schema, input) 489 }) 490 } 491 492 func newMessageWithValues(schema *desc.MessageDescriptor, input generatedWrite) messageAndTimeUnit { 493 message := dynamic.NewMessage(schema) 494 for i, field := range message.GetKnownFields() { 495 fieldNumber := int(field.GetNumber()) 496 switch { 497 case input.useDefaultValue[i]: 498 // Due to the way ProtoBuf encoding works where there is no way to 499 // distinguish between an "unset" field and a field set to its default 500 // value, we intentionally force some of the values to their default values 501 // to exercise those code paths. This is important because the probability of 502 // randomly generating a uint64 with the default value of zero is so unlikely 503 // that it will basically never happen. 504 continue 505 case field.IsMap(): 506 // Maps require special handling because the type will look like a message, they'll 507 // have the repeated label, and to construct them properly we need to look at both 508 // the key type as well as the value type. 509 var ( 510 mapKeyType = field.GetMapKeyType() 511 mapValueType = field.GetMapValueType() 512 513 mapKeysForTypeIFace, _ = valuesOfType(nil, i, mapKeyType, input) 514 mapValuesForTypeIFace, _ = valuesOfType(nil, i, mapValueType, input) 515 mapKeysForType = interfaceSlice(mapKeysForTypeIFace) 516 mapValuesForType = interfaceSlice(mapValuesForTypeIFace) 517 ) 518 for j, key := range mapKeysForType { 519 if messageAndTU, ok := mapValuesForType[j].(messageAndTimeUnit); ok { 520 message.PutMapFieldByNumber(fieldNumber, key, messageAndTU.message) 521 } else { 522 message.PutMapFieldByNumber(fieldNumber, key, mapValuesForType[j]) 523 } 524 } 525 default: 526 sliceValues, singleValue := valuesOfType(schema, i, field, input) 527 if field.IsRepeated() { 528 valuesToSet := interfaceSlice(sliceValues) 529 if _, ok := singleValue.(messageAndTimeUnit); ok { 530 // If its a slice of messageAndTimeUnit (indiciating a field with repeated 531 // nested messages) then we need to convert it to a slice of *dynamic.Message. 532 valuesToSet = make([]interface{}, 0, len(valuesToSet)) 533 for _, v := range interfaceSlice(sliceValues) { 534 valuesToSet = append(valuesToSet, v.(messageAndTimeUnit).message) 535 } 536 } 537 538 message.SetFieldByNumber(fieldNumber, valuesToSet) 539 } else { 540 if messageAndTU, ok := singleValue.(messageAndTimeUnit); ok { 541 message.SetFieldByNumber(fieldNumber, messageAndTU.message) 542 } else { 543 message.SetFieldByNumber(fieldNumber, singleValue) 544 } 545 } 546 } 547 } 548 549 // Basic sanity test to protect against bugs in the underlying library: 550 // https://github.com/jhump/protoreflect/issues/181 551 marshalled, err := message.Marshal() 552 if err != nil { 553 panic(err) 554 } 555 unmarshalled := dynamic.NewMessage(schema) 556 err = unmarshalled.Unmarshal(marshalled) 557 if err != nil { 558 panic(err) 559 } 560 if !dynamic.MessagesEqual(message, unmarshalled) { 561 panic("generated message that is not equal after being marshalled and unmarshalled") 562 } 563 564 return messageAndTimeUnit{ 565 message: message, 566 timeUnit: input.timeUnit, 567 } 568 } 569 570 func valuesOfType( 571 schema *desc.MessageDescriptor, 572 i int, 573 field *desc.FieldDescriptor, 574 input generatedWrite) (interface{}, interface{}) { 575 fieldType := field.GetType() 576 577 switch fieldType { 578 case dpb.FieldDescriptorProto_TYPE_BOOL: 579 return input.bools[0 : i+1], input.bools[i] 580 case dpb.FieldDescriptorProto_TYPE_ENUM: 581 return input.enums[0 : i+1], input.enums[i] 582 case dpb.FieldDescriptorProto_TYPE_BYTES: 583 bytesSlice := make([][]byte, 0, i) 584 for _, s := range input.strings[0 : i+1] { 585 bytesSlice = append(bytesSlice, []byte(s)) 586 } 587 return bytesSlice, []byte(input.strings[i]) 588 case dpb.FieldDescriptorProto_TYPE_STRING: 589 return input.strings[0 : i+1], input.strings[i] 590 case dpb.FieldDescriptorProto_TYPE_DOUBLE: 591 return input.float64s[0 : i+1], input.float64s[i] 592 case dpb.FieldDescriptorProto_TYPE_FLOAT: 593 return input.float32s[0 : i+1], input.float32s[i] 594 case dpb.FieldDescriptorProto_TYPE_SFIXED32: 595 fallthrough 596 case dpb.FieldDescriptorProto_TYPE_SINT32: 597 fallthrough 598 case dpb.FieldDescriptorProto_TYPE_INT32: 599 return input.int32s[0 : i+1], input.int32s[i] 600 case dpb.FieldDescriptorProto_TYPE_SFIXED64: 601 fallthrough 602 case dpb.FieldDescriptorProto_TYPE_SINT64: 603 fallthrough 604 case dpb.FieldDescriptorProto_TYPE_INT64: 605 return input.int64s[0 : i+1], input.int64s[i] 606 case dpb.FieldDescriptorProto_TYPE_FIXED32: 607 fallthrough 608 case dpb.FieldDescriptorProto_TYPE_UINT32: 609 return input.uint32s[0 : i+1], input.uint32s[i] 610 case dpb.FieldDescriptorProto_TYPE_FIXED64: 611 fallthrough 612 case dpb.FieldDescriptorProto_TYPE_UINT64: 613 return input.uint64s[0 : i+1], input.uint64s[i] 614 case dpb.FieldDescriptorProto_TYPE_MESSAGE: 615 // If the field is a nested message, figure out what the nested schema is 616 // and then generate another message to use as the value of that field. We 617 // reuse the same inputs value for simplicity. 618 // 619 // If the schema is set to nil, that means that field itself is a message type 620 // that we'd like to generate a value for so get the nested message schema 621 // from the field itself instead of looking it up. 622 var nestedMessageSchema *desc.MessageDescriptor 623 if schema != nil { 624 nestedMessageSchema = schema.FindFieldByNumber(field.GetNumber()).GetMessageType() 625 } else { 626 nestedMessageSchema = field.GetMessageType() 627 } 628 nestedMessages := make([]messageAndTimeUnit, 0, i) 629 for j := 0; j <= i; j++ { 630 nestedMessages = append(nestedMessages, newMessageWithValues(nestedMessageSchema, input)) 631 } 632 return nestedMessages, newMessageWithValues(nestedMessageSchema, input) 633 default: 634 panic(fmt.Sprintf("invalid field type in schema: %v", fieldType)) 635 } 636 } 637 638 func genWrite() gopter.Gen { 639 return gopter.CombineGens( 640 genTimeUnit(), 641 gen.SliceOfN(maxNumFields, gen.Bool()), 642 gen.SliceOfN(maxNumFields, gen.Bool()), 643 gen.SliceOfN(maxNumFields, gen.Int32Range(0, int32(maxNumEnumValues)-1)), 644 gen.SliceOfN(maxNumFields, gen.Identifier()), 645 gen.SliceOfN(maxNumFields, gen.Float32()), 646 gen.SliceOfN(maxNumFields, gen.Float64()), 647 gen.SliceOfN(maxNumFields, gen.Int8()), 648 gen.SliceOfN(maxNumFields, gen.Int16()), 649 gen.SliceOfN(maxNumFields, gen.Int32()), 650 gen.SliceOfN(maxNumFields, gen.Int64()), 651 gen.SliceOfN(maxNumFields, gen.UInt8()), 652 gen.SliceOfN(maxNumFields, gen.UInt16()), 653 gen.SliceOfN(maxNumFields, gen.UInt32()), 654 gen.SliceOfN(maxNumFields, gen.UInt64()), 655 ).Map(func(input []interface{}) generatedWrite { 656 return generatedWrite{ 657 timeUnit: input[0].(xtime.Unit), 658 useDefaultValue: input[1].([]bool), 659 bools: input[2].([]bool), 660 enums: input[3].([]int32), 661 strings: input[4].([]string), 662 float32s: input[5].([]float32), 663 float64s: input[6].([]float64), 664 int8s: input[7].([]int8), 665 int16s: input[8].([]int16), 666 int32s: input[9].([]int32), 667 int64s: input[10].([]int64), 668 uint8s: input[11].([]uint8), 669 uint16s: input[12].([]uint16), 670 uint32s: input[13].([]uint32), 671 uint64s: input[14].([]uint64), 672 } 673 }) 674 } 675 676 func genSchema(numFields int) gopter.Gen { 677 return gopter.CombineGens( 678 gen.SliceOfN(numFields, genFieldModifier()), 679 gen.SliceOfN(numFields, genMapKeyType()), 680 gen.SliceOfN(numFields, genFieldTypeWithNestedMessage()), 681 gen.SliceOfN(numFields, genFieldTypeWithNoNestedMessage()), 682 ). 683 Map(func(input []interface{}) *desc.MessageDescriptor { 684 var ( 685 fieldModifiers = input[0].([]fieldModifierProp) 686 mapKeyTypes = input[1].([]dpb.FieldDescriptorProto_Type) 687 // fieldTypes are generated with the possibility of a field being a nested message 688 // where nestedFieldTypes are generated without the possibility of a field being 689 // a nested message to prevent infinite recursion. This limits the property testing 690 // of nested message types to a maximum depth of 1, but in practice it doesn't matter 691 // much because nested messages are handled by the ProtoBuf specification not our 692 // custom encoding. 693 fieldTypes = input[2].([]dpb.FieldDescriptorProto_Type) 694 nestedFieldTypes = input[3].([]dpb.FieldDescriptorProto_Type) 695 ) 696 697 schemaBuilder := schemaBuilderFromFieldTypes(fieldModifiers, mapKeyTypes, fieldTypes, nestedFieldTypes) 698 schema, err := schemaBuilder.Build() 699 if err != nil { 700 panic(err) 701 } 702 703 return schema 704 }) 705 } 706 707 func schemaBuilderFromFieldTypes( 708 fieldModifiers []fieldModifierProp, 709 mapKeyTypes []dpb.FieldDescriptorProto_Type, 710 fieldTypes []dpb.FieldDescriptorProto_Type, 711 nestedMessageFieldTypes []dpb.FieldDescriptorProto_Type, 712 ) *builder.MessageBuilder { 713 var schemaName string 714 if nestedMessageFieldTypes != nil { 715 schemaName = "schema" 716 } else { 717 schemaName = "nested_schema" 718 } 719 schemaBuilder := builder.NewMessage(schemaName) 720 721 for i, fieldType := range fieldTypes { 722 var ( 723 fieldModifier = fieldModifiers[i] 724 fieldNum = int32(i + 1) // Zero not valid. 725 fieldBuilder *builder.FieldBuilder 726 ) 727 728 switch { 729 case fieldModifier == fieldModifierReserved: 730 // Sprinkle in some reserved fields to make sure that we handle those 731 // without issue. 732 schemaBuilder.AddReservedRange(fieldNum, fieldNum) 733 continue 734 case fieldModifier == fieldModifierMap: 735 // Map key types can only be scalar types. 736 mapKeyType := builder.FieldTypeScalar(mapKeyTypes[i]) 737 mapValueType := newBuilderFieldType( 738 // All map values should be "regular" I.E not repeated, reserved, or another map. 739 fieldNum, fieldType, make([]fieldModifierProp, len(nestedMessageFieldTypes)), mapKeyTypes, nestedMessageFieldTypes) 740 mapFieldName := fmt.Sprintf("_map_%d", fieldNum) 741 fieldBuilder = builder.NewMapField(mapFieldName, mapKeyType, mapValueType).SetNumber(fieldNum) 742 default: 743 builderFieldType := newBuilderFieldType(fieldNum, fieldType, fieldModifiers, mapKeyTypes, nestedMessageFieldTypes) 744 fieldBuilder = builder.NewField(fmt.Sprintf("_%d", fieldNum), builderFieldType). 745 SetNumber(fieldNum) 746 } 747 748 if fieldModifier == fieldModifierRepeated { 749 // This is safe because a field cant be repeated and a map by design. 750 fieldBuilder = fieldBuilder.SetRepeated() 751 } 752 753 schemaBuilder = schemaBuilder.AddField(fieldBuilder) 754 } 755 756 return schemaBuilder 757 } 758 759 func newBuilderFieldType( 760 fieldNum int32, 761 fieldType dpb.FieldDescriptorProto_Type, 762 fieldModifiers []fieldModifierProp, 763 mapKeyTypes []dpb.FieldDescriptorProto_Type, 764 nestedMessageFieldTypes []dpb.FieldDescriptorProto_Type, 765 ) *builder.FieldType { 766 if fieldType == dpb.FieldDescriptorProto_TYPE_ENUM { 767 var ( 768 enumFieldName = fmt.Sprintf("_enum_%d", fieldNum) 769 enumBuilder = builder.NewEnum(enumFieldName) 770 ) 771 for j := 0; j < maxNumEnumValues; j++ { 772 enumValueName := fmt.Sprintf("_enum_value_%d", j) 773 enumBuilder.AddValue(builder.NewEnumValue(enumValueName)) 774 } 775 return builder.FieldTypeEnum(enumBuilder) 776 } 777 778 if fieldType == dpb.FieldDescriptorProto_TYPE_MESSAGE { 779 // NestedMessageFieldTypes can't contain nested messages so we're limited to a single level 780 // of recursion here. 781 nestedMessageBuilder := schemaBuilderFromFieldTypes(fieldModifiers, mapKeyTypes, nestedMessageFieldTypes, nil) 782 return builder.FieldTypeMessage(nestedMessageBuilder) 783 } 784 785 return builder.FieldTypeScalar(fieldType) 786 } 787 788 func genTimeUnit() gopter.Gen { 789 return gen.OneConstOf( 790 xtime.Second, xtime.Millisecond, xtime.Microsecond, xtime.Nanosecond) 791 } 792 793 func genFieldModifier() gopter.Gen { 794 return gen.OneConstOf( 795 fieldModifierRegular, 796 fieldModifierReserved, 797 fieldModifierRepeated, 798 fieldModifierMap) 799 } 800 801 func genMapKeyType() gopter.Gen { 802 return gen.OneConstOf(validMapKeyTypes...) 803 } 804 805 func genFieldTypeWithNoNestedMessage() gopter.Gen { 806 return gen.OneConstOf(allowedProtoTypesSliceIface...) 807 } 808 809 func genFieldTypeWithNestedMessage() gopter.Gen { 810 allowedProtoTypesSliceIfaceWithNestedMessage := allowedProtoTypesSliceIface[:] // Shallow copy. 811 allowedProtoTypesSliceIfaceWithNestedMessage = append( 812 allowedProtoTypesSliceIfaceWithNestedMessage, 813 dpb.FieldDescriptorProto_TYPE_MESSAGE) 814 return gen.OneConstOf(allowedProtoTypesSliceIfaceWithNestedMessage...) 815 } 816 817 func interfaceSlice(slice interface{}) []interface{} { 818 // https://stackoverflow.com/questions/12753805/type-converting-slices-of-interfaces-in-go 819 s := reflect.ValueOf(slice) 820 if s.Kind() != reflect.Slice { 821 panic("InterfaceSlice() given a non-slice type") 822 } 823 824 ret := make([]interface{}, s.Len()) 825 826 for i := 0; i < s.Len(); i++ { 827 ret[i] = s.Index(i).Interface() 828 } 829 830 return ret 831 } 832 833 func printMessage(prefix string, m *dynamic.Message) { 834 json, err := m.MarshalJSON() 835 if err != nil { 836 panic(err) 837 } 838 fmt.Printf("%s: %s\n", prefix, string(json)) 839 } 840 841 func compareSegments(a ts.Segment, b ts.Segment) error { 842 var ( 843 aHead []byte 844 bHead []byte 845 aTail []byte 846 bTail []byte 847 ) 848 if a.Head != nil { 849 aHead = a.Head.Bytes() 850 } 851 if b.Head != nil { 852 bHead = b.Head.Bytes() 853 } 854 if a.Tail != nil { 855 aTail = a.Tail.Bytes() 856 } 857 if b.Tail != nil { 858 bTail = b.Tail.Bytes() 859 } 860 if !bytes.Equal(aHead, bHead) { 861 return fmt.Errorf( 862 "heads do not match. Expected: %v but got: %v", 863 aHead, bHead) 864 } 865 if !bytes.Equal(aTail, bTail) { 866 return fmt.Errorf( 867 "tails do not match. Expected: %v but got: %v", 868 aTail, bTail) 869 } 870 return nil 871 }