mount-utils: fix flaky test 'TestFormat'

Signed-off-by: TommyStarK <thomasmilox@gmail.com>
This commit is contained in:
TommyStarK 2023-04-23 19:33:23 +02:00
parent b1f901acf4
commit 1c45bacfb0

View File

@ -649,41 +649,28 @@ func TestCheckUmountError(t *testing.T) {
} }
} }
func TestFormat(t *testing.T) { // TODO https://github.com/kubernetes/kubernetes/pull/117539#discussion_r1181873355
func TestFormatConcurrency(t *testing.T) {
const ( const (
formatCount = 5 formatCount = 5
fstype = "ext4" fstype = "ext4"
output = "complete" output = "complete"
cmdDuration = 1 * time.Millisecond
defaultTimeout = 1 * time.Minute defaultTimeout = 1 * time.Minute
) )
tests := []struct { tests := []struct {
desc string desc string
max int max int
timeout time.Duration timeout time.Duration
wantConcurrent int
}{ }{
{ {
max: 0, max: 2,
wantConcurrent: formatCount,
}, },
{ {
max: -1, max: 3,
wantConcurrent: formatCount,
}, },
{ {
max: 1, max: 4,
wantConcurrent: 1,
},
{
max: 3,
wantConcurrent: 3,
},
{
max: 3,
timeout: 1 * time.Nanosecond,
wantConcurrent: formatCount,
}, },
} }
@ -693,20 +680,18 @@ func TestFormat(t *testing.T) {
tc.timeout = defaultTimeout tc.timeout = defaultTimeout
} }
var concurrent, maxConcurrent int var concurrent int
var mu sync.Mutex var mu sync.Mutex
witness := make(chan struct{})
exec := &testexec.FakeExec{} exec := &testexec.FakeExec{}
for i := 0; i < formatCount; i++ { for i := 0; i < formatCount; i++ {
exec.CommandScript = append(exec.CommandScript, makeFakeCommandAction(output, nil, func() { exec.CommandScript = append(exec.CommandScript, makeFakeCommandAction(output, nil, func() {
mu.Lock() mu.Lock()
concurrent++ concurrent++
if concurrent > maxConcurrent {
maxConcurrent = concurrent
}
mu.Unlock() mu.Unlock()
time.Sleep(cmdDuration) <-witness
mu.Lock() mu.Lock()
concurrent-- concurrent--
@ -715,23 +700,111 @@ func TestFormat(t *testing.T) {
} }
mounter := NewSafeFormatAndMount(nil, exec, WithMaxConcurrentFormat(tc.max, tc.timeout)) mounter := NewSafeFormatAndMount(nil, exec, WithMaxConcurrentFormat(tc.max, tc.timeout))
var wg sync.WaitGroup // we run max+1 goroutines and block the command execution
for i := 0; i < formatCount; i++ { // only max goroutine should be running and the additional one should wait
wg.Add(1) // for one to be released
for i := 0; i < tc.max+1; i++ {
go func() { go func() {
defer wg.Done()
mounter.format(fstype, nil) mounter.format(fstype, nil)
}() }()
} }
wg.Wait()
if maxConcurrent != tc.wantConcurrent { // wait for all goorutines to be scheduled
t.Errorf("SafeFormatAndMount.format() got concurrency: %d, want: %d", maxConcurrent, tc.wantConcurrent) time.Sleep(100 * time.Millisecond)
mu.Lock()
if concurrent != tc.max {
t.Errorf("SafeFormatAndMount.format() got concurrency: %d, want: %d", concurrent, tc.max)
} }
mu.Unlock()
// signal the commands to finish the goroutines, this will allow the command
// that is pending to be executed
for i := 0; i < tc.max; i++ {
witness <- struct{}{}
}
// wait for all goroutines to acquire the lock and decrement the counter
time.Sleep(100 * time.Millisecond)
mu.Lock()
if concurrent != 1 {
t.Errorf("SafeFormatAndMount.format() got concurrency: %d, want: 1", concurrent)
}
mu.Unlock()
// signal the pending command to finish, no more command should be running
close(witness)
// wait a few for the last goroutine to acquire the lock and decrements the counter down to zero
time.Sleep(10 * time.Millisecond)
mu.Lock()
if concurrent != 0 {
t.Errorf("SafeFormatAndMount.format() got concurrency: %d, want: 0", concurrent)
}
mu.Unlock()
}) })
} }
} }
// TODO https://github.com/kubernetes/kubernetes/pull/117539#discussion_r1181873355
func TestFormatTimeout(t *testing.T) {
const (
formatCount = 5
fstype = "ext4"
output = "complete"
maxConcurrency = 4
timeout = 200 * time.Millisecond
)
var concurrent int
var mu sync.Mutex
witness := make(chan struct{})
exec := &testexec.FakeExec{}
for i := 0; i < formatCount; i++ {
exec.CommandScript = append(exec.CommandScript, makeFakeCommandAction(output, nil, func() {
mu.Lock()
concurrent++
mu.Unlock()
<-witness
mu.Lock()
concurrent--
mu.Unlock()
}))
}
mounter := NewSafeFormatAndMount(nil, exec, WithMaxConcurrentFormat(maxConcurrency, timeout))
for i := 0; i < maxConcurrency+1; i++ {
go func() {
mounter.format(fstype, nil)
}()
}
// wait a bit more than the configured timeout
time.Sleep(timeout + 100*time.Millisecond)
mu.Lock()
if concurrent != maxConcurrency+1 {
t.Errorf("SafeFormatAndMount.format() got concurrency: %d, want: %d", concurrent, maxConcurrency+1)
}
mu.Unlock()
// signal the pending commands to finish
close(witness)
// wait for all goroutines to acquire the lock and decrement the counter
time.Sleep(100 * time.Millisecond)
mu.Lock()
if concurrent != 0 {
t.Errorf("SafeFormatAndMount.format() got concurrency: %d, want: 0", concurrent)
}
mu.Unlock()
}
func makeFakeCommandAction(stdout string, err error, cmdFn func()) testexec.FakeCommandAction { func makeFakeCommandAction(stdout string, err error, cmdFn func()) testexec.FakeCommandAction {
c := testexec.FakeCmd{ c := testexec.FakeCmd{
CombinedOutputScript: []testexec.FakeAction{ CombinedOutputScript: []testexec.FakeAction{