// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package semaphore
// Based on https://github.com/golang/sync/blob/master/semaphore/semaphore_test.go
import (
"context"
"math/rand"
"runtime"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
)
const maxSleep = 1 * time . Millisecond
func HammerDynamic ( sem * Dynamic , loops int ) {
for i := 0 ; i < loops ; i ++ {
sem . Acquire ( context . Background ( ) )
time . Sleep ( time . Duration ( rand . Int63n ( int64 ( maxSleep / time . Nanosecond ) ) ) * time . Nanosecond )
sem . Release ( )
}
}
// TestDynamic hammers the semaphore from all available cores to ensure we don't
// hit a panic or race detector notice something wonky.
func TestDynamic ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "too slow for testing.Short" )
}
t . Parallel ( )
n := runtime . GOMAXPROCS ( 0 )
loops := 10000 / n
sem := NewDynamic ( int64 ( n ) )
var wg sync . WaitGroup
wg . Add ( n )
for i := 0 ; i < n ; i ++ {
go func ( ) {
defer wg . Done ( )
HammerDynamic ( sem , loops )
} ( )
}
wg . Wait ( )
}
func TestDynamicPanic ( t * testing . T ) {
t . Parallel ( )
defer func ( ) {
if recover ( ) == nil {
t . Fatal ( "release of an unacquired dynamic semaphore did not panic" )
}
} ( )
w := NewDynamic ( 1 )
w . Release ( )
}
func checkAcquire ( t * testing . T , sem * Dynamic , wantAcquire bool ) {
t . Helper ( )
ctx , cancel := context . WithTimeout ( context . Background ( ) , 10 * time . Millisecond )
defer cancel ( )
err := sem . Acquire ( ctx )
if wantAcquire {
require . NoErrorf ( t , err , "failed to acquire when we should have" )
} else {
require . Error ( t , err , "failed to block when should be full" )
}
}
func TestDynamicAcquire ( t * testing . T ) {
t . Parallel ( )
ctx := context . Background ( )
sem := NewDynamic ( 2 )
// Consume one slot [free: 1]
sem . Acquire ( ctx )
// Should be able to consume another [free: 0]
checkAcquire ( t , sem , true )
// Should fail to consume another [free: 0]
checkAcquire ( t , sem , false )
// Release 2
sem . Release ( )
sem . Release ( )
// Should be able to consume another [free: 1]
checkAcquire ( t , sem , true )
// Should be able to consume another [free: 0]
checkAcquire ( t , sem , true )
// Should fail to consume another [free: 0]
checkAcquire ( t , sem , false )
// Now expand the semaphore and we should be able to acquire again [free: 2]
sem . SetSize ( 4 )
// Should be able to consume another [free: 1]
checkAcquire ( t , sem , true )
// Should be able to consume another [free: 0]
checkAcquire ( t , sem , true )
// Should fail to consume another [free: 0]
checkAcquire ( t , sem , false )
// Shrinking it should work [free: 0]
sem . SetSize ( 3 )
// Should fail to consume another [free: 0]
checkAcquire ( t , sem , false )
// Release one [free: 0] (3 slots used are release, size only 3)
sem . Release ( )
// Should fail to consume another [free: 0]
checkAcquire ( t , sem , false )
sem . Release ( )
// Should be able to consume another [free: 1]
checkAcquire ( t , sem , true )
}