github.com/GuanceCloud/cliutils@v1.1.21/diskcache/get_test.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  package diskcache
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	T "testing"
    13  	"time"
    14  
    15  	"github.com/GuanceCloud/cliutils/metrics"
    16  	"github.com/prometheus/client_golang/prometheus"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  func TestGetPut(t *T.T) {
    22  	testDir := t.TempDir()
    23  
    24  	err := os.MkdirAll(testDir, 0o755)
    25  	assert.NoError(t, err)
    26  
    27  	dq, err := Open(WithPath(testDir), WithCapacity(1<<30))
    28  	assert.NoError(t, err)
    29  
    30  	assert.NoError(t, dq.Put([]byte("hello message-1")))
    31  
    32  	for {
    33  		if err := dq.Get(func(msg []byte) error {
    34  			t.Logf("get message: %q\n", string(msg))
    35  			return nil
    36  		}); err != nil {
    37  			t.Log(time.Now().Format(time.RFC3339Nano), " fail to get message: ", err)
    38  			time.Sleep(time.Second * 1)
    39  		} else {
    40  			break
    41  		}
    42  	}
    43  
    44  	assert.NoError(t, dq.Put([]byte("hello message-2")))
    45  
    46  	ok := false
    47  
    48  	for i := 0; i < 10; i++ {
    49  		if err := dq.Get(func(msg []byte) error {
    50  			t.Logf("get message: %q\n", string(msg))
    51  			ok = true
    52  			return nil
    53  		}); err != nil {
    54  			t.Log(time.Now().Format(time.RFC3339Nano), " fail to get message: ", err)
    55  			time.Sleep(time.Second * 1)
    56  		} else {
    57  			break
    58  		}
    59  	}
    60  
    61  	assert.True(t, ok, "expected consume 1 message in 10 seconds, but got no message")
    62  
    63  	assert.NoError(t, dq.Close())
    64  }
    65  
    66  func TestDropInvalidDataFile(t *T.T) {
    67  	t.Run(`get-on-0bytes-data-file`, func(t *T.T) {
    68  		p := t.TempDir()
    69  		c, err := Open(WithPath(p))
    70  		require.NoError(t, err)
    71  
    72  		// put some data and rotate 10 datafiles
    73  		data := make([]byte, 100)
    74  		for i := 0; i < 10; i++ {
    75  			assert.NoError(t, c.Put(data))
    76  			assert.NoError(t, c.rotate())
    77  
    78  			// destroy the datafile
    79  			if i%2 == 0 {
    80  				assert.NoError(t, os.Truncate(c.dataFiles[i], 0))
    81  			}
    82  		}
    83  
    84  		assert.Len(t, c.dataFiles, 10)
    85  
    86  		for {
    87  			err := c.Get(func(get []byte) error {
    88  				// switch to 2nd file
    89  				assert.Equal(t, data, get)
    90  				return nil
    91  			})
    92  			if err != nil {
    93  				require.ErrorIs(t, err, ErrEOF)
    94  				break
    95  			}
    96  		}
    97  
    98  		reg := prometheus.NewRegistry()
    99  		register(reg)
   100  		mfs, err := reg.Gather()
   101  		require.NoError(t, err)
   102  
   103  		assert.Equalf(t, float64(5),
   104  			metrics.GetMetricOnLabels(mfs,
   105  				"diskcache_dropped_total",
   106  				c.path,
   107  				reasonBadDataFile,
   108  			).GetCounter().GetValue(),
   109  			"got metrics\n%s", metrics.MetricFamily2Text(mfs))
   110  	})
   111  }
   112  
   113  func TestFallbackOnError(t *T.T) {
   114  	t.Run(`get-erro-on-EOF`, func(t *T.T) {
   115  		p := t.TempDir()
   116  		c, err := Open(WithPath(p))
   117  		require.NoError(t, err)
   118  
   119  		// put some data
   120  		data := make([]byte, 100)
   121  		assert.NoError(t, c.Put(data))
   122  
   123  		assert.NoError(t, c.rotate())
   124  
   125  		require.NoError(t, c.Get(func(_ []byte) error {
   126  			return nil // ignore the data
   127  		}))
   128  
   129  		err = c.Get(func(_ []byte) error {
   130  			assert.True(t, 1 == 2) // should not been here
   131  			return nil
   132  		})
   133  
   134  		assert.ErrorIs(t, err, ErrEOF)
   135  		t.Logf("get: %s", err)
   136  
   137  		if errors.Is(err, ErrEOF) {
   138  			t.Logf("we should ignore the error")
   139  		}
   140  	})
   141  
   142  	t.Run(`fallback-on-error`, func(t *T.T) {
   143  		ResetMetrics()
   144  
   145  		p := t.TempDir()
   146  		c, err := Open(WithPath(p))
   147  		assert.NoError(t, err)
   148  
   149  		data := make([]byte, 100)
   150  		assert.NoError(t, c.Put(data))
   151  
   152  		assert.NoError(t, c.rotate())
   153  
   154  		// should get error when callback fail
   155  		require.Error(t, c.Get(func(_ []byte) error {
   156  			return fmt.Errorf("get error")
   157  		}))
   158  
   159  		assert.Equal(t, int64(0), c.pos.Seek)
   160  
   161  		// should no error when callback ok
   162  		assert.NoError(t, c.Get(func(x []byte) error {
   163  			assert.Equal(t, data, x)
   164  			return nil
   165  		}))
   166  
   167  		reg := prometheus.NewRegistry()
   168  		register(reg)
   169  		mfs, err := reg.Gather()
   170  		require.NoError(t, err)
   171  
   172  		assert.Equalf(t, float64(1),
   173  			metrics.GetMetricOnLabels(mfs, "diskcache_seek_back_total", c.path).GetCounter().GetValue(),
   174  			"got metrics\n%s", metrics.MetricFamily2Text(mfs))
   175  
   176  		t.Cleanup(func() {
   177  			ResetMetrics()
   178  		})
   179  	})
   180  
   181  	t.Run(`fallback-on-eof-error`, func(t *T.T) {
   182  		p := t.TempDir()
   183  		c, err := Open(WithPath(p))
   184  		assert.NoError(t, err)
   185  
   186  		// while on EOF, Fn error ignored
   187  		assert.ErrorIs(t, c.Get(func(_ []byte) error {
   188  			return fmt.Errorf("get error")
   189  		}), ErrEOF)
   190  
   191  		// still got EOF
   192  		assert.ErrorIs(t, c.Get(func(x []byte) error {
   193  			return nil
   194  		}), ErrEOF)
   195  	})
   196  
   197  	t.Run(`no-fallback-on-error`, func(t *T.T) {
   198  		p := t.TempDir()
   199  		c, err := Open(WithPath(p), WithNoFallbackOnError(true))
   200  		assert.NoError(t, err)
   201  
   202  		data := make([]byte, 100)
   203  		assert.NoError(t, c.Put(data))
   204  
   205  		assert.NoError(t, c.rotate())
   206  
   207  		c.Get(func(_ []byte) error {
   208  			return fmt.Errorf("get error")
   209  		})
   210  
   211  		assert.ErrorIs(t, c.Get(func(x []byte) error {
   212  			return nil
   213  		}), ErrEOF)
   214  	})
   215  }
   216  
   217  func TestPutGet(t *T.T) {
   218  	t.Run(`clean-pos-on-eof`, func(t *T.T) {
   219  		reg := prometheus.NewRegistry()
   220  		register(reg)
   221  
   222  		p := t.TempDir()
   223  		c, err := Open(WithPath(p))
   224  		assert.NoError(t, err)
   225  
   226  		data := make([]byte, 100)
   227  		if err := c.Put(data); err != nil {
   228  			t.Error(err)
   229  		}
   230  
   231  		assert.NoError(t, c.rotate())
   232  		assert.NoError(t, c.Get(func(data []byte) error { return nil }))
   233  		assert.Error(t, c.Get(func(data []byte) error { return nil })) // EOF
   234  
   235  		pos, err := posFromFile(c.pos.fname)
   236  		assert.NoError(t, err)
   237  
   238  		t.Logf("pos: %s", pos)
   239  
   240  		mfs, err := reg.Gather()
   241  		require.NoError(t, err)
   242  
   243  		t.Logf("\n%s", metrics.MetricFamily2Text(mfs))
   244  
   245  		t.Cleanup(func() {
   246  			c.Close()
   247  			ResetMetrics()
   248  		})
   249  	})
   250  
   251  	t.Run("put-get", func(t *T.T) {
   252  		reg := prometheus.NewRegistry()
   253  		register(reg)
   254  
   255  		p := t.TempDir()
   256  		c, err := Open(WithPath(p))
   257  		assert.NoError(t, err)
   258  
   259  		str := "hello world"
   260  		if err := c.Put([]byte(str)); err != nil {
   261  			t.Error(err)
   262  		}
   263  
   264  		assert.Equal(t, int64(len(str)+dataHeaderLen), c.curBatchSize)
   265  
   266  		if err := c.Get(func(data []byte) error {
   267  			t.Logf("get: %s", string(data))
   268  			return nil
   269  		}); err != nil {
   270  			t.Logf("get: %s", err)
   271  		}
   272  
   273  		mfs, err := reg.Gather()
   274  		require.NoError(t, err)
   275  		t.Logf("\n%s", metrics.MetricFamily2Text(mfs))
   276  
   277  		t.Cleanup(func() {
   278  			c.Close()
   279  			os.RemoveAll(p)
   280  		})
   281  	})
   282  
   283  	t.Run(`get-without-pos`, func(t *T.T) {
   284  		reg := prometheus.NewRegistry()
   285  		register(reg)
   286  
   287  		p := t.TempDir()
   288  
   289  		kbdata := make([]byte, 1024)
   290  
   291  		c, err := Open(WithPath(p), WithNoPos(true))
   292  		assert.NoError(t, err)
   293  
   294  		for i := 0; i < 10; i++ { // write 10kb
   295  			require.NoError(t, c.Put(kbdata), "cache: %s", c)
   296  		}
   297  
   298  		// force rotate
   299  		assert.NoError(t, c.rotate())
   300  
   301  		// read 2 cache
   302  		assert.NoError(t, c.Get(func(data []byte) error {
   303  			assert.Len(t, data, len(kbdata))
   304  			return nil
   305  		}))
   306  
   307  		assert.NoError(t, c.Get(func(data []byte) error {
   308  			assert.Len(t, data, len(kbdata))
   309  			return nil
   310  		}))
   311  
   312  		// close the cache for next re-Open()
   313  		assert.NoError(t, c.Close())
   314  
   315  		mfs, err := reg.Gather()
   316  		require.NoError(t, err)
   317  		t.Logf("\n%s", metrics.MetricFamily2Text(mfs))
   318  
   319  		c2, err := Open(WithPath(p), WithNoPos(true))
   320  		assert.NoError(t, err)
   321  		defer c2.Close()
   322  
   323  		require.Lenf(t, c2.dataFiles, 1, "cache: %s", c2)
   324  
   325  		var ncached int
   326  		for {
   327  			if err := c2.Get(func(_ []byte) error {
   328  				ncached++
   329  				return nil
   330  			}); err != nil {
   331  				if errors.Is(err, ErrEOF) {
   332  					t.Logf("cache EOF")
   333  					break
   334  				} else {
   335  					assert.NoError(t, err)
   336  				}
   337  			}
   338  		}
   339  
   340  		mfs, err = reg.Gather()
   341  		require.NoError(t, err)
   342  		t.Logf("\n%s", metrics.MetricFamily2Text(mfs))
   343  
   344  		// without .pos, still got 10 cache
   345  		assert.Equal(t, 10, ncached, "cache: %s", c2)
   346  	})
   347  
   348  	t.Run(`get-with-pos`, func(t *T.T) {
   349  		p := t.TempDir()
   350  
   351  		kbdata := make([]byte, 1024)
   352  
   353  		c, err := Open(WithPath(p),
   354  			WithCapacity(int64(len(kbdata)*10)),
   355  			WithBatchSize(int64(len(kbdata)*2)))
   356  		assert.NoError(t, err)
   357  
   358  		for i := 0; i < 10; i++ { // write 10kb
   359  			require.NoError(t, c.Put(kbdata), "cache: %s", c)
   360  		}
   361  
   362  		// create a read pos
   363  		assert.NoError(t, c.Get(func(data []byte) error {
   364  			assert.Len(t, data, len(kbdata))
   365  			return nil
   366  		}))
   367  		assert.Equal(t, int64(len(kbdata)+dataHeaderLen), c.pos.Seek)
   368  		assert.NoError(t, c.Close())
   369  
   370  		_, err = os.Stat(c.pos.fname)
   371  		require.NoError(t, err)
   372  
   373  		// reopen the cache
   374  		c2, err := Open(WithPath(p),
   375  			WithCapacity(int64(len(kbdata)*10)),
   376  			WithBatchSize(int64(len(kbdata)*2)))
   377  		require.NoError(t, err, "get error: %s", err)
   378  		assert.Equal(t, c2.pos.Seek, int64(len(kbdata)+dataHeaderLen))
   379  
   380  		assert.NoError(t, c2.Get(func(data []byte) error {
   381  			assert.Len(t, data, len(kbdata))
   382  			return nil
   383  		}))
   384  		assert.Equal(t, int64(len(kbdata)+dataHeaderLen), c.pos.Seek)
   385  		assert.NoError(t, c2.Close())
   386  		assert.Equal(t, c2.pos.Seek, int64(2*(len(kbdata)+dataHeaderLen)))
   387  
   388  		t.Cleanup(func() {
   389  			c2.Close()
   390  			os.RemoveAll(p)
   391  		})
   392  	})
   393  }