package proto import ( "reflect" "testing" ) func TestRewrite(t *testing.T) { type message struct { A int B float32 C float64 D string M *message } tests := []struct { scenario string in message out message rw Rewriter }{ { scenario: "identity", in: message{A: 42, M: &message{A: 1}}, out: message{A: 42, M: &message{A: 1}}, rw: MessageRewriter(nil), }, { scenario: "rewrite field 1", in: message{A: 21}, out: message{A: 42}, rw: MessageRewriter{ 1: FieldNumber(1).Int(42), }, }, { scenario: "rewrite field 2", in: message{A: 21, B: 0.125}, out: message{A: 21, B: -1}, rw: MessageRewriter{ 2: FieldNumber(2).Float32(-1), }, }, { scenario: "rewrite field 3", in: message{A: 21, B: 0.125, C: 0.0}, out: message{A: 21, B: 0.125, C: 1.0}, rw: MessageRewriter{ 3: FieldNumber(3).Float64(+1), }, }, { scenario: "rewrite field 4", in: message{A: 21, B: 0.125, C: 1.0, D: "A"}, out: message{A: 21, B: 0.125, C: 1.0, D: "Hello World!"}, rw: MessageRewriter{ 4: FieldNumber(4).String("Hello World!"), }, }, } for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { b, err := Marshal(test.in) if err != nil { t.Fatal(err) } b, err = test.rw.Rewrite(nil, b) if err != nil { t.Fatal(err) } m := message{} if err := Unmarshal(b, &m); err != nil { t.Fatal(err) } if !reflect.DeepEqual(m, test.out) { t.Errorf("messages mismatch:\nwant: %+v\ngot: %+v", test.out, m) } }) } } func TestParseRewriteTemplate(t *testing.T) { type submessage struct { Question string `protobuf:"bytes,1,opt,name=question,proto3"` Answer string `protobuf:"bytes,2,opt,name=answer,proto3"` } type message struct { Field1 bool `protobuf:"varint,1,opt,name=field_1,proto3"` Field2 int `protobuf:"varint,2,opt,name=field_2,proto3"` Field3 int32 `protobuf:"varint,3,opt,name=field_3,proto3"` Field4 int64 `protobuf:"varint,4,opt,name=field_4,proto3"` Field5 uint `protobuf:"varint,5,opt,name=field_5,proto3"` Field6 uint32 `protobuf:"varint,6,opt,name=field_6,proto3"` Field7 uint64 `protobuf:"varint,7,opt,name=field_7,proto3"` Field8 int32 `protobuf:"zigzag32,8,opt,name=field_8,proto3"` Field9 int64 `protobuf:"zigzag64,9,opt,name=field_9,proto3"` Field10 float32 `protobuf:"fixed32,10,opt,name=field_10,proto3"` Field11 float64 `protobuf:"fixed64,11,opt,name=field_11,proto3"` Field12 string `protobuf:"bytes,12,opt,name=field_12,proto3"` Field13 []byte `protobuf:"bytes,13,opt,name=field_13,proto3"` Zero1 bool `protobuf:"varint,21,opt,name=zero_1,proto3"` Zero2 int `protobuf:"varint,22,opt,name=zero_2,proto3"` Zero3 int32 `protobuf:"varint,23,opt,name=zero_3,proto3"` Zero4 int64 `protobuf:"varint,24,opt,name=zero_4,proto3"` Zero5 uint `protobuf:"varint,25,opt,name=zero_5,proto3"` Zero6 uint32 `protobuf:"varint,26,opt,name=zero_6,proto3"` Zero7 uint64 `protobuf:"varint,27,opt,name=zero_7,proto3"` Zero8 float32 `protobuf:"fixed32,28,opt,name=zero_8,proto3"` Zero9 float64 `protobuf:"fixed64,29,opt,name=zero_9,proto3"` Subfield *submessage `protobuf:"bytes,99,opt,name=subfield,proto3"` Submessages []submessage `protobuf:"bytes,100,rep,name=submessages,proto3"` Mapping map[string]int `protobuf:"bytes,200,opt,name=mapping,proto3"` } original := &message{ Field1: false, Field2: -1, Field3: -2, Field4: -3, Field5: 1, Field6: 2, Field7: 3, Field8: -10, Field9: -11, Field10: 1.0, Field11: 2.0, Field12: "field 12", Field13: nil, Zero1: true, Zero2: 102, Zero3: 103, Zero4: 104, Zero5: 105, Zero6: 106, Zero7: 107, Zero8: 0.108, Zero9: 0.109, Subfield: &submessage{ Answer: "Good!", }, Submessages: []submessage{ {Question: "Q1?", Answer: "A1"}, {Question: "Q2?", Answer: "A2"}, {Question: "Q3?", Answer: "A3"}, }, Mapping: map[string]int{ "hello": 1, "world": 2, }, } expected := &message{ Field1: true, Field2: 2, Field3: 3, Field4: 4, Field5: 10, Field6: 11, Field7: 12, Field8: -21, Field9: -42, Field10: 0.25, Field11: 0.5, Field12: "Hello!", Field13: []byte("World!"), Subfield: &submessage{ Question: "How are you?", Answer: "Good!", }, Submessages: []submessage{ {Question: "Q1?", Answer: "A1"}, {Question: "Q2?", Answer: "A2"}, {Question: "Q3?", Answer: "Hello World!"}, }, Mapping: map[string]int{ "answer": 42, }, } rw, err := ParseRewriteTemplate(TypeOf(reflect.TypeOf(original)), []byte(`{ "field_1": true, "field_2": 2, "field_3": 3, "field_4": 4, "field_5": 10, "field_6": 11, "field_7": 12, "field_8": -21, "field_9": -42, "field_10": 0.25, "field_11": 0.5, "field_12": "Hello!", "field_13": "World!", "zero_1": null, "zero_2": null, "zero_3": null, "zero_4": null, "zero_5": null, "zero_6": null, "zero_7": null, "zero_8": null, "zero_9": null, "subfield": { "question": "How are you?" }, "submessages": [ { "question": "Q1?", "answer": "A1" }, { "question": "Q2?", "answer": "A2" }, { "question": "Q3?", "answer": "Hello World!" } ], "mapping": { "answer": 42 } }`)) if err != nil { t.Fatal(err) } b1, err := Marshal(original) if err != nil { t.Fatal(err) } b2, err := rw.Rewrite(nil, b1) if err != nil { t.Fatal(err) } found := &message{} if err := Unmarshal(b2, &found); err != nil { t.Fatal(err) } if !reflect.DeepEqual(expected, found) { t.Error("messages mismatch after rewrite") t.Logf("want:\n%+v", expected) t.Logf("got:\n%+v", found) } } func BenchmarkRewrite(b *testing.B) { type message struct { A int B float32 C float64 D string } in := message{A: 21, B: 0.125, D: "A"} rw := MessageRewriter{ 1: FieldNumber(1).Int(42), 2: FieldNumber(2).Float32(-1), 3: FieldNumber(3).Float64(+1), 4: FieldNumber(4).String("Hello World!"), } p, err := Marshal(in) if err != nil { b.Fatal(err) } out := make([]byte, 0, 2*cap(p)) for i := 0; i < b.N; i++ { out, err = rw.Rewrite(out[:0], p) } } func TestRewriteStructIdentity(t *testing.T) { type Node struct { Next []Node `protobuf:"bytes,1,rep,name=next,proto3" json:"next"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name"` Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type"` On uint32 `protobuf:"varint,4,opt,name=on,proto3,enum=op.Status" json:"on,omitempty"` Key string `protobuf:"bytes,5,opt,name=key,proto3" json:"key,omitempty"` Seed uint64 `protobuf:"fixed64,6,opt,name=seed,proto3" json:"seed,omitempty"` ScheduleAfter uint32 `protobuf:"varint,7,opt,name=schedule_after,json=scheduleAfter,proto3" json:"schedule_after,omitempty"` ExpireAfter uint32 `protobuf:"varint,8,opt,name=expire_after,json=expireAfter,proto3" json:"expire_after,omitempty"` BackoffCoefficient uint32 `protobuf:"varint,9,opt,name=backoff_coefficient,json=backoffCoefficient,proto3" json:"backoff_coefficient,omitempty"` BackoffMinDelay uint32 `protobuf:"varint,10,opt,name=backoff_min_delay,json=backoffMinDelay,proto3" json:"backoff_min_delay,omitempty"` BackoffMaxDelay uint32 `protobuf:"varint,11,opt,name=backoff_max_delay,json=backoffMaxDelay,proto3" json:"backoff_max_delay,omitempty"` ExecutionTimeout uint32 `protobuf:"varint,12,opt,name=execution_timeout,json=executionTimeout,proto3" json:"execution_timeout,omitempty"` NextLength uint64 `protobuf:"varint,13,opt,name=next_length,json=nextLength,proto3" json:"next_length,omitempty"` BatchMaxBytes uint32 `protobuf:"varint,14,opt,name=batch_max_bytes,json=batchMaxBytes,proto3" json:"batch_max_bytes,omitempty"` BatchMaxCount uint32 `protobuf:"varint,15,opt,name=batch_max_count,json=batchMaxCount,proto3" json:"batch_max_count,omitempty"` BatchTimeout uint32 `protobuf:"varint,16,opt,name=batch_timeout,json=batchTimeout,proto3" json:"batch_timeout,omitempty"` BatchKey [16]byte `protobuf:"bytes,17,opt,name=batch_key,json=batchKey,proto3,customtype=U128" json:"batch_key"` } type Header struct { Flows string `protobuf:"bytes,1,opt,name=flows,proto3" json:"flows,omitempty"` Root Node `protobuf:"bytes,2,opt,name=root,proto3" json:"root"` TraceContext []byte `protobuf:"bytes,6,opt,name=trace_context,json=traceContext,proto3" json:"trace_context"` ContentLength int64 `protobuf:"varint,7,opt,name=content_length,json=contentLength,proto3" json:"content_length,omitempty"` ContentType string `protobuf:"bytes,8,opt,name=content_type,json=contentType,proto3" json:"content_type"` } b := []byte{ 0xa, 0xd, 0x66, 0x6c, 0x6f, 0x77, 0x2d, 0x42, 0x3a, 0x66, 0x6c, 0x6f, 0x77, 0x2d, 0x30, 0x12, 0x7a, 0xa, 0x30, 0x12, 0xc, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x61, 0x72, 0x64, 0x1a, 0x0, 0x20, 0x2, 0x31, 0x69, 0xf2, 0xc9, 0xd0, 0xc1, 0x2f, 0xe0, 0x80, 0x68, 0x65, 0x8a, 0x1, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x4, 0x74, 0x65, 0x73, 0x74, 0x1a, 0x4, 0x68, 0x74, 0x74, 0x70, 0x31, 0xa, 0x7f, 0xf5, 0xf8, 0x13, 0x1d, 0xfb, 0x17, 0x38, 0xc1, 0xd, 0x40, 0xff, 0xdb, 0x1, 0x48, 0xc6, 0xf, 0x50, 0xbd, 0x1b, 0x58, 0x9a, 0xbe, 0x2, 0x60, 0x88, 0x27, 0x68, 0x5d, 0x70, 0x80, 0x80, 0x4, 0x78, 0xa, 0x80, 0x1, 0xd0, 0xf, 0x8a, 0x1, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x32, 0x1c, 0xa, 0x10, 0x2a, 0x43, 0x4c, 0xf3, 0x8c, 0x67, 0x48, 0x8f, 0xe, 0xca, 0xe8, 0x28, 0x96, 0x6c, 0x2b, 0xe4, 0x12, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x38, 0x91, 0x66, 0x42, 0x18, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, } m := Header{} if err := Unmarshal(b, &m); err != nil { t.Fatal(err) } r, err := ParseRewriteTemplate(TypeOf(reflect.TypeOf(m)), []byte(`{"root":{}}`)) if err != nil { t.Fatal(err) } c, err := r.Rewrite(nil, b) if err != nil { t.Fatal(err) } x := Header{} if err := Unmarshal(c, &x); err != nil { t.Fatal(err) } if !reflect.DeepEqual(m, x) { t.Errorf("messages mismatch") t.Logf("want: %+v", m) t.Logf("got: %+v", x) } }