sftest/sftest.go (270 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package sftest import ( "encoding/json" "errors" "fmt" "testing" "github.com/stretchr/testify/assert" structform "github.com/elastic/go-structform" ) type Recording []Record type Record interface { Replay(v structform.ExtVisitor) error } type NilRec struct{} type BoolRec struct{ Value bool } type StringRec struct{ Value string } type IntRec struct{ Value int } type Int8Rec struct{ Value int8 } type Int16Rec struct{ Value int16 } type Int32Rec struct{ Value int32 } type Int64Rec struct{ Value int64 } type UintRec struct{ Value uint } type ByteRec struct{ Value byte } type Uint8Rec struct{ Value uint8 } type Uint16Rec struct{ Value uint16 } type Uint32Rec struct{ Value uint32 } type Uint64Rec struct{ Value uint64 } type Float32Rec struct{ Value float32 } type Float64Rec struct{ Value float64 } // extended (yet) non-recordible records type Int8ArrRec struct{ Value []int8 } type StringArrRec struct{ Value []string } type StringObjRec struct{ Value map[string]string } type UintObjRec struct{ Value map[string]uint } type ObjectStartRec struct { Len int T structform.BaseType } type ObjectFinishRec struct{} type ObjectKeyRec struct{ Value string } type ArrayStartRec struct { Len int T structform.BaseType } type ArrayFinishRec struct{} func (NilRec) Replay(vs structform.ExtVisitor) error { return vs.OnNil() } func (r BoolRec) Replay(vs structform.ExtVisitor) error { return vs.OnBool(r.Value) } func (r StringRec) Replay(vs structform.ExtVisitor) error { return vs.OnString(r.Value) } func (r IntRec) Replay(vs structform.ExtVisitor) error { return vs.OnInt(r.Value) } func (r Int8Rec) Replay(vs structform.ExtVisitor) error { return vs.OnInt8(r.Value) } func (r Int16Rec) Replay(vs structform.ExtVisitor) error { return vs.OnInt16(r.Value) } func (r Int32Rec) Replay(vs structform.ExtVisitor) error { return vs.OnInt32(r.Value) } func (r Int64Rec) Replay(vs structform.ExtVisitor) error { return vs.OnInt64(r.Value) } func (r UintRec) Replay(vs structform.ExtVisitor) error { return vs.OnUint(r.Value) } func (r ByteRec) Replay(vs structform.ExtVisitor) error { return vs.OnByte(r.Value) } func (r Uint8Rec) Replay(vs structform.ExtVisitor) error { return vs.OnUint8(r.Value) } func (r Uint16Rec) Replay(vs structform.ExtVisitor) error { return vs.OnUint16(r.Value) } func (r Uint32Rec) Replay(vs structform.ExtVisitor) error { return vs.OnUint32(r.Value) } func (r Uint64Rec) Replay(vs structform.ExtVisitor) error { return vs.OnUint64(r.Value) } func (r Float32Rec) Replay(vs structform.ExtVisitor) error { return vs.OnFloat32(r.Value) } func (r Float64Rec) Replay(vs structform.ExtVisitor) error { return vs.OnFloat64(r.Value) } func (r ObjectStartRec) Replay(vs structform.ExtVisitor) error { return vs.OnObjectStart(r.Len, r.T) } func (r ObjectFinishRec) Replay(vs structform.ExtVisitor) error { return vs.OnObjectFinished() } func (r ObjectKeyRec) Replay(vs structform.ExtVisitor) error { return vs.OnKey(r.Value) } func (r ArrayStartRec) Replay(vs structform.ExtVisitor) error { return vs.OnArrayStart(r.Len, r.T) } func (r ArrayFinishRec) Replay(vs structform.ExtVisitor) error { return vs.OnArrayFinished() } func (r Int8ArrRec) Replay(vs structform.ExtVisitor) error { return vs.OnInt8Array(r.Value) } func (r StringArrRec) Replay(vs structform.ExtVisitor) error { return vs.OnStringArray(r.Value) } func (r UintObjRec) Replay(vs structform.ExtVisitor) error { return vs.OnUintObject(r.Value) } func (r StringObjRec) Replay(vs structform.ExtVisitor) error { return vs.OnStringObject(r.Value) } func (rec *Recording) Replay(vs structform.Visitor) error { evs := structform.EnsureExtVisitor(vs) for _, r := range *rec { if err := r.Replay(evs); err != nil { return err } } return nil } func (r *Recording) add(v Record) error { *r = append(*r, v) return nil } func (r *Recording) OnNil() error { return r.add(NilRec{}) } func (r *Recording) OnBool(b bool) error { return r.add(BoolRec{b}) } func (r *Recording) OnString(s string) error { return r.add(StringRec{s}) } func (r *Recording) OnInt8(i int8) error { return r.add(Int8Rec{i}) } func (r *Recording) OnInt16(i int16) error { return r.add(Int16Rec{i}) } func (r *Recording) OnInt32(i int32) error { return r.add(Int32Rec{i}) } func (r *Recording) OnInt64(i int64) error { return r.add(Int64Rec{i}) } func (r *Recording) OnInt(i int) error { return r.add(IntRec{i}) } func (r *Recording) OnUint8(i uint8) error { return r.add(Uint8Rec{i}) } func (r *Recording) OnByte(i byte) error { return r.add(ByteRec{i}) } func (r *Recording) OnUint16(i uint16) error { return r.add(Uint16Rec{i}) } func (r *Recording) OnUint32(i uint32) error { return r.add(Uint32Rec{i}) } func (r *Recording) OnUint64(i uint64) error { return r.add(Uint64Rec{i}) } func (r *Recording) OnUint(i uint) error { return r.add(UintRec{i}) } func (r *Recording) OnFloat32(i float32) error { return r.add(Float32Rec{i}) } func (r *Recording) OnFloat64(i float64) error { return r.add(Float64Rec{i}) } func (r *Recording) OnArrayStart(len int, baseType structform.BaseType) error { return r.add(ArrayStartRec{len, baseType}) } func (r *Recording) OnArrayFinished() error { return r.add(ArrayFinishRec{}) } func (r *Recording) OnObjectStart(len int, baseType structform.BaseType) error { return r.add(ObjectStartRec{len, baseType}) } func (r *Recording) OnObjectFinished() error { return r.add(ObjectFinishRec{}) } func (r *Recording) OnKey(s string) error { return r.add(ObjectKeyRec{s}) } func (r *Recording) expand() Recording { var to Recording r.Replay(&to) return to } func (r Recording) Assert(t *testing.T, expected Recording) { exp, err := expected.ToJSON() if err != nil { t.Error("Assert (expected): ", err) t.Logf(" recording: %#v", exp) return } act, err := r.ToJSON() if err != nil { t.Error("Assert (actual): ", err) t.Logf(" recording: %#v", r) return } assert.Equal(t, exp, act) } func (r Recording) ToJSON() (string, error) { v, _, err := buildValue(r.expand()) if err != nil { return "", err } b, err := json.MarshalIndent(v, "", " ") if err != nil { return "", err } return string(b), nil } type builder struct { stack []interface{} value interface{} } func buildValue(rec Recording) (interface{}, Recording, error) { if len(rec) == 0 { return nil, nil, errors.New("empty recording") } switch v := rec[0].(type) { case NilRec: return nil, rec[1:], nil case BoolRec: return v.Value, rec[1:], nil case StringRec: return v.Value, rec[1:], nil case IntRec: return v.Value, rec[1:], nil case Int8Rec: return v.Value, rec[1:], nil case Int16Rec: return v.Value, rec[1:], nil case Int32Rec: return v.Value, rec[1:], nil case Int64Rec: return v.Value, rec[1:], nil case UintRec: return v.Value, rec[1:], nil case ByteRec: return v.Value, rec[1:], nil case Uint8Rec: return v.Value, rec[1:], nil case Uint16Rec: return v.Value, rec[1:], nil case Uint32Rec: return v.Value, rec[1:], nil case Uint64Rec: return v.Value, rec[1:], nil case Float32Rec: return v.Value, rec[1:], nil case Float64Rec: return v.Value, rec[1:], nil case ArrayStartRec: return buildArray(rec[1:]) case ObjectStartRec: return buildObject(rec[1:]) default: return nil, nil, fmt.Errorf("Invalid record entry: %v", v) } } func buildArray(rec Recording) (interface{}, Recording, error) { a := []interface{}{} for len(rec) > 0 { var ( v interface{} err error ) if _, end := rec[0].(ArrayFinishRec); end { return a, rec[1:], nil } v, rec, err = buildValue(rec) if err != nil { return nil, nil, err } a = append(a, v) } return nil, nil, errors.New("missing array finish record") } func buildObject(rec Recording) (interface{}, Recording, error) { obj := map[string]interface{}{} for len(rec) > 0 { var ( key string v interface{} err error ) switch v := rec[0].(type) { case ObjectFinishRec: return obj, rec[1:], nil case ObjectKeyRec: key = v.Value } v, rec, err = buildValue(rec[1:]) if err != nil { return nil, nil, err } obj[key] = v } return nil, nil, errors.New("missing object finish record") } func TestEncodeParseConsistent( t *testing.T, samples []Recording, constr func() (structform.Visitor, func(structform.Visitor) error), ) { for i, sample := range samples { sample := sample expected, err := sample.ToJSON() if err != nil { panic(err) } title := fmt.Sprintf("test %v: %#v => %v", i, sample, expected) t.Run(title, func(t *testing.T) { t.Parallel() enc, dec := constr() err = sample.Replay(enc) if err != nil { t.Errorf("Failed to encode %#v with %v", sample, err) return } var target Recording err = dec(&target) if err != nil { t.Errorf("Failed to decode %#v with %v", target, err) t.Logf(" recording: %#v", target) } target.Assert(t, sample) }) } } func concatSamples(recs ...[]Recording) []Recording { var out []Recording for _, r := range recs { out = append(out, r...) } return out }