Commit cc2715c3 authored by mbaxter's avatar mbaxter Committed by GitHub

cannon: Add more sync tests (#12949)

* cannon: Port go map tests

* cannon: Port pool_test.go

* cannon: Port a few more mutex tests

* cannon: Port waitgroup_test.go

* cannon: Port oncefunc_test.go (in progress)

* cannon: Port atomic_test.go (in progress)

* cannon: Port value_test.go (in progress)

* cannon: Fix atomic tests by using a test mock

* cannon: Fix test fail behavior

* cannon: Move test util to a shared module

* cannon: Use common testutil throughout

* cannon: Fix failing tests

* cannon: Add sanity check test program for test runner utils

* cannon: Add more util tests, fix step counts

* cannon: Rename test util method

* cannon: Fix panic, error handling in testutil

* cannon: Dedupe test running code

* cannon: Simplify testutil interface

* cannon: Mark mt tests as slow

* cannon: Cut debugging code from test

* cannon: Validate gc complete output

* cannon: Synchronize access to bool values
parent c29b2094
...@@ -169,7 +169,7 @@ func SignExtendImmediate(insn uint32) Word { ...@@ -169,7 +169,7 @@ func SignExtendImmediate(insn uint32) Word {
func assertMips64(insn uint32) { func assertMips64(insn uint32) {
if arch.IsMips32 { if arch.IsMips32 {
panic(fmt.Sprintf("invalid instruction: %x", insn)) panic(fmt.Sprintf("invalid instruction: 0x%08x", insn))
} }
} }
...@@ -327,7 +327,7 @@ func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem ...@@ -327,7 +327,7 @@ func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem
assertMips64(insn) assertMips64(insn)
return Word(int64(rt) >> (((insn >> 6) & 0x1f) + 32)) return Word(int64(rt) >> (((insn >> 6) & 0x1f) + 32))
default: default:
panic(fmt.Sprintf("invalid instruction: %x", insn)) panic(fmt.Sprintf("invalid instruction: 0x%08x", insn))
} }
} else { } else {
switch opcode { switch opcode {
......
...@@ -34,20 +34,82 @@ func TestInstrumentedState_Claim(t *testing.T) { ...@@ -34,20 +34,82 @@ func TestInstrumentedState_Claim(t *testing.T) {
testutil.RunVMTest_Claim(t, CreateInitialState, vmFactory, false) testutil.RunVMTest_Claim(t, CreateInitialState, vmFactory, false)
} }
func TestInstrumentedState_UtilsCheck(t *testing.T) {
// Sanity check that test running utilities will return a non-zero exit code on failure
t.Parallel()
cases := []struct {
name string
expectedOutput string
}{
{name: "utilscheck", expectedOutput: "Test failed: ShouldFail"},
{name: "utilscheck2", expectedOutput: "Test failed: ShouldFail (subtest 2)"},
{name: "utilscheck3", expectedOutput: "Test panicked: ShouldFail (panic test)"},
{name: "utilscheck4", expectedOutput: "Test panicked: ShouldFail"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
state, meta := testutil.LoadELFProgram(t, testutil.ProgramPath(c.name), CreateInitialState, false)
oracle := testutil.StaticOracle(t, []byte{})
var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), meta)
for i := 0; i < 1_000_000; i++ {
if us.GetState().GetExited() {
break
}
_, err := us.Step(false)
require.NoError(t, err)
}
t.Logf("Completed in %d steps", state.Step)
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(1), state.ExitCode, "exit with 1")
require.Contains(t, stdOutBuf.String(), c.expectedOutput)
require.NotContains(t, stdOutBuf.String(), "Passed test that should have failed")
require.Equal(t, "", stdErrBuf.String(), "should not print any errors")
})
}
}
func TestInstrumentedState_MultithreadedProgram(t *testing.T) { func TestInstrumentedState_MultithreadedProgram(t *testing.T) {
if os.Getenv("SKIP_SLOW_TESTS") == "true" {
t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled")
}
t.Parallel() t.Parallel()
cases := []struct { cases := []struct {
name string name string
expectedOutput []string expectedOutput []string
programName string programName string
steps int
}{ }{
{ {
name: "wg and chan test", name: "general concurrency test",
expectedOutput: []string{ expectedOutput: []string{
"waitgroup result: 42", "waitgroup result: 42",
"channels result: 1234", "channels result: 1234",
"GC complete!",
},
programName: "mt-general",
steps: 5_000_000,
},
{
name: "atomic test",
expectedOutput: []string{
"Atomic tests passed",
},
programName: "mt-atomic",
steps: 350_000_000,
},
{
name: "waitgroup test",
expectedOutput: []string{
"WaitGroup tests passed",
}, },
programName: "mt-wg", programName: "mt-wg",
steps: 15_000_000,
}, },
{ {
name: "mutex test", name: "mutex test",
...@@ -55,6 +117,7 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) { ...@@ -55,6 +117,7 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) {
"Mutex test passed", "Mutex test passed",
}, },
programName: "mt-mutex", programName: "mt-mutex",
steps: 5_000_000,
}, },
{ {
name: "cond test", name: "cond test",
...@@ -62,6 +125,7 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) { ...@@ -62,6 +125,7 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) {
"Cond test passed", "Cond test passed",
}, },
programName: "mt-cond", programName: "mt-cond",
steps: 5_000_000,
}, },
{ {
name: "rwmutex test", name: "rwmutex test",
...@@ -69,6 +133,7 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) { ...@@ -69,6 +133,7 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) {
"RWMutex test passed", "RWMutex test passed",
}, },
programName: "mt-rwmutex", programName: "mt-rwmutex",
steps: 5_000_000,
}, },
{ {
name: "once test", name: "once test",
...@@ -76,6 +141,15 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) { ...@@ -76,6 +141,15 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) {
"Once test passed", "Once test passed",
}, },
programName: "mt-once", programName: "mt-once",
steps: 5_000_000,
},
{
name: "oncefunc test",
expectedOutput: []string{
"OnceFunc tests passed",
},
programName: "mt-oncefunc",
steps: 15_000_000,
}, },
{ {
name: "map test", name: "map test",
...@@ -83,6 +157,7 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) { ...@@ -83,6 +157,7 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) {
"Map test passed", "Map test passed",
}, },
programName: "mt-map", programName: "mt-map",
steps: 100_000_000,
}, },
{ {
name: "pool test", name: "pool test",
...@@ -90,6 +165,15 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) { ...@@ -90,6 +165,15 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) {
"Pool test passed", "Pool test passed",
}, },
programName: "mt-pool", programName: "mt-pool",
steps: 50_000_000,
},
{
name: "value test",
expectedOutput: []string{
"Value tests passed",
},
programName: "mt-value",
steps: 3_000_000,
}, },
} }
...@@ -97,12 +181,14 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) { ...@@ -97,12 +181,14 @@ func TestInstrumentedState_MultithreadedProgram(t *testing.T) {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
t.Parallel() t.Parallel()
state, _ := testutil.LoadELFProgram(t, testutil.ProgramPath(test.programName), CreateInitialState, false)
state, meta := testutil.LoadELFProgram(t, testutil.ProgramPath(test.programName), CreateInitialState, false)
oracle := testutil.StaticOracle(t, []byte{}) oracle := testutil.StaticOracle(t, []byte{})
var stdOutBuf, stdErrBuf bytes.Buffer var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), nil) us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), meta)
for i := 0; i < 5_000_000; i++ {
for i := 0; i < test.steps; i++ {
if us.GetState().GetExited() { if us.GetState().GetExited() {
break break
} }
......
This diff is collapsed.
module atomic
go 1.22
toolchain go1.22.0
require utils v0.0.0
replace utils => ../../utils
package main
import (
"fmt"
"utils/testutil"
)
func main() {
testutil.RunTest(TestSwapInt32, "TestSwapInt32")
testutil.RunTest(TestSwapInt32Method, "TestSwapInt32Method")
testutil.RunTest(TestSwapUint32, "TestSwapUint32")
testutil.RunTest(TestSwapUint32Method, "TestSwapUint32Method")
testutil.RunTest(TestSwapInt64, "TestSwapInt64")
testutil.RunTest(TestSwapInt64Method, "TestSwapInt64Method")
testutil.RunTest(TestSwapUint64, "TestSwapUint64")
testutil.RunTest(TestSwapUint64Method, "TestSwapUint64Method")
testutil.RunTest(TestSwapUintptr, "TestSwapUintptr")
testutil.RunTest(TestSwapUintptrMethod, "TestSwapUintptrMethod")
testutil.RunTest(TestSwapPointer, "TestSwapPointer")
testutil.RunTest(TestSwapPointerMethod, "TestSwapPointerMethod")
testutil.RunTest(TestAddInt32, "TestAddInt32")
testutil.RunTest(TestAddInt32Method, "TestAddInt32Method")
testutil.RunTest(TestAddUint32, "TestAddUint32")
testutil.RunTest(TestAddUint32Method, "TestAddUint32Method")
testutil.RunTest(TestAddInt64, "TestAddInt64")
testutil.RunTest(TestAddInt64Method, "TestAddInt64Method")
testutil.RunTest(TestAddUint64, "TestAddUint64")
testutil.RunTest(TestAddUint64Method, "TestAddUint64Method")
testutil.RunTest(TestAddUintptr, "TestAddUintptr")
testutil.RunTest(TestAddUintptrMethod, "TestAddUintptrMethod")
testutil.RunTest(TestCompareAndSwapInt32, "TestCompareAndSwapInt32")
testutil.RunTest(TestCompareAndSwapInt32Method, "TestCompareAndSwapInt32Method")
testutil.RunTest(TestCompareAndSwapUint32, "TestCompareAndSwapUint32")
testutil.RunTest(TestCompareAndSwapUint32Method, "TestCompareAndSwapUint32Method")
testutil.RunTest(TestCompareAndSwapInt64, "TestCompareAndSwapInt64")
testutil.RunTest(TestCompareAndSwapInt64Method, "TestCompareAndSwapInt64Method")
testutil.RunTest(TestCompareAndSwapUint64, "TestCompareAndSwapUint64")
testutil.RunTest(TestCompareAndSwapUint64Method, "TestCompareAndSwapUint64Method")
testutil.RunTest(TestCompareAndSwapUintptr, "TestCompareAndSwapUintptr")
testutil.RunTest(TestCompareAndSwapUintptrMethod, "TestCompareAndSwapUintptrMethod")
testutil.RunTest(TestCompareAndSwapPointer, "TestCompareAndSwapPointer")
testutil.RunTest(TestCompareAndSwapPointerMethod, "TestCompareAndSwapPointerMethod")
testutil.RunTest(TestLoadInt32, "TestLoadInt32")
testutil.RunTest(TestLoadInt32Method, "TestLoadInt32Method")
testutil.RunTest(TestLoadUint32, "TestLoadUint32")
testutil.RunTest(TestLoadUint32Method, "TestLoadUint32Method")
testutil.RunTest(TestLoadInt64, "TestLoadInt64")
testutil.RunTest(TestLoadInt64Method, "TestLoadInt64Method")
testutil.RunTest(TestLoadUint64, "TestLoadUint64")
testutil.RunTest(TestLoadUint64Method, "TestLoadUint64Method")
testutil.RunTest(TestLoadUintptr, "TestLoadUintptr")
testutil.RunTest(TestLoadUintptrMethod, "TestLoadUintptrMethod")
testutil.RunTest(TestLoadPointer, "TestLoadPointer")
testutil.RunTest(TestLoadPointerMethod, "TestLoadPointerMethod")
testutil.RunTest(TestStoreInt32, "TestStoreInt32")
testutil.RunTest(TestStoreInt32Method, "TestStoreInt32Method")
testutil.RunTest(TestStoreUint32, "TestStoreUint32")
testutil.RunTest(TestStoreUint32Method, "TestStoreUint32Method")
testutil.RunTest(TestStoreInt64, "TestStoreInt64")
testutil.RunTest(TestStoreInt64Method, "TestStoreInt64Method")
testutil.RunTest(TestStoreUint64, "TestStoreUint64")
testutil.RunTest(TestStoreUint64Method, "TestStoreUint64Method")
testutil.RunTest(TestStoreUintptr, "TestStoreUintptr")
testutil.RunTest(TestStoreUintptrMethod, "TestStoreUintptrMethod")
testutil.RunTest(TestStorePointer, "TestStorePointer")
testutil.RunTest(TestStorePointerMethod, "TestStorePointerMethod")
testutil.RunTest(TestHammer32, "TestHammer32")
testutil.RunTest(TestHammer64, "TestHammer64")
testutil.RunTest(TestAutoAligned64, "TestAutoAligned64")
testutil.RunTest(TestNilDeref, "TestNilDeref")
testutil.RunTest(TestStoreLoadSeqCst32, "TestStoreLoadSeqCst32")
testutil.RunTest(TestStoreLoadSeqCst64, "TestStoreLoadSeqCst64")
testutil.RunTest(TestStoreLoadRelAcq32, "TestStoreLoadRelAcq32")
testutil.RunTest(TestStoreLoadRelAcq64, "TestStoreLoadRelAcq64")
testutil.RunTest(TestUnaligned64, "TestUnaligned64")
testutil.RunTest(TestHammerStoreLoad, "TestHammerStoreLoad")
fmt.Println("Atomic tests passed")
}
module mtgeneral
go 1.22
toolchain go1.22.0
package main
import (
"fmt"
"os"
"runtime"
"sync"
"sync/atomic"
)
func main() {
// try some concurrency!
var wg sync.WaitGroup
wg.Add(2)
var x atomic.Int32
go func() {
x.Add(2)
wg.Done()
}()
go func() {
x.Add(40)
wg.Done()
}()
wg.Wait()
fmt.Printf("waitgroup result: %d\n", x.Load())
// channels
a := make(chan int, 1)
b := make(chan int)
c := make(chan int)
go func() {
t0 := <-a
b <- t0
}()
go func() {
t1 := <-b
c <- t1
}()
a <- 1234
out := <-c
fmt.Printf("channels result: %d\n", out)
// try a GC! (the runtime might not have run one yet)
runtime.GC()
_, _ = os.Stdout.Write([]byte("GC complete!\n"))
}
...@@ -3,3 +3,6 @@ module map ...@@ -3,3 +3,6 @@ module map
go 1.22 go 1.22
toolchain go1.22.0 toolchain go1.22.0
require utils v0.0.0
replace utils => ../../utils
...@@ -2,42 +2,18 @@ package main ...@@ -2,42 +2,18 @@ package main
import ( import (
"fmt" "fmt"
"sync"
"utils/testutil"
) )
func main() { func main() {
var m sync.Map testutil.RunTest(TestMapMatchesRWMutex, "TestMapMatchesRWMutex")
testutil.RunTest(TestMapMatchesDeepCopy, "TestMapMatchesDeepCopy")
m.Store("hello", "world") testutil.RunTest(TestConcurrentRange, "TestConcurrentRange")
m.Store("foo", "bar") testutil.RunTest(TestIssue40999, "TestIssue40999")
m.Store("baz", "qux") testutil.RunTest(TestMapRangeNestedCall, "TestMapRangeNestedCall")
testutil.RunTest(TestCompareAndSwap_NonExistingKey, "TestCompareAndSwap_NonExistingKey")
m.Delete("foo") testutil.RunTest(TestMapRangeNoAllocations, "TestMapRangeNoAllocations")
m.Load("baz")
go func() {
m.CompareAndDelete("hello", "world")
m.LoadAndDelete("baz")
}()
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
m.Load("hello")
m.Load("baz")
m.Range(func(k, v interface{}) bool {
m.Load("hello")
m.Load("baz")
return true
})
m.CompareAndSwap("hello", "world", "Go")
m.LoadOrStore("hello", "world")
wg.Done()
}()
}
wg.Wait()
fmt.Println("Map test passed") fmt.Println("Map test passed")
} }
// This file is based on code written by The Go Authors.
// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/map_reference_test.go
//
// --- Original License Notice ---
//
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main
import (
"sync"
"sync/atomic"
)
// 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 = &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
}
// This file is based on code written by The Go Authors.
// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/map_test.go
//
// --- Original License Notice ---
//
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main
import (
"math/rand"
"reflect"
"runtime"
"sync"
"sync/atomic"
"testing"
"testing/quick"
"utils/testutil"
)
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(sync.Map), 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 *testutil.TestRunner) {
if err := quick.CheckEqual(applyMap, applyRWMutexMap, nil); err != nil {
t.Error(err)
}
}
func TestMapMatchesDeepCopy(t *testutil.TestRunner) {
if err := quick.CheckEqual(applyMap, applyDeepCopyMap, nil); err != nil {
t.Error(err)
}
}
func TestConcurrentRange(t *testutil.TestRunner) {
const mapSize = 1 << 10
m := new(sync.Map)
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
//}
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)
}
}
}
func TestIssue40999(t *testutil.TestRunner) {
var m sync.Map
// Since the miss-counting in missLocked (via Delete)
// compares the miss count with len(m.dirty),
// add an initial entry to bias len(m.dirty) above the miss count.
m.Store(nil, struct{}{})
var finalized uint32
// Set finalizers that count for collected keys. A non-zero count
// indicates that keys have not been leaked.
for atomic.LoadUint32(&finalized) == 0 {
p := new(int)
runtime.SetFinalizer(p, func(*int) {
atomic.AddUint32(&finalized, 1)
})
m.Store(p, struct{}{})
m.Delete(p)
runtime.GC()
}
}
func TestMapRangeNestedCall(t *testutil.TestRunner) { // Issue 46399
var m sync.Map
for i, v := range [3]string{"hello", "world", "Go"} {
m.Store(i, v)
}
m.Range(func(key, value any) bool {
m.Range(func(key, value any) bool {
// We should be able to load the key offered in the Range callback,
// because there are no concurrent Delete involved in this tested map.
if v, ok := m.Load(key); !ok || !reflect.DeepEqual(v, value) {
t.Fatalf("Nested Range loads unexpected value, got %+v want %+v", v, value)
}
// We didn't keep 42 and a value into the map before, if somehow we loaded
// a value from such a key, meaning there must be an internal bug regarding
// nested range in the Map.
if _, loaded := m.LoadOrStore(42, "dummy"); loaded {
t.Fatalf("Nested Range loads unexpected value, want store a new value")
}
// Try to Store then LoadAndDelete the corresponding value with the key
// 42 to the Map. In this case, the key 42 and associated value should be
// removed from the Map. Therefore any future range won't observe key 42
// as we checked in above.
val := "sync.Map"
m.Store(42, val)
if v, loaded := m.LoadAndDelete(42); !loaded || !reflect.DeepEqual(v, val) {
t.Fatalf("Nested Range loads unexpected value, got %v, want %v", v, val)
}
return true
})
// Remove key from Map on-the-fly.
m.Delete(key)
return true
})
// After a Range of Delete, all keys should be removed and any
// further Range won't invoke the callback. Hence length remains 0.
length := 0
m.Range(func(key, value any) bool {
length++
return true
})
if length != 0 {
t.Fatalf("Unexpected sync.Map size, got %v want %v", length, 0)
}
}
func TestCompareAndSwap_NonExistingKey(t *testutil.TestRunner) {
m := &sync.Map{}
if m.CompareAndSwap(m, nil, 42) {
// See https://go.dev/issue/51972#issuecomment-1126408637.
t.Fatalf("CompareAndSwap on a non-existing key succeeded")
}
}
func TestMapRangeNoAllocations(t *testutil.TestRunner) { // Issue 62404
var m sync.Map
allocs := testing.AllocsPerRun(10, func() {
m.Range(func(key, value any) bool {
return true
})
})
if allocs > 0 {
t.Errorf("AllocsPerRun of m.Range = %v; want 0", allocs)
}
}
...@@ -3,3 +3,6 @@ module mutex ...@@ -3,3 +3,6 @@ module mutex
go 1.22 go 1.22
toolchain go1.22.0 toolchain go1.22.0
require utils v0.0.0
replace utils => ../../utils
// Portions of this code are derived from code written by The Go Authors.
// See original source: https://github.com/golang/go/blob/400433af3660905ecaceaf19ddad3e6c24b141df/src/sync/mutex_test.go
//
// --- Original License Notice ---
//
// Copyright 2009 The Go Authors.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main package main
import ( import (
"fmt" "fmt"
"os"
"sync" "utils/testutil"
) )
func main() { func main() {
TestMutex() testutil.RunTest(TestSemaphore, "TestSemaphore")
} testutil.RunTest(TestMutex, "TestMutex")
testutil.RunTest(TestMutexFairness, "TestMutexFairness")
func TestMutex() {
m := new(sync.Mutex)
m.Lock()
if m.TryLock() {
_, _ = fmt.Fprintln(os.Stderr, "TryLock succeeded with mutex locked")
os.Exit(1)
}
m.Unlock()
if !m.TryLock() {
_, _ = fmt.Fprintln(os.Stderr, "TryLock failed with mutex unlocked")
os.Exit(1)
}
m.Unlock()
c := make(chan bool)
for i := 0; i < 10; i++ {
go HammerMutex(m, 1000, c)
}
for i := 0; i < 10; i++ {
<-c
}
fmt.Println("Mutex test passed") fmt.Println("Mutex test passed")
} }
func HammerMutex(m *sync.Mutex, loops int, cdone chan bool) {
for i := 0; i < loops; i++ {
if i%3 == 0 {
if m.TryLock() {
m.Unlock()
}
continue
}
m.Lock()
m.Unlock()
}
cdone <- true
}
// This file is based on code written by The Go Authors.
// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/mutex_test.go
//
// --- Original License Notice ---
//
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main
import (
"runtime"
. "sync"
"time"
"utils/testutil"
)
func HammerSemaphore(s *uint32, loops int, cdone chan bool) {
for i := 0; i < loops; i++ {
Runtime_Semacquire(s)
Runtime_Semrelease(s, false, 0)
}
cdone <- true
}
func TestSemaphore(t *testutil.TestRunner) {
s := new(uint32)
*s = 1
c := make(chan bool)
for i := 0; i < 10; i++ {
go HammerSemaphore(s, 1000, c)
}
for i := 0; i < 10; i++ {
<-c
}
}
func HammerMutex(m *Mutex, loops int, cdone chan bool) {
for i := 0; i < loops; i++ {
if i%3 == 0 {
if m.TryLock() {
m.Unlock()
}
continue
}
m.Lock()
m.Unlock()
}
cdone <- true
}
func TestMutex(t *testutil.TestRunner) {
if n := runtime.SetMutexProfileFraction(1); n != 0 {
t.Logf("got mutexrate %d expected 0", n)
}
defer runtime.SetMutexProfileFraction(0)
m := new(Mutex)
m.Lock()
if m.TryLock() {
t.Fatalf("TryLock succeeded with mutex locked")
}
m.Unlock()
if !m.TryLock() {
t.Fatalf("TryLock failed with mutex unlocked")
}
m.Unlock()
c := make(chan bool)
for i := 0; i < 10; i++ {
go HammerMutex(m, 1000, c)
}
for i := 0; i < 10; i++ {
<-c
}
}
func TestMutexFairness(t *testutil.TestRunner) {
var mu Mutex
stop := make(chan bool)
defer close(stop)
go func() {
for {
mu.Lock()
time.Sleep(100 * time.Microsecond)
mu.Unlock()
select {
case <-stop:
return
default:
}
}
}()
done := make(chan bool, 1)
go func() {
for i := 0; i < 10; i++ {
time.Sleep(100 * time.Microsecond)
mu.Lock()
mu.Unlock()
}
done <- true
}()
select {
case <-done:
case <-time.After(10 * time.Second):
t.Fatalf("can't acquire Mutex in 10 seconds")
}
}
package main
import (
_ "unsafe" // Required for go:linkname
)
var Runtime_Semacquire = runtime_Semacquire
var Runtime_Semrelease = runtime_Semrelease
//go:linkname runtime_Semacquire sync.runtime_Semacquire
func runtime_Semacquire(s *uint32)
//go:linkname runtime_Semrelease sync.runtime_Semrelease
func runtime_Semrelease(s *uint32, handoff bool, skipframes int)
module oncefunc
go 1.22
toolchain go1.22.0
require utils v0.0.0
replace utils => ../../utils
package main
import (
"fmt"
"utils/testutil"
)
func main() {
testutil.RunTest(TestOnceFunc, "TestOnceFunc")
testutil.RunTest(TestOnceValue, "TestOnceValue")
testutil.RunTest(TestOnceValues, "TestOnceValues")
testutil.RunTest(TestOnceFuncPanic, "TestOnceFuncPanic")
testutil.RunTest(TestOnceValuePanic, "TestOnceValuePanic")
testutil.RunTest(TestOnceValuesPanic, "TestOnceValuesPanic")
testutil.RunTest(TestOnceFuncPanicNil, "TestOnceFuncPanicNil")
testutil.RunTest(TestOnceFuncGoexit, "TestOnceFuncGoexit")
testutil.RunTest(TestOnceFuncPanicTraceback, "TestOnceFuncPanicTraceback")
testutil.RunTest(TestOnceXGC, "TestOnceXGC")
fmt.Println("OnceFunc tests passed")
}
// This file is based on code written by The Go Authors.
// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/oncefunc_test.go
//
// --- Original License Notice ---
//
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main
import (
"bytes"
"math"
"runtime"
"runtime/debug"
"sync"
"sync/atomic"
"testing"
_ "unsafe"
"utils/testutil"
)
// We assume that the Once.Do tests have already covered parallelism.
func TestOnceFunc(t *testutil.TestRunner) {
calls := 0
f := sync.OnceFunc(func() { calls++ })
allocs := testing.AllocsPerRun(10, f)
if calls != 1 {
t.Errorf("want calls==1, got %d", calls)
}
if allocs != 0 {
t.Errorf("want 0 allocations per call, got %v", allocs)
}
}
func TestOnceValue(t *testutil.TestRunner) {
calls := 0
f := sync.OnceValue(func() int {
calls++
return calls
})
allocs := testing.AllocsPerRun(10, func() { f() })
value := f()
if calls != 1 {
t.Errorf("want calls==1, got %d", calls)
}
if value != 1 {
t.Errorf("want value==1, got %d", value)
}
if allocs != 0 {
t.Errorf("want 0 allocations per call, got %v", allocs)
}
}
func TestOnceValues(t *testutil.TestRunner) {
calls := 0
f := sync.OnceValues(func() (int, int) {
calls++
return calls, calls + 1
})
allocs := testing.AllocsPerRun(10, func() { f() })
v1, v2 := f()
if calls != 1 {
t.Errorf("want calls==1, got %d", calls)
}
if v1 != 1 || v2 != 2 {
t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2)
}
if allocs != 0 {
t.Errorf("want 0 allocations per call, got %v", allocs)
}
}
func testOncePanicX(t testing.TB, calls *int, f func()) {
testOncePanicWith(t, calls, f, func(label string, p any) {
if p != "x" {
t.Fatalf("%s: want panic %v, got %v", label, "x", p)
}
})
}
func testOncePanicWith(t testing.TB, calls *int, f func(), check func(label string, p any)) {
// Check that the each call to f panics with the same value, but the
// underlying function is only called once.
for _, label := range []string{"first time", "second time"} {
var p any
panicked := true
func() {
defer func() {
p = recover()
}()
f()
panicked = false
}()
if !panicked {
t.Fatalf("%s: f did not panic", label)
}
check(label, p)
}
if *calls != 1 {
t.Errorf("want calls==1, got %d", *calls)
}
}
func TestOnceFuncPanic(t *testutil.TestRunner) {
calls := 0
f := sync.OnceFunc(func() {
calls++
panic("x")
})
testOncePanicX(t, &calls, f)
}
func TestOnceValuePanic(t *testutil.TestRunner) {
calls := 0
f := sync.OnceValue(func() int {
calls++
panic("x")
})
testOncePanicX(t, &calls, func() { f() })
}
func TestOnceValuesPanic(t *testutil.TestRunner) {
calls := 0
f := sync.OnceValues(func() (int, int) {
calls++
panic("x")
})
testOncePanicX(t, &calls, func() { f() })
}
func TestOnceFuncPanicNil(t *testutil.TestRunner) {
calls := 0
f := sync.OnceFunc(func() {
calls++
panic(nil)
})
testOncePanicWith(t, &calls, f, func(label string, p any) {
switch p.(type) {
case nil, *runtime.PanicNilError:
return
}
t.Fatalf("%s: want nil panic, got %v", label, p)
})
}
func TestOnceFuncGoexit(t *testutil.TestRunner) {
// If f calls Goexit, the results are unspecified. But check that f doesn't
// get called twice.
calls := 0
f := sync.OnceFunc(func() {
calls++
runtime.Goexit()
})
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
defer func() { recover() }()
f()
}()
wg.Wait()
}
if calls != 1 {
t.Errorf("want calls==1, got %d", calls)
}
}
func TestOnceFuncPanicTraceback(t *testutil.TestRunner) {
// Test that on the first invocation of a OnceFunc, the stack trace goes all
// the way to the origin of the panic.
f := sync.OnceFunc(onceFuncPanic)
defer func() {
if p := recover(); p != "x" {
t.Fatalf("want panic %v, got %v", "x", p)
}
stack := debug.Stack()
//want := "sync_test.onceFuncPanic"
want := "main.onceFuncPanic"
if !bytes.Contains(stack, []byte(want)) {
t.Fatalf("want stack containing %v, got:\n%s", want, string(stack))
}
}()
f()
}
func onceFuncPanic() {
panic("x")
}
func TestOnceXGC(t *testutil.TestRunner) {
fns := map[string]func([]byte) func(){
"OnceFunc": func(buf []byte) func() {
return sync.OnceFunc(func() { buf[0] = 1 })
},
"OnceValue": func(buf []byte) func() {
f := sync.OnceValue(func() any { buf[0] = 1; return nil })
return func() { f() }
},
"OnceValues": func(buf []byte) func() {
f := sync.OnceValues(func() (any, any) { buf[0] = 1; return nil, nil })
return func() { f() }
},
}
for n, fn := range fns {
t.Run(n, func(t testing.TB) {
buf := make([]byte, 1024)
var gc atomic.Bool
runtime.SetFinalizer(&buf[0], func(_ *byte) {
gc.Store(true)
})
f := fn(buf)
gcwaitfin()
if gc.Load() != false {
t.Fatal("wrapped function garbage collected too early")
}
f()
gcwaitfin()
if gc.Load() != true {
// Even if f is still alive, the function passed to Once(Func|Value|Values)
// is not kept alive after the first call to f.
t.Fatal("wrapped function should be garbage collected, but still live")
}
f()
})
}
}
// gcwaitfin performs garbage collection and waits for all finalizers to run.
func gcwaitfin() {
runtime.GC()
runtime_blockUntilEmptyFinalizerQueue(math.MaxInt64)
}
//go:linkname runtime_blockUntilEmptyFinalizerQueue runtime.blockUntilEmptyFinalizerQueue
func runtime_blockUntilEmptyFinalizerQueue(int64) bool
// This file is based on code written by The Go Authors.
// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/export_test.go
//
// --- Original License Notice ---
//
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main
// Export for testing.
// var Runtime_Semacquire = runtime_Semacquire
// var Runtime_Semrelease = runtime_Semrelease
var Runtime_procPin = runtime_procPin
var Runtime_procUnpin = runtime_procUnpin
// poolDequeue testing.
type PoolDequeue interface {
PushHead(val any) bool
PopHead() (any, bool)
PopTail() (any, bool)
}
func NewPoolDequeue(n int) PoolDequeue {
d := &poolDequeue{
vals: make([]eface, n),
}
// For testing purposes, set the head and tail indexes close
// to wrapping around.
d.headTail.Store(d.pack(1<<dequeueBits-500, 1<<dequeueBits-500))
return d
}
func (d *poolDequeue) PushHead(val any) bool {
return d.pushHead(val)
}
func (d *poolDequeue) PopHead() (any, bool) {
return d.popHead()
}
func (d *poolDequeue) PopTail() (any, bool) {
return d.popTail()
}
func NewPoolChain() PoolDequeue {
return new(poolChain)
}
func (c *poolChain) PushHead(val any) bool {
c.pushHead(val)
return true
}
func (c *poolChain) PopHead() (any, bool) {
return c.popHead()
}
func (c *poolChain) PopTail() (any, bool) {
return c.popTail()
}
...@@ -3,3 +3,6 @@ module pool ...@@ -3,3 +3,6 @@ module pool
go 1.22 go 1.22
toolchain go1.22.0 toolchain go1.22.0
require utils v0.0.0
replace utils => ../../utils
...@@ -2,37 +2,19 @@ package main ...@@ -2,37 +2,19 @@ package main
import ( import (
"fmt" "fmt"
"sync"
"utils/testutil"
) )
func main() { func main() {
var x sync.Pool testutil.RunTest(TestPool, "TestPool")
testutil.RunTest(TestPoolNew, "TestPoolNew")
x.Put(1) testutil.RunTest(TestPoolGC, "TestPoolGC")
x.Put(2) testutil.RunTest(TestPoolRelease, "TestPoolRelease")
testutil.RunTest(TestPoolStress, "TestPoolStress")
// try some concurrency! testutil.RunTest(TestPoolDequeue, "TestPoolDequeue")
var wg sync.WaitGroup testutil.RunTest(TestPoolChain, "TestPoolChain")
wg.Add(2) testutil.RunTest(TestNilPool, "TestNilPool")
go func() {
x.Put(3)
wg.Done()
}()
go func() {
x.Put(4)
wg.Done()
}()
wg.Wait()
wg.Add(4)
for i := 0; i < 4; i++ {
go func() {
x.Get()
wg.Done()
}()
}
wg.Wait()
fmt.Println("Pool test passed") fmt.Println("Pool test passed")
} }
// This file is based on code written by The Go Authors.
// See original source: https://github.com/golang/go/blob/go1.22.7/src/sync/pool_test.go
//
// --- Original License Notice ---
//
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main
import (
"runtime"
"runtime/debug"
. "sync"
"sync/atomic"
"testing"
"time"
"utils/testutil"
)
var short bool = true
func TestPool(t *testutil.TestRunner) {
// disable GC so we can control when it happens.
defer debug.SetGCPercent(debug.SetGCPercent(-1))
var p Pool
if p.Get() != nil {
t.Fatal("expected empty")
}
// Make sure that the goroutine doesn't migrate to another P
// between Put and Get calls.
Runtime_procPin()
p.Put("a")
p.Put("b")
if g := p.Get(); g != "a" {
t.Fatalf("got %#v; want a", g)
}
if g := p.Get(); g != "b" {
t.Fatalf("got %#v; want b", g)
}
if g := p.Get(); g != nil {
t.Fatalf("got %#v; want nil", g)
}
Runtime_procUnpin()
// Put in a large number of objects so they spill into
// stealable space.
for i := 0; i < 100; i++ {
p.Put("c")
}
// After one GC, the victim cache should keep them alive.
runtime.GC()
if g := p.Get(); g != "c" {
t.Fatalf("got %#v; want c after GC", g)
}
// A second GC should drop the victim cache.
runtime.GC()
if g := p.Get(); g != nil {
t.Fatalf("got %#v; want nil after second GC", g)
}
}
func TestPoolNew(t *testutil.TestRunner) {
// disable GC so we can control when it happens.
defer debug.SetGCPercent(debug.SetGCPercent(-1))
i := 0
p := Pool{
New: func() any {
i++
return i
},
}
if v := p.Get(); v != 1 {
t.Fatalf("got %v; want 1", v)
}
if v := p.Get(); v != 2 {
t.Fatalf("got %v; want 2", v)
}
// Make sure that the goroutine doesn't migrate to another P
// between Put and Get calls.
Runtime_procPin()
p.Put(42)
if v := p.Get(); v != 42 {
t.Fatalf("got %v; want 42", v)
}
Runtime_procUnpin()
if v := p.Get(); v != 3 {
t.Fatalf("got %v; want 3", v)
}
}
// Test that Pool does not hold pointers to previously cached resources.
func TestPoolGC(t *testutil.TestRunner) {
testPool(t, true)
}
// Test that Pool releases resources on GC.
func TestPoolRelease(t *testutil.TestRunner) {
testPool(t, false)
}
func testPool(t testing.TB, drain bool) {
var p Pool
const N = 100
loop:
for try := 0; try < 3; try++ {
if try == 1 && short {
break
}
var fin, fin1 uint32
for i := 0; i < N; i++ {
v := new(string)
runtime.SetFinalizer(v, func(vv *string) {
atomic.AddUint32(&fin, 1)
})
p.Put(v)
}
if drain {
for i := 0; i < N; i++ {
p.Get()
}
}
for i := 0; i < 5; i++ {
runtime.GC()
time.Sleep(time.Duration(i*100+10) * time.Millisecond)
// 1 pointer can remain on stack or elsewhere
if fin1 = atomic.LoadUint32(&fin); fin1 >= N-1 {
continue loop
}
}
t.Fatalf("only %v out of %v resources are finalized on try %v", fin1, N, try)
}
}
func TestPoolStress(t *testutil.TestRunner) {
const P = 10
N := int(1e6)
if short {
N /= 100
}
var p Pool
done := make(chan bool)
for i := 0; i < P; i++ {
go func() {
var v any = 0
for j := 0; j < N; j++ {
if v == nil {
v = 0
}
p.Put(v)
v = p.Get()
if v != nil && v.(int) != 0 {
t.Errorf("expect 0, got %v", v)
break
}
}
done <- true
}()
}
for i := 0; i < P; i++ {
<-done
}
}
func TestPoolDequeue(t *testutil.TestRunner) {
testPoolDequeue(t, NewPoolDequeue(16))
}
func TestPoolChain(t *testutil.TestRunner) {
testPoolDequeue(t, NewPoolChain())
}
func testPoolDequeue(t testing.TB, d PoolDequeue) {
const P = 10
var N int = 2e6
if short {
N = 1e3
}
have := make([]int32, N)
var stop int32
var wg WaitGroup
record := func(val int) {
atomic.AddInt32(&have[val], 1)
if val == N-1 {
atomic.StoreInt32(&stop, 1)
}
}
// Start P-1 consumers.
for i := 1; i < P; i++ {
wg.Add(1)
go func() {
fail := 0
for atomic.LoadInt32(&stop) == 0 {
val, ok := d.PopTail()
if ok {
fail = 0
record(val.(int))
} else {
// Speed up the test by
// allowing the pusher to run.
if fail++; fail%100 == 0 {
runtime.Gosched()
}
}
}
wg.Done()
}()
}
// Start 1 producer.
nPopHead := 0
wg.Add(1)
go func() {
for j := 0; j < N; j++ {
for !d.PushHead(j) {
// Allow a popper to run.
runtime.Gosched()
}
if j%10 == 0 {
val, ok := d.PopHead()
if ok {
nPopHead++
record(val.(int))
}
}
}
wg.Done()
}()
wg.Wait()
// Check results.
for i, count := range have {
if count != 1 {
t.Errorf("expected have[%d] = 1, got %d", i, count)
}
}
// Check that at least some PopHeads succeeded. We skip this
// check in short mode because it's common enough that the
// queue will stay nearly empty all the time and a PopTail
// will happen during the window between every PushHead and
// PopHead.
if !short && nPopHead == 0 {
t.Errorf("popHead never succeeded")
}
}
func TestNilPool(t *testutil.TestRunner) {
catch := func() {
if recover() == nil {
t.Error("expected panic")
}
}
var p *Pool
t.Run("Get", func(t testing.TB) {
defer catch()
if p.Get() != nil {
t.Error("expected empty")
}
t.Error("should have panicked already")
})
t.Run("Put", func(t testing.TB) {
defer catch()
p.Put("a")
t.Error("should have panicked already")
})
}
This diff is collapsed.
package main
import (
_ "unsafe" // Required for go:linkname
)
//go:linkname runtime_procPin runtime.procPin
func runtime_procPin() int
//go:linkname runtime_procUnpin runtime.procUnpin
func runtime_procUnpin()
module mtvalue
go 1.22
toolchain go1.22.0
require utils v0.0.0
replace utils => ../../utils
package main
import (
"fmt"
"utils/testutil"
)
func main() {
testutil.RunTest(TestValue, "TestValue")
testutil.RunTest(TestValueLarge, "TestValueLarge")
testutil.RunTest(TestValuePanic, "TestValuePanic")
testutil.RunTest(TestValueConcurrent, "TestValueConcurrent")
testutil.RunTest(TestValue_Swap, "TestValue_Swap")
testutil.RunTest(TestValueSwapConcurrent, "TestValueSwapConcurrent")
testutil.RunTest(TestValue_CompareAndSwap, "TestValue_CompareAndSwap")
testutil.RunTest(TestValueCompareAndSwapConcurrent, "TestValueCompareAndSwapConcurrent")
fmt.Println("Value tests passed")
}
This diff is collapsed.
...@@ -3,3 +3,6 @@ module wg ...@@ -3,3 +3,6 @@ module wg
go 1.22 go 1.22
toolchain go1.22.0 toolchain go1.22.0
require utils v0.0.0
replace utils => ../../utils
...@@ -2,39 +2,15 @@ package main ...@@ -2,39 +2,15 @@ package main
import ( import (
"fmt" "fmt"
"sync"
"sync/atomic" "utils/testutil"
) )
func main() { func main() {
// try some concurrency! testutil.RunTest(TestWaitGroup, "TestWaitGroup")
var wg sync.WaitGroup testutil.RunTest(TestWaitGroupMisuse, "TestWaitGroupMisuse")
wg.Add(2) testutil.RunTest(TestWaitGroupRace, "TestWaitGroupRace")
var x atomic.Int32 testutil.RunTest(TestWaitGroupAlign, "TestWaitGroupAlign")
go func() {
x.Add(2)
wg.Done()
}()
go func() {
x.Add(40)
wg.Done()
}()
wg.Wait()
fmt.Printf("waitgroup result: %d\n", x.Load())
// channels fmt.Println("WaitGroup tests passed")
a := make(chan int, 1)
b := make(chan int)
c := make(chan int)
go func() {
t0 := <-a
b <- t0
}()
go func() {
t1 := <-b
c <- t1
}()
a <- 1234
out := <-c
fmt.Printf("channels result: %d\n", out)
} }
This diff is collapsed.
module utilscheck
go 1.22
toolchain go1.22.0
require utils v0.0.0
replace utils => ../../utils
package main
import (
"fmt"
"utils/testutil"
)
func main() {
testutil.RunTest(ShouldFail, "ShouldFail")
fmt.Println("Passed test that should have failed")
}
func ShouldFail(t *testutil.TestRunner) {
t.Fail()
}
module utilscheck2
go 1.22
toolchain go1.22.0
require utils v0.0.0
replace utils => ../../utils
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment