From 0edcc437e1e6cbd71a1ddf83c6741f4ac28c18cb Mon Sep 17 00:00:00 2001 From: sunshineplan Date: Wed, 14 Aug 2024 16:01:34 +0800 Subject: [PATCH] Update go 1.23 (#74) --- cache/cache.go | 4 +- cache/cache_test.go | 4 +- cache/map.go | 15 +- cache/map_reference_test.go | 274 --------------------------------- cache/map_test.go | 202 ------------------------ csv/convert_test.go | 4 +- csv/reader.go | 7 +- csv/reader_test.go | 3 +- csv/writer.go | 3 +- csv/writer_test.go | 4 +- go.mod | 2 +- loadbalance/roundrobin_test.go | 8 +- slice/slice_test.go | 4 +- txt/reader.go | 33 +++- txt/reader_test.go | 12 +- 15 files changed, 62 insertions(+), 517 deletions(-) delete mode 100644 cache/map_reference_test.go delete mode 100644 cache/map_test.go diff --git a/cache/cache.go b/cache/cache.go index 9e77118..922ed59 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -124,8 +124,8 @@ func (c *Cache[Key, Value]) Swap(key Key, value Value) (previous Value, loaded b return } -// Empty deletes all values in cache. -func (c *Cache[Key, Value]) Empty() { +// Clear deletes all values in cache. +func (c *Cache[Key, Value]) Clear() { c.m.Range(func(k Key, i *item[Value]) bool { c.m.Delete(k) i.Lock() diff --git a/cache/cache_test.go b/cache/cache_test.go index 6f77e5a..7391bb8 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -39,7 +39,7 @@ func TestEmpty(t *testing.T) { t.Error("expected ok; got not") } } - cache.Empty() + cache.Clear() for _, i := range []string{"a", "b", "c"} { if _, ok := cache.Get(i); ok { t.Error("expected not ok; got ok") @@ -49,7 +49,7 @@ func TestEmpty(t *testing.T) { func TestRenew(t *testing.T) { cache := New[string, string](true) - defer cache.Empty() + defer cache.Clear() expire := make(chan struct{}) cache.Set("renew", "old", 2*time.Second, func() (string, error) { defer func() { close(expire) }() diff --git a/cache/map.go b/cache/map.go index b422af9..b6bb1ee 100644 --- a/cache/map.go +++ b/cache/map.go @@ -26,13 +26,22 @@ func (m *Map[Key, Value]) Store(key Key, value Value) { m.m.Store(key, value) } +// Clear deletes all the entries, resulting in an empty Map. +func (m *Map[Key, Value]) Clear() { + m.m.Clear() +} + // LoadOrStore returns the existing value for the key if present. // Otherwise, it stores and returns the given value. // The loaded result is true if the value was loaded, false if stored. func (m *Map[Key, Value]) LoadOrStore(key Key, value Value) (actual Value, loaded bool) { var v any - if v, loaded = m.m.LoadOrStore(key, value); v != nil { - actual = v.(Value) + if v, loaded = m.m.LoadOrStore(key, value); loaded { + if v != nil { + actual = v.(Value) + } + } else { + actual = value } return } @@ -92,10 +101,10 @@ func (m *Map[Key, Value]) CompareAndDelete(key Key, old Value) (deleted bool) { func (m *Map[Key, Value]) Range(f func(Key, Value) bool) { m.m.Range(func(key, value any) bool { var k Key + var v Value if key != nil { k = key.(Key) } - var v Value if value != nil { v = value.(Value) } diff --git a/cache/map_reference_test.go b/cache/map_reference_test.go deleted file mode 100644 index 9c7f08f..0000000 --- a/cache/map_reference_test.go +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cache_test - -import ( - "sync" - "sync/atomic" - - "github.com/sunshineplan/utils/cache" -) - -// This file contains reference map implementations for unit-tests. - -// mapInterface is the interface Map implements. -type mapInterface interface { - Load(any) (any, bool) - Store(key, value any) - LoadOrStore(key, value any) (actual any, loaded bool) - LoadAndDelete(key any) (value any, loaded bool) - Delete(any) - Swap(key, value any) (previous any, loaded bool) - CompareAndSwap(key, old, new any) (swapped bool) - CompareAndDelete(key, old any) (deleted bool) - Range(func(key, value any) (shouldContinue bool)) -} - -var ( - _ mapInterface = &cache.Map[any, any]{} - _ mapInterface = &RWMutexMap{} - _ mapInterface = &DeepCopyMap{} -) - -// RWMutexMap is an implementation of mapInterface using a sync.RWMutex. -type RWMutexMap struct { - mu sync.RWMutex - dirty map[any]any -} - -func (m *RWMutexMap) Load(key any) (value any, ok bool) { - m.mu.RLock() - value, ok = m.dirty[key] - m.mu.RUnlock() - return -} - -func (m *RWMutexMap) Store(key, value any) { - m.mu.Lock() - if m.dirty == nil { - m.dirty = make(map[any]any) - } - m.dirty[key] = value - m.mu.Unlock() -} - -func (m *RWMutexMap) LoadOrStore(key, value any) (actual any, loaded bool) { - m.mu.Lock() - actual, loaded = m.dirty[key] - if !loaded { - actual = value - if m.dirty == nil { - m.dirty = make(map[any]any) - } - m.dirty[key] = value - } - m.mu.Unlock() - return actual, loaded -} - -func (m *RWMutexMap) Swap(key, value any) (previous any, loaded bool) { - m.mu.Lock() - if m.dirty == nil { - m.dirty = make(map[any]any) - } - - previous, loaded = m.dirty[key] - m.dirty[key] = value - m.mu.Unlock() - return -} - -func (m *RWMutexMap) LoadAndDelete(key any) (value any, loaded bool) { - m.mu.Lock() - value, loaded = m.dirty[key] - if !loaded { - m.mu.Unlock() - return nil, false - } - delete(m.dirty, key) - m.mu.Unlock() - return value, loaded -} - -func (m *RWMutexMap) Delete(key any) { - m.mu.Lock() - delete(m.dirty, key) - m.mu.Unlock() -} - -func (m *RWMutexMap) CompareAndSwap(key, old, new any) (swapped bool) { - m.mu.Lock() - defer m.mu.Unlock() - if m.dirty == nil { - return false - } - - value, loaded := m.dirty[key] - if loaded && value == old { - m.dirty[key] = new - return true - } - return false -} - -func (m *RWMutexMap) CompareAndDelete(key, old any) (deleted bool) { - m.mu.Lock() - defer m.mu.Unlock() - if m.dirty == nil { - return false - } - - value, loaded := m.dirty[key] - if loaded && value == old { - delete(m.dirty, key) - return true - } - return false -} - -func (m *RWMutexMap) Range(f func(key, value any) (shouldContinue bool)) { - m.mu.RLock() - keys := make([]any, 0, len(m.dirty)) - for k := range m.dirty { - keys = append(keys, k) - } - m.mu.RUnlock() - - for _, k := range keys { - v, ok := m.Load(k) - if !ok { - continue - } - if !f(k, v) { - break - } - } -} - -// DeepCopyMap is an implementation of mapInterface using a Mutex and -// atomic.Value. It makes deep copies of the map on every write to avoid -// acquiring the Mutex in Load. -type DeepCopyMap struct { - mu sync.Mutex - clean atomic.Value -} - -func (m *DeepCopyMap) Load(key any) (value any, ok bool) { - clean, _ := m.clean.Load().(map[any]any) - value, ok = clean[key] - return value, ok -} - -func (m *DeepCopyMap) Store(key, value any) { - m.mu.Lock() - dirty := m.dirty() - dirty[key] = value - m.clean.Store(dirty) - m.mu.Unlock() -} - -func (m *DeepCopyMap) LoadOrStore(key, value any) (actual any, loaded bool) { - clean, _ := m.clean.Load().(map[any]any) - actual, loaded = clean[key] - if loaded { - return actual, loaded - } - - m.mu.Lock() - // Reload clean in case it changed while we were waiting on m.mu. - clean, _ = m.clean.Load().(map[any]any) - actual, loaded = clean[key] - if !loaded { - dirty := m.dirty() - dirty[key] = value - actual = value - m.clean.Store(dirty) - } - m.mu.Unlock() - return actual, loaded -} - -func (m *DeepCopyMap) Swap(key, value any) (previous any, loaded bool) { - m.mu.Lock() - dirty := m.dirty() - previous, loaded = dirty[key] - dirty[key] = value - m.clean.Store(dirty) - m.mu.Unlock() - return -} - -func (m *DeepCopyMap) LoadAndDelete(key any) (value any, loaded bool) { - m.mu.Lock() - dirty := m.dirty() - value, loaded = dirty[key] - delete(dirty, key) - m.clean.Store(dirty) - m.mu.Unlock() - return -} - -func (m *DeepCopyMap) Delete(key any) { - m.mu.Lock() - dirty := m.dirty() - delete(dirty, key) - m.clean.Store(dirty) - m.mu.Unlock() -} - -func (m *DeepCopyMap) CompareAndSwap(key, old, new any) (swapped bool) { - clean, _ := m.clean.Load().(map[any]any) - if previous, ok := clean[key]; !ok || previous != old { - return false - } - - m.mu.Lock() - defer m.mu.Unlock() - dirty := m.dirty() - value, loaded := dirty[key] - if loaded && value == old { - dirty[key] = new - m.clean.Store(dirty) - return true - } - return false -} - -func (m *DeepCopyMap) CompareAndDelete(key, old any) (deleted bool) { - clean, _ := m.clean.Load().(map[any]any) - if previous, ok := clean[key]; !ok || previous != old { - return false - } - - m.mu.Lock() - defer m.mu.Unlock() - - dirty := m.dirty() - value, loaded := dirty[key] - if loaded && value == old { - delete(dirty, key) - m.clean.Store(dirty) - return true - } - return false -} - -func (m *DeepCopyMap) Range(f func(key, value any) (shouldContinue bool)) { - clean, _ := m.clean.Load().(map[any]any) - for k, v := range clean { - if !f(k, v) { - break - } - } -} - -func (m *DeepCopyMap) dirty() map[any]any { - clean, _ := m.clean.Load().(map[any]any) - dirty := make(map[any]any, len(clean)+1) - for k, v := range clean { - dirty[k] = v - } - return dirty -} diff --git a/cache/map_test.go b/cache/map_test.go deleted file mode 100644 index cc8ace0..0000000 --- a/cache/map_test.go +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cache_test - -import ( - "math/rand" - "reflect" - "runtime" - "sync" - "testing" - "testing/quick" - - "github.com/sunshineplan/utils/cache" -) - -type mapOp string - -const ( - opLoad = mapOp("Load") - opStore = mapOp("Store") - opLoadOrStore = mapOp("LoadOrStore") - opLoadAndDelete = mapOp("LoadAndDelete") - opDelete = mapOp("Delete") - opSwap = mapOp("Swap") - opCompareAndSwap = mapOp("CompareAndSwap") - opCompareAndDelete = mapOp("CompareAndDelete") -) - -var mapOps = [...]mapOp{ - opLoad, - opStore, - opLoadOrStore, - opLoadAndDelete, - opDelete, - opSwap, - opCompareAndSwap, - opCompareAndDelete, -} - -// mapCall is a quick.Generator for calls on mapInterface. -type mapCall struct { - op mapOp - k, v any -} - -func (c mapCall) apply(m mapInterface) (any, bool) { - switch c.op { - case opLoad: - return m.Load(c.k) - case opStore: - m.Store(c.k, c.v) - return nil, false - case opLoadOrStore: - return m.LoadOrStore(c.k, c.v) - case opLoadAndDelete: - return m.LoadAndDelete(c.k) - case opDelete: - m.Delete(c.k) - return nil, false - case opSwap: - return m.Swap(c.k, c.v) - case opCompareAndSwap: - if m.CompareAndSwap(c.k, c.v, rand.Int()) { - m.Delete(c.k) - return c.v, true - } - return nil, false - case opCompareAndDelete: - if m.CompareAndDelete(c.k, c.v) { - if _, ok := m.Load(c.k); !ok { - return nil, true - } - } - return nil, false - default: - panic("invalid mapOp") - } -} - -type mapResult struct { - value any - ok bool -} - -func randValue(r *rand.Rand) any { - b := make([]byte, r.Intn(4)) - for i := range b { - b[i] = 'a' + byte(rand.Intn(26)) - } - return string(b) -} - -func (mapCall) Generate(r *rand.Rand, size int) reflect.Value { - c := mapCall{op: mapOps[rand.Intn(len(mapOps))], k: randValue(r)} - switch c.op { - case opStore, opLoadOrStore: - c.v = randValue(r) - } - return reflect.ValueOf(c) -} - -func applyCalls(m mapInterface, calls []mapCall) (results []mapResult, final map[any]any) { - for _, c := range calls { - v, ok := c.apply(m) - results = append(results, mapResult{v, ok}) - } - - final = make(map[any]any) - m.Range(func(k, v any) bool { - final[k] = v - return true - }) - - return results, final -} - -func applyMap(calls []mapCall) ([]mapResult, map[any]any) { - return applyCalls(new(cache.Map[any, any]), calls) -} - -func applyRWMutexMap(calls []mapCall) ([]mapResult, map[any]any) { - return applyCalls(new(RWMutexMap), calls) -} - -func applyDeepCopyMap(calls []mapCall) ([]mapResult, map[any]any) { - return applyCalls(new(DeepCopyMap), calls) -} - -func TestMapMatchesRWMutex(t *testing.T) { - if err := quick.CheckEqual(applyMap, applyRWMutexMap, nil); err != nil { - t.Error(err) - } -} - -func TestMapMatchesDeepCopy(t *testing.T) { - if err := quick.CheckEqual(applyMap, applyDeepCopyMap, nil); err != nil { - t.Error(err) - } -} - -func TestConcurrentRange(t *testing.T) { - const mapSize = 1 << 10 - - m := new(cache.Map[any, any]) - for n := int64(1); n <= mapSize; n++ { - m.Store(n, int64(n)) - } - - done := make(chan struct{}) - var wg sync.WaitGroup - defer func() { - close(done) - wg.Wait() - }() - for g := int64(runtime.GOMAXPROCS(0)); g > 0; g-- { - r := rand.New(rand.NewSource(g)) - wg.Add(1) - go func(g int64) { - defer wg.Done() - for i := int64(0); ; i++ { - select { - case <-done: - return - default: - } - for n := int64(1); n < mapSize; n++ { - if r.Int63n(mapSize) == 0 { - m.Store(n, n*i*g) - } else { - m.Load(n) - } - } - } - }(g) - } - - iters := 1 << 10 - if testing.Short() { - iters = 16 - } - for n := iters; n > 0; n-- { - seen := make(map[int64]bool, mapSize) - - m.Range(func(ki, vi any) bool { - k, v := ki.(int64), vi.(int64) - if v%k != 0 { - t.Fatalf("while Storing multiples of %v, Range saw value %v", k, v) - } - if seen[k] { - t.Fatalf("Range visited key %v twice", k) - } - seen[k] = true - return true - }) - - if len(seen) != mapSize { - t.Fatalf("Range visited %v elements of %v-element Map", len(seen), mapSize) - } - } -} diff --git a/csv/convert_test.go b/csv/convert_test.go index 7016c49..f5e0186 100644 --- a/csv/convert_test.go +++ b/csv/convert_test.go @@ -2,7 +2,7 @@ package csv import ( "encoding/json" - "reflect" + "slices" "strings" "testing" ) @@ -41,7 +41,7 @@ func TestConvert(t *testing.T) { if err := setCell(&a, "[1,2]"); err != nil { t.Fatal(err) } - if expect := []int{1, 2}; !reflect.DeepEqual(expect, a) { + if expect := []int{1, 2}; !slices.Equal(expect, a) { t.Errorf("expected %v; got %v", expect, a) } diff --git a/csv/reader.go b/csv/reader.go index 22a3637..41479b3 100644 --- a/csv/reader.go +++ b/csv/reader.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "strings" "sync" ) @@ -51,11 +52,7 @@ func (r *Reader) Read() (record []string, err error) { if err == nil { r.once.Do(func() { if len(record) > 0 { - if s := record[0]; len(s) >= 3 { - if b := s[:3]; b == string(utf8bom) { - record[0] = s[3:] - } - } + record[0] = strings.TrimPrefix(record[0], string(utf8bom)) } }) } diff --git a/csv/reader_test.go b/csv/reader_test.go index 947ad5d..44895f5 100644 --- a/csv/reader_test.go +++ b/csv/reader_test.go @@ -3,6 +3,7 @@ package csv import ( "encoding/json" "reflect" + "slices" "strings" "testing" ) @@ -19,7 +20,7 @@ a,1,"[1,2]" b,2,"[3,4]" ` r := NewReader(strings.NewReader(csv), true) - if expect := []string{"A", "B", "C"}; !reflect.DeepEqual(expect, r.fields) { + if expect := []string{"A", "B", "C"}; !slices.Equal(expect, r.fields) { t.Errorf("expected %v; got %v", expect, r.fields) } diff --git a/csv/writer.go b/csv/writer.go index 5d2b011..a2a3236 100644 --- a/csv/writer.go +++ b/csv/writer.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "reflect" + "slices" ) var utf8bom = []byte{0xEF, 0xBB, 0xBF} @@ -185,7 +186,7 @@ func (w *Writer) Write(record any) error { return fmt.Errorf("not support record format: %s", v.Kind()) } - if reflect.DeepEqual(r, make([]string, len(w.fields))) { + if slices.Equal(r, make([]string, len(w.fields))) { return nil } diff --git a/csv/writer_test.go b/csv/writer_test.go index da64e85..14305de 100644 --- a/csv/writer_test.go +++ b/csv/writer_test.go @@ -3,7 +3,7 @@ package csv import ( "bytes" "io" - "reflect" + "slices" "testing" ) @@ -21,7 +21,7 @@ func TestWriteFields(t *testing.T) { }{}); err != nil { t.Error(err) } else { - if expect := []field{{"A", ""}, {"B", "b"}}; !reflect.DeepEqual(expect, w.fields) { + if expect := []field{{"A", ""}, {"B", "b"}}; !slices.Equal(expect, w.fields) { t.Errorf("expected %v; got %v", expect, w.fields) } } diff --git a/go.mod b/go.mod index 1d1095c..b0c0ef7 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/sunshineplan/utils -go 1.22 +go 1.23 diff --git a/loadbalance/roundrobin_test.go b/loadbalance/roundrobin_test.go index ff250cb..5671e4c 100644 --- a/loadbalance/roundrobin_test.go +++ b/loadbalance/roundrobin_test.go @@ -1,7 +1,7 @@ package loadbalance import ( - "reflect" + "slices" "testing" ) @@ -11,7 +11,7 @@ func TestRoundRobin(t *testing.T) { for range 6 { res = append(res, r1.Next()) } - if expect := []string{"a", "b", "c", "a", "b", "c"}; !reflect.DeepEqual(res, expect) { + if expect := []string{"a", "b", "c", "a", "b", "c"}; !slices.Equal(res, expect) { t.Errorf("want %v, got %v", expect, res) } res = nil @@ -20,7 +20,7 @@ func TestRoundRobin(t *testing.T) { for range 12 { res = append(res, r2.Next()) } - if expect := []string{"a", "a", "b", "c", "a", "a", "b", "c", "a", "a", "b", "c"}; !reflect.DeepEqual(res, expect) { + if expect := []string{"a", "a", "b", "c", "a", "a", "b", "c", "a", "a", "b", "c"}; !slices.Equal(res, expect) { t.Errorf("want %v, got %v", expect, res) } res = nil @@ -29,7 +29,7 @@ func TestRoundRobin(t *testing.T) { for range 7 { res = append(res, r1.Next()) } - if expect := []string{"a", "a", "a", "b", "c", "b", "c"}; !reflect.DeepEqual(res, expect) { + if expect := []string{"a", "a", "a", "b", "c", "b", "c"}; !slices.Equal(res, expect) { t.Errorf("want %v, got %v", expect, res) } } diff --git a/slice/slice_test.go b/slice/slice_test.go index 3c57409..2f3d221 100644 --- a/slice/slice_test.go +++ b/slice/slice_test.go @@ -1,13 +1,13 @@ package slice import ( - "reflect" + "slices" "testing" ) func testDeduplicate[E comparable](t *testing.T, s1, s2 []E) { res := Deduplicate(s1) - if !reflect.DeepEqual(res, s2) { + if !slices.Equal(res, s2) { t.Errorf("expected %#v; got %#v", s2, res) } } diff --git a/txt/reader.go b/txt/reader.go index f9fa0f9..f80cd70 100644 --- a/txt/reader.go +++ b/txt/reader.go @@ -3,18 +3,35 @@ package txt import ( "bufio" "io" + "iter" "os" ) -// ReadAll reads all contents from r. -func ReadAll(r io.Reader) ([]string, error) { - var content []string - scanner := bufio.NewScanner(r) - for scanner.Scan() { - content = append(content, scanner.Text()) +type Reader struct { + scanner *bufio.Scanner +} + +func NewReader(r io.Reader) *Reader { + return &Reader{bufio.NewScanner(r)} +} + +func (r *Reader) Iter() iter.Seq[string] { + return func(yield func(string) bool) { + for r.scanner.Scan() { + if !yield(r.scanner.Text()) { + return + } + } } +} - return content, scanner.Err() +// ReadAll reads all contents from r. +func ReadAll(r io.Reader) []string { + var s []string + for i := range NewReader(r).Iter() { + s = append(s, i) + } + return s } // ReadFile reads all contents from file. @@ -25,5 +42,5 @@ func ReadFile(file string) ([]string, error) { } defer f.Close() - return ReadAll(f) + return ReadAll(f), nil } diff --git a/txt/reader_test.go b/txt/reader_test.go index c9fda4b..b9cddb0 100644 --- a/txt/reader_test.go +++ b/txt/reader_test.go @@ -1,7 +1,7 @@ package txt import ( - "reflect" + "slices" "strings" "testing" ) @@ -11,12 +11,8 @@ func TestReader(t *testing.T) { B C ` - content, err := ReadAll(strings.NewReader(txt)) - if err != nil { - t.Fatal(err) - } - - if expect := []string{"A", "B", "C"}; !reflect.DeepEqual(expect, content) { - t.Errorf("expected %v; got %v", expect, content) + res := ReadAll(strings.NewReader(txt)) + if expect := []string{"A", "B", "C"}; !slices.Equal(expect, res) { + t.Errorf("expected %v; got %v", expect, res) } }