From 6be8d61d4181917540f13e0fd38d8b1adc97f23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez?= Date: Mon, 14 Nov 2022 15:52:16 +0100 Subject: [PATCH] Add test to reproduce actgardner/gogen-avro issue --- singledecoder_test.go | 86 ++++++++++++++++++++++++++++++++++++++--- testschema2.avsc | 27 +++++++++++++ testschema2_gen_test.go | 74 +++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 testschema2.avsc create mode 100644 testschema2_gen_test.go diff --git a/singledecoder_test.go b/singledecoder_test.go index 9d93c9a..5073977 100644 --- a/singledecoder_test.go +++ b/singledecoder_test.go @@ -11,9 +11,11 @@ import ( ) //go:generate avrogo testschema1.avsc +//go:generate avrogo testschema2.avsc -func TestSingleDecoder(t *testing.T) { +func TestSingleDecoder_CompabilitySameRecord(t *testing.T) { c := qt.New(t) + ctx := context.Background() dec := avro.NewSingleDecoder(memRegistry{ 1: mustParseType(`{ "name": "TestRecord", @@ -28,6 +30,17 @@ func TestSingleDecoder(t *testing.T) { "type": { "type": "int" } + }, { + "name": "C", + "type": [ + "null", + { + "name": "EnumC", + "type": "enum", + "symbols": ["x", "y", "z"] + } + ], + "default": "null" }] }`), 2: mustParseType(`{ @@ -69,23 +82,86 @@ func TestSingleDecoder(t *testing.T) { // 1: the schema id // 40: B=20 (zig-zag encoded) // 80: A=40 (ditto) - _, err = dec.Unmarshal(context.Background(), []byte{1, 40, 80}, &x) + // 0: C=null Choose null value + _, err = dec.Unmarshal(ctx, []byte{1, 40, 80, 0}, &x) c.Assert(err, qt.Equals, nil) - c.Assert(x, qt.Equals, TestRecord{A: 40, B: 20}) + c.Assert(x, qt.DeepEquals, TestRecord{A: 40, B: 20}) // Check the record compatibility stuff is working by reading from a // record written with less fields (note: the default value for A is 42). var x1 TestRecord - _, err = dec.Unmarshal(context.Background(), []byte{2, 80}, &x1) + _, err = dec.Unmarshal(ctx, []byte{2, 80}, &x1) c.Assert(err, qt.Equals, nil) c.Assert(x1, qt.Equals, TestRecord{A: 42, B: 40}) // There's no default value for A, so it doesn't work that way around. var x2 TestRecord - _, err = dec.Unmarshal(context.Background(), []byte{3, 80}, &x2) + _, err = dec.Unmarshal(ctx, []byte{3, 80}, &x2) c.Assert(err, qt.ErrorMatches, `cannot unmarshal: cannot create decoder: Incompatible schemas: field B in reader is not present in writer and has no default value`) } +func TestSingleDecoder_CompabilityDifferentRecord(t *testing.T) { + c := qt.New(t) + ctx := context.Background() + dec := avro.NewSingleDecoder(memRegistry{ + 1: mustParseType(`{ + "name": "TestRecord", + "type": "record", + "fields": [{ + "name": "B", + "type": { + "type": "int" + } + }, { + "name": "A", + "type": { + "type": "int" + } + }] +}`), + 2: mustParseType(`{ + "name": "TestRecord", + "type": "record", + "fields": [{ + "name": "B", + "type": { + "type": "int" + } + }, { + "name": "A", + "type": { + "type": "int" + } + }, { + "name": "C", + "type": [ + "null", + { + "name": "EnumC", + "type": "enum", + "symbols": ["x", "y", "z"] + } + ], + "default": "null" + }] +}`), + }, nil) + cVal := EnumCY + data, _, err := avro.Marshal(TestNewRecord{A: 40, B: 20, C: &cVal}) + c.Assert(err, qt.IsNil) + c.Logf("data: %d", data) + var x TestRecord + // In the byte slice below: + // 1: the schema id + // 40: B=20 (zig-zag encoded) + // 80: A=40 (ditto) + // 2: C=Choose EnumC type + // 2 C="y" + _, err = dec.Unmarshal(ctx, []byte{2, 40, 80, 2, 2}, &x) + c.Assert(err, qt.IsNil) + c.Assert(x, qt.Equals, TestRecord{A: 40, B: 20}) +} + // memRegistry implements DecodingRegistry and EncodingRegistry by associating a single-byte // schema ID with schemas. type memRegistry map[int64]*avro.Type diff --git a/testschema2.avsc b/testschema2.avsc new file mode 100644 index 0000000..e596878 --- /dev/null +++ b/testschema2.avsc @@ -0,0 +1,27 @@ +{ + "name": "TestNewRecord", + "type": "record", + "fields": [{ + "name": "A", + "type": { + "type": "int" + }, + "default": 42 + }, { + "name": "B", + "type": { + "type": "int" + } + }, { + "name": "C", + "type": [ + "null", + { + "name": "EnumC", + "type": "enum", + "symbols": ["x", "y", "z"] + } + ], + "default": null + }] +} diff --git a/testschema2_gen_test.go b/testschema2_gen_test.go new file mode 100644 index 0000000..c1834de --- /dev/null +++ b/testschema2_gen_test.go @@ -0,0 +1,74 @@ +// Code generated by avrogen. DO NOT EDIT. + +package avro_test + +import ( + "fmt" + "github.com/heetch/avro/avrotypegen" + "strconv" +) + +type EnumC int + +const ( + EnumCX EnumC = iota + EnumCY + EnumCZ +) + +var _EnumC_strings = []string{ + "x", + "y", + "z", +} + +// String returns the textual representation of EnumC. +func (e EnumC) String() string { + if e < 0 || int(e) >= len(_EnumC_strings) { + return "EnumC(" + strconv.FormatInt(int64(e), 10) + ")" + } + return _EnumC_strings[e] +} + +// MarshalText implements encoding.TextMarshaler +// by returning the textual representation of EnumC. +func (e EnumC) MarshalText() ([]byte, error) { + if e < 0 || int(e) >= len(_EnumC_strings) { + return nil, fmt.Errorf("EnumC value %d is out of bounds", e) + } + return []byte(_EnumC_strings[e]), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler +// by expecting the textual representation of EnumC. +func (e *EnumC) UnmarshalText(data []byte) error { + // Note for future: this could be more efficient. + for i, s := range _EnumC_strings { + if string(data) == s { + *e = EnumC(i) + return nil + } + } + return fmt.Errorf("unknown value %q for EnumC", data) +} + +type TestNewRecord struct { + A int + B int + C *EnumC +} + +// AvroRecord implements the avro.AvroRecord interface. +func (TestNewRecord) AvroRecord() avrotypegen.RecordInfo { + return avrotypegen.RecordInfo{ + Schema: `{"fields":[{"default":42,"name":"A","type":{"type":"int"}},{"name":"B","type":{"type":"int"}},{"default":null,"name":"C","type":["null",{"name":"EnumC","symbols":["x","y","z"],"type":"enum"}]}],"name":"TestNewRecord","type":"record"}`, + Required: []bool{ + 1: true, + }, + Defaults: []func() interface{}{ + 0: func() interface{} { + return 42 + }, + }, + } +}