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  }