git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/object/transformer/transformer_test.go (about) 1 package transformer 2 3 import ( 4 "context" 5 "crypto/rand" 6 "crypto/sha256" 7 "testing" 8 9 cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" 10 cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" 11 objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" 12 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" 13 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version" 14 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 15 "github.com/stretchr/testify/require" 16 ) 17 18 func TestTransformer(t *testing.T) { 19 const maxSize = 100 20 21 tt := new(testTarget) 22 23 target, pk := newPayloadSizeLimiter(maxSize, 0, func() ObjectWriter { return tt }) 24 25 cnr := cidtest.ID() 26 hdr := newObject(cnr) 27 28 var owner user.ID 29 user.IDFromKey(&owner, pk.PrivateKey.PublicKey) 30 hdr.SetOwnerID(owner) 31 32 expectedPayload := make([]byte, maxSize*2+maxSize/2) 33 _, _ = rand.Read(expectedPayload) 34 35 ids := writeObject(t, context.Background(), target, hdr, expectedPayload) 36 require.Equal(t, 4, len(tt.objects)) // 3 parts + linking object 37 38 var actualPayload []byte 39 for i := range tt.objects { 40 childCnr, ok := tt.objects[i].ContainerID() 41 require.True(t, ok) 42 require.Equal(t, cnr, childCnr) 43 require.Equal(t, objectSDK.TypeRegular, tt.objects[i].Type()) 44 require.Equal(t, owner, tt.objects[i].OwnerID()) 45 46 payload := tt.objects[i].Payload() 47 require.EqualValues(t, tt.objects[i].PayloadSize(), len(payload)) 48 actualPayload = append(actualPayload, payload...) 49 50 if len(payload) != 0 { 51 cs, ok := tt.objects[i].PayloadChecksum() 52 require.True(t, ok) 53 54 h := sha256.Sum256(payload) 55 require.Equal(t, h[:], cs.Value()) 56 } 57 58 require.True(t, tt.objects[i].VerifyIDSignature()) 59 switch i { 60 case 0, 1: 61 require.EqualValues(t, maxSize, len(payload)) 62 require.Nil(t, tt.objects[i].Parent()) 63 case 2: 64 require.EqualValues(t, maxSize/2, len(payload)) 65 parent := tt.objects[i].Parent() 66 require.NotNil(t, parent) 67 require.Nil(t, parent.SplitID()) 68 require.True(t, parent.VerifyIDSignature()) 69 case 3: 70 parID, ok := tt.objects[i].ParentID() 71 require.True(t, ok) 72 require.Equal(t, ids.ParentID, &parID) 73 74 children := tt.objects[i].Children() 75 for j := range i { 76 id, ok := tt.objects[j].ID() 77 require.True(t, ok) 78 require.Equal(t, id, children[j]) 79 } 80 } 81 } 82 require.Equal(t, expectedPayload, actualPayload) 83 84 t.Run("parent checksum", func(t *testing.T) { 85 cs, ok := ids.ParentHeader.PayloadChecksum() 86 require.True(t, ok) 87 88 h := sha256.Sum256(expectedPayload) 89 require.Equal(t, h[:], cs.Value()) 90 }) 91 } 92 93 func newObject(cnr cid.ID) *objectSDK.Object { 94 ver := version.Current() 95 hdr := objectSDK.New() 96 hdr.SetContainerID(cnr) 97 hdr.SetType(objectSDK.TypeRegular) 98 hdr.SetVersion(&ver) 99 return hdr 100 } 101 102 func writeObject(t *testing.T, ctx context.Context, target ChunkedObjectWriter, header *objectSDK.Object, payload []byte) *AccessIdentifiers { 103 require.NoError(t, target.WriteHeader(ctx, header)) 104 105 _, err := target.Write(ctx, payload) 106 require.NoError(t, err) 107 108 ids, err := target.Close(ctx) 109 require.NoError(t, err) 110 111 return ids 112 } 113 114 func BenchmarkTransformer(b *testing.B) { 115 hdr := newObject(cidtest.ID()) 116 117 const ( 118 // bufferSize is taken from https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/670619d2426fee233a37efe21a0471989b16a4fc/pool/pool.go#L1825 119 bufferSize = 3 * 1024 * 1024 120 smallSize = 8 * 1024 121 bigSize = 64 * 1024 * 1024 * 9 / 2 // 4.5 parts 122 ) 123 b.Run("small", func(b *testing.B) { 124 b.Run("no size hint", func(b *testing.B) { 125 benchmarkTransformer(b, hdr, smallSize, 0, 0) 126 }) 127 b.Run("no size hint, with buffer", func(b *testing.B) { 128 benchmarkTransformer(b, hdr, smallSize, 0, bufferSize) 129 }) 130 b.Run("with size hint, with buffer", func(b *testing.B) { 131 benchmarkTransformer(b, hdr, smallSize, smallSize, bufferSize) 132 }) 133 }) 134 b.Run("big", func(b *testing.B) { 135 b.Run("no size hint", func(b *testing.B) { 136 benchmarkTransformer(b, hdr, bigSize, 0, 0) 137 }) 138 b.Run("no size hint, with buffer", func(b *testing.B) { 139 benchmarkTransformer(b, hdr, bigSize, 0, bufferSize) 140 }) 141 b.Run("with size hint, with buffer", func(b *testing.B) { 142 benchmarkTransformer(b, hdr, bigSize, bigSize, bufferSize) 143 }) 144 }) 145 } 146 147 func benchmarkTransformer(b *testing.B, header *objectSDK.Object, payloadSize, sizeHint, bufferSize int) { 148 const maxSize = 64 * 1024 * 1024 149 150 payload := make([]byte, payloadSize) 151 ctx := context.Background() 152 153 b.ReportAllocs() 154 b.ResetTimer() 155 for range b.N { 156 f, _ := newPayloadSizeLimiter(maxSize, uint64(sizeHint), func() ObjectWriter { return benchTarget{} }) 157 if err := f.WriteHeader(ctx, header); err != nil { 158 b.Fatalf("write header: %v", err) 159 } 160 if bufferSize == 0 { 161 if _, err := f.Write(ctx, payload); err != nil { 162 b.Fatalf("write: %v", err) 163 } 164 } else { 165 j := 0 166 for ; j+bufferSize < payloadSize; j += bufferSize { 167 if _, err := f.Write(ctx, payload[j:j+bufferSize]); err != nil { 168 b.Fatalf("write: %v", err) 169 } 170 } 171 if _, err := f.Write(ctx, payload[j:payloadSize]); err != nil { 172 b.Fatalf("write: %v", err) 173 } 174 } 175 if _, err := f.Close(ctx); err != nil { 176 b.Fatalf("close: %v", err) 177 } 178 } 179 } 180 181 func newPayloadSizeLimiter(maxSize uint64, sizeHint uint64, nextTarget TargetInitializer) (ChunkedObjectWriter, *keys.PrivateKey) { 182 p, err := keys.NewPrivateKey() 183 if err != nil { 184 panic(err) 185 } 186 187 return NewPayloadSizeLimiter(Params{ 188 Key: &p.PrivateKey, 189 NextTargetInit: nextTarget, 190 NetworkState: dummyEpochSource(123), 191 MaxSize: maxSize, 192 SizeHint: sizeHint, 193 WithoutHomomorphicHash: true, 194 }), p 195 } 196 197 type dummyEpochSource uint64 198 199 func (s dummyEpochSource) CurrentEpoch() uint64 { 200 return uint64(s) 201 } 202 203 type benchTarget struct{} 204 205 func (benchTarget) WriteObject(context.Context, *objectSDK.Object) error { 206 return nil 207 } 208 209 type testTarget struct { 210 objects []*objectSDK.Object 211 } 212 213 func (tt *testTarget) WriteObject(_ context.Context, o *objectSDK.Object) error { 214 tt.objects = append(tt.objects, o) 215 return nil // AccessIdentifiers should not be used. 216 }