github.com/lirm/aeron-go@v0.0.0-20230415210743-920325491dc4/aeron/flyweight/flightweight_test.go (about)

     1  /*
     2  Copyright 2016 Stanislav Liberman
     3  Copyright 2022 Steven Stern
     4  
     5  Licensed under the Apache License, Version 2.0 (the "License");
     6  you may not use this file except in compliance with the License.
     7  You may obtain a copy of the License at
     8  
     9  http://www.apache.org/licenses/LICENSE-2.0
    10  
    11  Unless required by applicable law or agreed to in writing, software
    12  distributed under the License is distributed on an "AS IS" BASIS,
    13  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  See the License for the specific language governing permissions and
    15  limitations under the License.
    16  */
    17  
    18  package flyweight
    19  
    20  import (
    21  	"github.com/lirm/aeron-go/aeron/atomic"
    22  	"github.com/lirm/aeron-go/aeron/util"
    23  	"github.com/stretchr/testify/suite"
    24  	"testing"
    25  )
    26  
    27  const (
    28  	str1 = "Hello, World!"
    29  	str2 = "I hope the alignment is right"
    30  	str3 = "Goodbye!"
    31  )
    32  
    33  type FlyweightAlignmentTestSuite struct {
    34  	suite.Suite
    35  
    36  	shouldAlign bool
    37  	stringBuf   *atomic.Buffer
    38  }
    39  
    40  type StringFly struct {
    41  	FWBase
    42  
    43  	s  StringField
    44  	s2 StringField
    45  	s3 StringField
    46  
    47  	shouldWrap bool
    48  }
    49  
    50  func (m *StringFly) Wrap(buf *atomic.Buffer, offset int) Flyweight {
    51  	pos := offset
    52  	pos += m.s.Wrap(buf, pos, m, m.shouldWrap)
    53  	pos += m.s2.Wrap(buf, pos, m, m.shouldWrap)
    54  	pos += m.s3.Wrap(buf, pos, m, m.shouldWrap)
    55  	m.SetSize(pos - offset)
    56  	return m
    57  }
    58  
    59  // Only works for small strings
    60  func (f *FlyweightAlignmentTestSuite) BuildStringFly(str string) []byte {
    61  	bytes := []byte{byte(len(str)), 0, 0, 0}
    62  	bytes = append(bytes, []byte(str)...)
    63  	if f.shouldAlign {
    64  		for len(bytes)%4 != 0 {
    65  			bytes = append(bytes, 0)
    66  		}
    67  	}
    68  	return bytes
    69  }
    70  
    71  func (f *FlyweightAlignmentTestSuite) SetupTest() {
    72  	// This checks for a regression bug in StringField.Wrap.  The original bug was that if a StringField starts with an
    73  	// unaligned offset, Wrap() returned a bad offset.  To check for regressions, we need a somewhat awkward
    74  	// construction:
    75  	//
    76  	// str1: ends with an unaligned offset, setting up the bug
    77  	// str2: triggers the bug, returning a bad offset
    78  	// str3: uses the bad offset and corrupts the buffer (does not have to be a string)
    79  	testBuffer := f.BuildStringFly(str1)
    80  	testBuffer = append(testBuffer, f.BuildStringFly(str2)...)
    81  	testBuffer = append(testBuffer, f.BuildStringFly(str3)...)
    82  	f.stringBuf = atomic.MakeBuffer(testBuffer)
    83  
    84  	sf := StringFly{shouldWrap: f.shouldAlign}
    85  	sf.Wrap(f.stringBuf, 0)
    86  }
    87  
    88  // Return the possibly-aligned length of `str1`, plus 4 to account for the length field.
    89  func (f *FlyweightAlignmentTestSuite) len(str string) int {
    90  	if f.shouldAlign {
    91  		return 4 + int(util.AlignInt32(int32(len(str)), 4))
    92  	} else {
    93  		return 4 + len(str)
    94  	}
    95  }
    96  
    97  func (f *FlyweightAlignmentTestSuite) TestStringFlyweight() {
    98  	sf := StringFly{shouldWrap: f.shouldAlign}
    99  	sf.Wrap(f.stringBuf, 0)
   100  	f.Assert().Equal(f.len(str1)+f.len(str2)+f.len(str3), sf.Size())
   101  	f.Assert().Equal(sf.s.Get(), str1)
   102  	f.Assert().Equal(sf.s2.Get(), str2)
   103  	f.Assert().Equal(sf.s3.Get(), str3)
   104  }
   105  
   106  type FlyweightTestSuite struct {
   107  	suite.Suite
   108  }
   109  
   110  type PaddedFly struct {
   111  	FWBase
   112  
   113  	l1   Int64Field
   114  	i1   Int32Field
   115  	pad  Padding
   116  	i2   Int32Field
   117  	pad2 Padding
   118  }
   119  
   120  func (m *PaddedFly) Wrap(buf *atomic.Buffer, offset int) Flyweight {
   121  	pos := offset
   122  	pos += m.l1.Wrap(buf, pos)
   123  	pos += m.i1.Wrap(buf, pos)
   124  	pos += m.pad.Wrap(buf, pos, 64, 64)
   125  	pos += m.i2.Wrap(buf, pos)
   126  	pos += m.pad2.Wrap(buf, pos, 128, 64)
   127  
   128  	m.SetSize(pos - offset)
   129  	return m
   130  }
   131  
   132  func (f *FlyweightTestSuite) TestPaddingWrap() {
   133  	buf := atomic.MakeBuffer(make([]byte, 256), 256)
   134  
   135  	var fw PaddedFly
   136  	fw.Wrap(buf, 0)
   137  
   138  	f.Assert().Equal(fw.Size(), 192)
   139  }
   140  
   141  // Another regression test: Copy of PaddedFly, plus a tiny extra padding that should not be negative.
   142  type MorePaddedFly struct {
   143  	FWBase
   144  
   145  	l1   Int64Field
   146  	i1   Int32Field
   147  	pad  Padding
   148  	i2   Int32Field
   149  	pad2 Padding
   150  	i3   Int32Field
   151  	pad3 Padding
   152  }
   153  
   154  func (m *MorePaddedFly) Wrap(buf *atomic.Buffer, offset int) Flyweight {
   155  	pos := offset
   156  	pos += m.l1.Wrap(buf, pos)
   157  	pos += m.i1.Wrap(buf, pos)
   158  	pos += m.pad.Wrap(buf, pos, 64, 64)
   159  	pos += m.i2.Wrap(buf, pos)
   160  	pos += m.pad2.Wrap(buf, pos, 128, 64)
   161  	pos += m.i3.Wrap(buf, pos)
   162  	pos += m.pad3.Wrap(buf, pos, 1, 64)
   163  
   164  	m.SetSize(pos - offset)
   165  	return m
   166  }
   167  
   168  func (f *FlyweightTestSuite) TestMinimumPaddingWrap() {
   169  	buf := atomic.MakeBuffer(make([]byte, 512), 512)
   170  
   171  	var fw MorePaddedFly
   172  	fw.Wrap(buf, 0)
   173  
   174  	f.Assert().Equal(fw.Size(), 256)
   175  }
   176  
   177  func (f *FlyweightTestSuite) TestLengthAndRawDataFieldCopyString() {
   178  	testString := "Talos loves Aeron"
   179  	buf := atomic.MakeBuffer(make([]byte, 128), 128)
   180  	field := LengthAndRawDataField{}
   181  	field.Wrap(buf, 0)
   182  	field.CopyString(testString)
   183  	f.Require().Equal(field.GetAsASCII(), testString)
   184  	f.Require().Equal(field.GetAsBuffer().GetBytesArray(0, int32(len(testString))), []byte(testString))
   185  	f.Require().EqualValues(field.Length(), len(testString))
   186  }
   187  
   188  func (f *FlyweightTestSuite) TestLengthAndRawDataFieldCopyBytes() {
   189  	testString := "Talos loves Aeron"
   190  	buf := atomic.MakeBuffer(make([]byte, 128), 128)
   191  	field := LengthAndRawDataField{}
   192  	field.Wrap(buf, 0)
   193  	field.CopyBuffer(atomic.MakeBuffer([]byte(testString)), 0, int32(len(testString)))
   194  	f.Require().Equal(field.GetAsASCII(), testString)
   195  	f.Require().Equal(field.GetAsBuffer().GetBytesArray(0, int32(len(testString))), []byte(testString))
   196  	f.Require().EqualValues(field.Length(), len(testString))
   197  }
   198  
   199  func (f *FlyweightTestSuite) TestLengthAndRawDataFieldGetFields() {
   200  	testString := "Talos loves Aeron"
   201  	bufferBytes := append([]byte{byte(len(testString)), 0, 0, 0},
   202  		[]byte(testString)...)
   203  	buf := atomic.MakeBuffer(bufferBytes)
   204  	field := LengthAndRawDataField{}
   205  	f.Require().Equal(field.Wrap(buf, 0), len(testString)+4)
   206  	f.Require().Equal(field.GetAsASCII(), testString)
   207  	f.Require().Equal(field.GetAsBuffer().GetBytesArray(0, int32(len(testString))), []byte(testString))
   208  }
   209  
   210  func TestFlyweight(t *testing.T) {
   211  	suite.Run(t, &FlyweightAlignmentTestSuite{shouldAlign: true})
   212  	suite.Run(t, &FlyweightAlignmentTestSuite{shouldAlign: false})
   213  	suite.Run(t, new(FlyweightTestSuite))
   214  }