package pvdata_test import ( "bytes" "math" "reflect" "testing" "github.com/uopi/gopva/pvdata" ) // ---- Scalar value codec: every type code ----------------------------- func TestScalarRoundTrip(t *testing.T) { cases := []struct { name string tc byte v pvdata.Value }{ {"bool-true", pvdata.TypeCodeBoolean, true}, {"bool-false", pvdata.TypeCodeBoolean, false}, {"byte", pvdata.TypeCodeByte, int8(-12)}, {"short", pvdata.TypeCodeShort, int16(-3000)}, {"int", pvdata.TypeCodeInt, int32(-100000)}, {"long", pvdata.TypeCodeLong, int64(-1 << 40)}, {"ubyte", pvdata.TypeCodeUByte, uint8(200)}, {"ushort", pvdata.TypeCodeUShort, uint16(60000)}, {"uint", pvdata.TypeCodeUInt, uint32(4000000000)}, {"ulong", pvdata.TypeCodeULong, uint64(1 << 50)}, {"float", pvdata.TypeCodeFloat, float32(1.5)}, {"double", pvdata.TypeCodeDouble, float64(2.5)}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { desc := pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: c.tc} var buf bytes.Buffer if err := pvdata.WriteValue(&buf, desc, c.v); err != nil { t.Fatalf("WriteValue: %v", err) } got, err := pvdata.ReadValue(&buf, desc) if err != nil { t.Fatalf("ReadValue: %v", err) } if got != c.v { t.Errorf("got %v (%T) want %v (%T)", got, got, c.v, c.v) } }) } } // ---- Scalar array codec: every array code ---------------------------- func TestScalarArrayRoundTrip(t *testing.T) { cases := []struct { name string tc byte v pvdata.Value }{ {"bool", pvdata.TypeCodeBoolArr, []bool{true, false, true}}, {"byte", pvdata.TypeCodeByteArr, []int8{-1, 2, -3}}, {"short", pvdata.TypeCodeShortArr, []int16{-1, 2, 30000}}, {"int", pvdata.TypeCodeIntArr, []int32{-1, 2, 1 << 30}}, {"long", pvdata.TypeCodeLongArr, []int64{-1, 1 << 40}}, {"ubyte", pvdata.TypeCodeUByteArr, []uint8{1, 2, 255}}, {"ushort", pvdata.TypeCodeUShortArr, []uint16{1, 60000}}, {"uint", pvdata.TypeCodeUIntArr, []uint32{1, 4000000000}}, {"ulong", pvdata.TypeCodeULongArr, []uint64{1, 1 << 50}}, {"float", pvdata.TypeCodeFloatArr, []float32{1.5, -2.25}}, {"double", pvdata.TypeCodeDoubleArr, []float64{1.5, -2.25}}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { desc := pvdata.FieldDesc{Kind: pvdata.KindScalarArr, TypeCode: c.tc} var buf bytes.Buffer if err := pvdata.WriteValue(&buf, desc, c.v); err != nil { t.Fatalf("WriteValue: %v", err) } got, err := pvdata.ReadValue(&buf, desc) if err != nil { t.Fatalf("ReadValue: %v", err) } if !reflect.DeepEqual(got, c.v) { t.Errorf("got %v want %v", got, c.v) } }) } } func TestStringArrayRoundTrip(t *testing.T) { desc := pvdata.FieldDesc{Kind: pvdata.KindStringArr, TypeCode: pvdata.TypeCodeStringArr} for _, v := range [][]string{{"a", "", "epics"}, {}} { var buf bytes.Buffer if err := pvdata.WriteValue(&buf, desc, v); err != nil { t.Fatalf("WriteValue: %v", err) } got, err := pvdata.ReadValue(&buf, desc) if err != nil { t.Fatalf("ReadValue: %v", err) } gs := got.([]string) if len(gs) != len(v) { t.Fatalf("len: got %d want %d", len(gs), len(v)) } for i := range v { if gs[i] != v[i] { t.Errorf("[%d]: got %q want %q", i, gs[i], v[i]) } } } } // ---- FieldDesc round-trip for every Kind ----------------------------- func TestFieldDescVariants(t *testing.T) { descs := []pvdata.FieldDesc{ {TypeCode: pvdata.TypeCodeNull}, {Kind: pvdata.KindString, TypeCode: pvdata.TypeCodeString}, {Kind: pvdata.KindStringArr, TypeCode: pvdata.TypeCodeStringArr}, {Kind: pvdata.KindVariant, TypeCode: pvdata.TypeCodeVariant}, {Kind: pvdata.KindScalarArr, TypeCode: pvdata.TypeCodeDoubleArr}, {Kind: pvdata.KindUnion, TypeCode: pvdata.TypeCodeUnion, TypeID: "u", Fields: []pvdata.Field{ {Name: "a", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}}, }}, {Kind: pvdata.KindStructArr, TypeCode: pvdata.TypeCodeStructArr, TypeID: "s", Fields: []pvdata.Field{ {Name: "x", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeDouble}}, }}, {Kind: pvdata.KindUnionArr, TypeCode: pvdata.TypeCodeUnionArr, TypeID: "ua", Fields: []pvdata.Field{ {Name: "y", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}}, }}, } for _, d := range descs { var buf bytes.Buffer if err := pvdata.WriteFieldDesc(&buf, d); err != nil { t.Fatalf("WriteFieldDesc(%#x): %v", d.TypeCode, err) } got, err := pvdata.ReadFieldDesc(&buf) if err != nil { t.Fatalf("ReadFieldDesc(%#x): %v", d.TypeCode, err) } if got.TypeCode != d.TypeCode { t.Errorf("TypeCode: got %#x want %#x", got.TypeCode, d.TypeCode) } if got.TypeID != d.TypeID { t.Errorf("TypeID: got %q want %q", got.TypeID, d.TypeID) } if len(got.Fields) != len(d.Fields) { t.Errorf("Fields: got %d want %d", len(got.Fields), len(d.Fields)) } } } // ---- Struct array value (readStructArr) ------------------------------ func TestStructArrRoundTrip(t *testing.T) { fields := []pvdata.Field{ {Name: "x", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}}, } elems := []pvdata.StructValue{ {TypeID: "p", Fields: []pvdata.FieldValue{{Name: "x", Value: int32(1)}}}, {TypeID: "p", Fields: []pvdata.FieldValue{{Name: "x", Value: int32(2)}}}, } var buf bytes.Buffer if err := pvdata.WriteSize(&buf, int64(len(elems))); err != nil { t.Fatal(err) } sdesc := pvdata.FieldDesc{Kind: pvdata.KindStruct, TypeCode: pvdata.TypeCodeStruct, TypeID: "p", Fields: fields} for _, sv := range elems { if err := pvdata.WriteValue(&buf, sdesc, sv); err != nil { t.Fatal(err) } } adesc := pvdata.FieldDesc{Kind: pvdata.KindStructArr, TypeCode: pvdata.TypeCodeStructArr, TypeID: "p", Fields: fields} got, err := pvdata.ReadValue(&buf, adesc) if err != nil { t.Fatal(err) } arr := got.([]pvdata.StructValue) if len(arr) != 2 || arr[0].Fields[0].Value.(int32) != 1 || arr[1].Fields[0].Value.(int32) != 2 { t.Errorf("got %+v", arr) } } // ---- Union value (readUnion / writeUnion) ---------------------------- func unionDesc() pvdata.FieldDesc { return pvdata.FieldDesc{ Kind: pvdata.KindUnion, TypeCode: pvdata.TypeCodeUnion, TypeID: "u", Fields: []pvdata.Field{ {Name: "i", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}}, {Name: "s", Desc: pvdata.FieldDesc{Kind: pvdata.KindString, TypeCode: pvdata.TypeCodeString}}, }, } } func TestUnionSelectedValue(t *testing.T) { desc := unionDesc() // Manually encode: select field 1 (string) with value "hi". var buf bytes.Buffer if err := pvdata.WriteSize(&buf, 1); err != nil { t.Fatal(err) } if err := pvdata.WriteValue(&buf, desc.Fields[1].Desc, "hi"); err != nil { t.Fatal(err) } got, err := pvdata.ReadValue(&buf, desc) if err != nil { t.Fatal(err) } uv := got.(pvdata.UnionValue) if uv.Selected != 1 || uv.Name != "s" || uv.Value.(string) != "hi" { t.Errorf("got %+v", uv) } } func TestUnionNullSelector(t *testing.T) { desc := unionDesc() // writeUnion only writes the selector; an out-of-range selector → null. var buf bytes.Buffer if err := pvdata.WriteValue(&buf, desc, pvdata.UnionValue{Selected: 7}); err != nil { t.Fatal(err) } got, err := pvdata.ReadValue(&buf, desc) if err != nil { t.Fatal(err) } uv := got.(pvdata.UnionValue) if uv.Selected != 7 || uv.Value != nil { t.Errorf("got %+v, want null selector", uv) } } func TestUnionArrRoundTrip(t *testing.T) { desc := pvdata.FieldDesc{ Kind: pvdata.KindUnionArr, TypeCode: pvdata.TypeCodeUnionArr, TypeID: "u", Fields: []pvdata.Field{ {Name: "i", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}}, }, } var buf bytes.Buffer if err := pvdata.WriteSize(&buf, 2); err != nil { t.Fatal(err) } for _, val := range []int32{10, 20} { if err := pvdata.WriteSize(&buf, 0); err != nil { // selector 0 t.Fatal(err) } if err := pvdata.WriteValue(&buf, desc.Fields[0].Desc, val); err != nil { t.Fatal(err) } } got, err := pvdata.ReadValue(&buf, desc) if err != nil { t.Fatal(err) } arr := got.([]pvdata.UnionValue) if len(arr) != 2 || arr[0].Value.(int32) != 10 || arr[1].Value.(int32) != 20 { t.Errorf("got %+v", arr) } } // ---- Variant value (readVariant + WriteValue variant path) ----------- func TestVariantRoundTrip(t *testing.T) { inner := pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeDouble} vv := pvdata.VariantValue{Desc: inner, Value: float64(9.5)} desc := pvdata.FieldDesc{Kind: pvdata.KindVariant, TypeCode: pvdata.TypeCodeVariant} var buf bytes.Buffer if err := pvdata.WriteValue(&buf, desc, vv); err != nil { t.Fatal(err) } got, err := pvdata.ReadValue(&buf, desc) if err != nil { t.Fatal(err) } gvv := got.(pvdata.VariantValue) if gvv.Value.(float64) != 9.5 || gvv.Desc.TypeCode != pvdata.TypeCodeDouble { t.Errorf("got %+v", gvv) } } // ---- Compact size: extended (>MaxInt32) encodings -------------------- func TestCompactSizeLarge(t *testing.T) { cases := []int64{math.MaxInt32, math.MaxInt32 + 1, 1 << 40} for _, n := range cases { var buf bytes.Buffer if err := pvdata.WriteSize(&buf, n); err != nil { t.Fatalf("WriteSize(%d): %v", n, err) } got, err := pvdata.ReadSize(&buf) if err != nil { t.Fatalf("ReadSize(%d): %v", n, err) } if got != n { t.Errorf("round-trip size %d → %d", n, got) } } } // ---- Error / truncation paths ---------------------------------------- func TestReadErrors(t *testing.T) { if _, err := pvdata.ReadSize(bytes.NewReader(nil)); err == nil { t.Error("ReadSize on empty: want error") } if _, err := pvdata.ReadString(bytes.NewReader([]byte{0x05})); err == nil { t.Error("ReadString truncated: want error") } if _, err := pvdata.ReadFieldDesc(bytes.NewReader(nil)); err == nil { t.Error("ReadFieldDesc on empty: want error") } // Unknown scalar type code. badScalar := pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: 0x99} if _, err := pvdata.ReadValue(bytes.NewReader([]byte{1, 2, 3, 4}), badScalar); err == nil { t.Error("ReadValue unknown scalar code: want error") } // Unknown array type code (after a valid size byte). badArr := pvdata.FieldDesc{Kind: pvdata.KindScalarArr, TypeCode: 0x99} if _, err := pvdata.ReadValue(bytes.NewReader([]byte{0x01}), badArr); err == nil { t.Error("ReadValue unknown array code: want error") } // Unknown kind. if _, err := pvdata.ReadValue(bytes.NewReader(nil), pvdata.FieldDesc{Kind: pvdata.Kind(99)}); err == nil { t.Error("ReadValue unknown kind: want error") } // WriteValue unsupported kind (struct array has no writer). if err := pvdata.WriteValue(&bytes.Buffer{}, pvdata.FieldDesc{Kind: pvdata.KindStructArr}, nil); err == nil { t.Error("WriteValue unsupported kind: want error") } } // ---- BitSet edges ---------------------------------------------------- func TestBitSetEdges(t *testing.T) { bs := pvdata.NewBitSet() if bs.Has(0) { t.Error("fresh NewBitSet should be empty") } bs.Set(0) bs.Set(5) bs.Set(12) if got := bs.FieldIndices(); !reflect.DeepEqual(got, []int{0, 5, 12}) { t.Errorf("FieldIndices: got %v want [0 5 12]", got) } if bs.Has(100) { // far out of range → no panic, false t.Error("Has(100) should be false") } // Empty BitSet round-trips as a zero-length payload. var buf bytes.Buffer if err := pvdata.WriteBitSet(&buf, pvdata.NewBitSet()); err != nil { t.Fatal(err) } got, err := pvdata.ReadBitSet(&buf) if err != nil { t.Fatal(err) } if len(got.FieldIndices()) != 0 { t.Errorf("empty BitSet round-trip: got %v", got.FieldIndices()) } }