github.com/prysmaticlabs/prysm@v1.4.4/shared/mputil/multilock_test.go (about)

     1  /*
     2  Copyright 2017 Albert Tedja
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6      http://www.apache.org/licenses/LICENSE-2.0
     7  Unless required by applicable law or agreed to in writing, software
     8  distributed under the License is distributed on an "AS IS" BASIS,
     9  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10  See the License for the specific language governing permissions and
    11  limitations under the License.
    12  */
    13  package mputil
    14  
    15  import (
    16  	"sync"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/stretchr/testify/assert"
    21  )
    22  
    23  func TestUnique(t *testing.T) {
    24  	var arr []string
    25  	assert := assert.New(t)
    26  
    27  	arr = []string{"a", "b", "c"}
    28  	assert.Equal(arr, unique(arr))
    29  
    30  	arr = []string{"a", "a", "a"}
    31  	assert.Equal([]string{"a"}, unique(arr))
    32  
    33  	arr = []string{"a", "a", "b"}
    34  	assert.Equal([]string{"a", "b"}, unique(arr))
    35  
    36  	arr = []string{"a", "b", "a"}
    37  	assert.Equal([]string{"a", "b"}, unique(arr))
    38  
    39  	arr = []string{"a", "b", "c", "b", "d"}
    40  	assert.Equal([]string{"a", "b", "c", "d"}, unique(arr))
    41  }
    42  
    43  func TestGetChan(t *testing.T) {
    44  	ch1 := getChan("a")
    45  	ch2 := getChan("aa")
    46  	ch3 := getChan("a")
    47  
    48  	assert := assert.New(t)
    49  	assert.NotEqual(ch1, ch2)
    50  	assert.Equal(ch1, ch3)
    51  }
    52  
    53  func TestLockUnlock(t *testing.T) {
    54  	var wg sync.WaitGroup
    55  
    56  	wg.Add(5)
    57  
    58  	go func() {
    59  		lock := NewMultilock("dog", "cat", "owl")
    60  		lock.Lock()
    61  		defer lock.Unlock()
    62  
    63  		<-time.After(100 * time.Millisecond)
    64  		wg.Done()
    65  	}()
    66  
    67  	go func() {
    68  		lock := NewMultilock("cat", "dog", "bird")
    69  		lock.Lock()
    70  		defer lock.Unlock()
    71  
    72  		<-time.After(100 * time.Millisecond)
    73  		wg.Done()
    74  	}()
    75  
    76  	go func() {
    77  		lock := NewMultilock("cat", "bird", "owl")
    78  		lock.Lock()
    79  		defer lock.Unlock()
    80  
    81  		<-time.After(100 * time.Millisecond)
    82  		wg.Done()
    83  	}()
    84  
    85  	go func() {
    86  		lock := NewMultilock("bird", "owl", "snake")
    87  		lock.Lock()
    88  		defer lock.Unlock()
    89  
    90  		<-time.After(100 * time.Millisecond)
    91  		wg.Done()
    92  	}()
    93  
    94  	go func() {
    95  		lock := NewMultilock("owl", "snake")
    96  		lock.Lock()
    97  		defer lock.Unlock()
    98  
    99  		<-time.After(1 * time.Second)
   100  		wg.Done()
   101  	}()
   102  
   103  	wg.Wait()
   104  }
   105  
   106  func TestLockUnlock_CleansUnused(t *testing.T) {
   107  	var wg sync.WaitGroup
   108  	wg.Add(1)
   109  	go func() {
   110  		lock := NewMultilock("dog", "cat", "owl")
   111  		lock.Lock()
   112  		assert.Equal(t, 3, len(locks.list))
   113  		defer lock.Unlock()
   114  
   115  		<-time.After(100 * time.Millisecond)
   116  		wg.Done()
   117  	}()
   118  	wg.Wait()
   119  	// We expect that unlocking completely cleared the locks list
   120  	// given all 3 lock keys were unused at time of unlock.
   121  	assert.Equal(t, 0, len(locks.list))
   122  }
   123  
   124  func TestLockUnlock_DoesNotCleanIfHeldElsewhere(t *testing.T) {
   125  	var wg sync.WaitGroup
   126  	wg.Add(2)
   127  	go func() {
   128  		lock := NewMultilock("cat")
   129  		lock.Lock()
   130  		// We take 200 milliseconds to release the lock on "cat"
   131  		<-time.After(200 * time.Millisecond)
   132  		lock.Unlock()
   133  		// Assert that at the end of this goroutine, all locks are cleared.
   134  		assert.Equal(t, 0, len(locks.list))
   135  		wg.Done()
   136  	}()
   137  	go func() {
   138  		lock := NewMultilock("dog", "cat", "owl")
   139  		lock.Lock()
   140  		// We release the locks after 100 milliseconds, and check that "cat" is not
   141  		// cleared as a lock for it is still held by the previous goroutine.
   142  		<-time.After(100 * time.Millisecond)
   143  		lock.Unlock()
   144  		assert.Equal(t, 1, len(locks.list))
   145  		_, ok := locks.list["cat"]
   146  		assert.Equal(t, true, ok)
   147  		wg.Done()
   148  	}()
   149  	wg.Wait()
   150  	// We expect that at the end of this test, all locks are cleared.
   151  	assert.Equal(t, 0, len(locks.list))
   152  }
   153  
   154  func TestYield(t *testing.T) {
   155  	var wg sync.WaitGroup
   156  
   157  	wg.Add(2)
   158  	var resources = map[string]int{}
   159  
   160  	go func() {
   161  		lock := NewMultilock("A", "C")
   162  		lock.Lock()
   163  		defer lock.Unlock()
   164  
   165  		for resources["ac"] == 0 {
   166  			lock.Yield()
   167  		}
   168  		resources["dc"] = 10
   169  
   170  		wg.Done()
   171  	}()
   172  
   173  	go func() {
   174  		lock := NewMultilock("D", "C")
   175  		lock.Lock()
   176  		defer lock.Unlock()
   177  
   178  		resources["ac"] = 5
   179  		for resources["dc"] == 0 {
   180  			lock.Yield()
   181  		}
   182  
   183  		wg.Done()
   184  	}()
   185  
   186  	wg.Wait()
   187  
   188  	assert.Equal(t, 5, resources["ac"])
   189  	assert.Equal(t, 10, resources["dc"])
   190  }
   191  
   192  func TestClean(t *testing.T) {
   193  	var wg sync.WaitGroup
   194  
   195  	wg.Add(3)
   196  
   197  	// some goroutine that holds multiple locks
   198  	go1done := make(chan bool, 1)
   199  	go func() {
   200  	Loop:
   201  		for {
   202  			select {
   203  			case <-go1done:
   204  				break Loop
   205  			default:
   206  				lock := NewMultilock("A", "B", "C", "E", "Z")
   207  				lock.Lock()
   208  				<-time.After(30 * time.Millisecond)
   209  				lock.Unlock()
   210  			}
   211  		}
   212  		wg.Done()
   213  	}()
   214  
   215  	// another goroutine
   216  	go2done := make(chan bool, 1)
   217  	go func() {
   218  	Loop:
   219  		for {
   220  			select {
   221  			case <-go2done:
   222  				break Loop
   223  			default:
   224  				lock := NewMultilock("B", "C", "K", "L", "Z")
   225  				lock.Lock()
   226  				<-time.After(200 * time.Millisecond)
   227  				lock.Unlock()
   228  			}
   229  		}
   230  		wg.Done()
   231  	}()
   232  
   233  	// this one cleans up the locks every 100 ms
   234  	done := make(chan bool, 1)
   235  	go func() {
   236  		c := time.Tick(100 * time.Millisecond)
   237  	Loop:
   238  		for {
   239  			select {
   240  			case <-done:
   241  				break Loop
   242  			case <-c:
   243  				Clean()
   244  			}
   245  		}
   246  		wg.Done()
   247  	}()
   248  
   249  	<-time.After(2 * time.Second)
   250  	go1done <- true
   251  	go2done <- true
   252  	<-time.After(1 * time.Second)
   253  	done <- true
   254  	wg.Wait()
   255  	assert.Equal(t, []string{}, Clean())
   256  }
   257  
   258  func TestBankAccountProblem(t *testing.T) {
   259  	var wg sync.WaitGroup
   260  	wg.Add(3)
   261  
   262  	joe := 50.0
   263  	susan := 100.0
   264  
   265  	// withdraw $80 from joe, only if balance is sufficient
   266  	go func() {
   267  		lock := NewMultilock("joe")
   268  		lock.Lock()
   269  		defer lock.Unlock()
   270  
   271  		for joe < 80.0 {
   272  			lock.Yield()
   273  		}
   274  		joe -= 80.0
   275  
   276  		wg.Done()
   277  	}()
   278  
   279  	// transfer $200 from susan to joe, only if balance is sufficient
   280  	go func() {
   281  		lock := NewMultilock("joe", "susan")
   282  		lock.Lock()
   283  		defer lock.Unlock()
   284  
   285  		for susan < 200.0 {
   286  			lock.Yield()
   287  		}
   288  
   289  		susan -= 200.0
   290  		joe += 200.0
   291  
   292  		wg.Done()
   293  	}()
   294  
   295  	// susan deposit $300 to cover balance
   296  	go func() {
   297  		lock := NewMultilock("susan")
   298  		lock.Lock()
   299  		defer lock.Unlock()
   300  
   301  		susan += 300.0
   302  
   303  		wg.Done()
   304  	}()
   305  
   306  	wg.Wait()
   307  	assert.Equal(t, 170.0, joe)
   308  	assert.Equal(t, 200.0, susan)
   309  }
   310  
   311  func TestSyncCondCompatibility(t *testing.T) {
   312  	var wg sync.WaitGroup
   313  	wg.Add(2)
   314  	cond := sync.NewCond(NewMultilock("A", "C"))
   315  	var testValues = [3]string{"foo", "bar", "fizz!"}
   316  	sharedRsc := testValues[0]
   317  
   318  	go func() {
   319  		cond.L.Lock()
   320  		for sharedRsc == testValues[0] {
   321  			cond.Wait()
   322  		}
   323  		sharedRsc = testValues[2]
   324  		cond.Broadcast()
   325  		cond.L.Unlock()
   326  		wg.Done()
   327  	}()
   328  
   329  	go func() {
   330  		cond.L.Lock()
   331  		sharedRsc = testValues[1]
   332  		cond.Broadcast()
   333  		for sharedRsc == testValues[1] {
   334  			cond.Wait()
   335  		}
   336  		cond.L.Unlock()
   337  		wg.Done()
   338  	}()
   339  
   340  	wg.Wait()
   341  	assert.Equal(t, testValues[2], sharedRsc)
   342  }