| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // concurrency_test is a simple test of the framework for systematic |
| // testing of concurrency. |
| package concurrency_test |
| |
| import ( |
| "fmt" |
| "io/ioutil" |
| "os" |
| "testing" |
| "time" |
| |
| "v.io/x/lib/vlog" |
| "v.io/x/ref/runtime/internal/testing/concurrency" |
| "v.io/x/ref/runtime/internal/testing/concurrency/sync" |
| ) |
| |
| var rw sync.RWMutex |
| |
| // createRWMutexSets returns sets of thread identifiers that match the |
| // logic of rwMutexThreadClosure. |
| func createRWMutexSets(n int) (map[int]bool, map[int]bool, map[int]bool, map[int]bool, map[int]bool) { |
| locks := make(map[int]bool) |
| rlocks := make(map[int]bool) |
| runlocks := make(map[int]bool) |
| llocks := make(map[int]bool) |
| lunlocks := make(map[int]bool) |
| for i := 1; i <= n; i++ { |
| switch i % 3 { |
| case 0: |
| locks[i] = true |
| case 1: |
| rlocks[i] = true |
| runlocks[i] = true |
| case 2: |
| llocks[i] = true |
| lunlocks[i] = true |
| } |
| } |
| return locks, rlocks, runlocks, llocks, lunlocks |
| } |
| |
| // generateRWMutexOutputs generates all legal outputs of sequencing calls to |
| // rw.Lock(), rw.Unlock(), rw.RLock(), rw.RUnlock(), |
| // rw.RLocker().Lock(), and rw.RLocker().Unlock(). The inputs identify |
| // the threads that wish to invoke these functions. |
| func generateRWMutexOutputs(locks, rlocks, runlocks, llocks, lunlocks map[int]bool) []string { |
| if length(locks) == 0 && length(rlocks) == 0 && length(runlocks) == 0 && length(llocks) == 0 && length(lunlocks) == 0 { |
| // Base case. |
| return []string{""} |
| } |
| result := make([]string, 0) |
| if length(rlocks) == length(runlocks) && length(llocks) == length(lunlocks) { |
| // rw.Lock() + rw.Unlock() can happen next if the previous calls |
| // to rw.RLock(), rw.RUnlock, rw.RLocker().Lock(), and |
| // rw.RLocker.Unlock() are balanced. |
| for lock, ok := range locks { |
| if ok { |
| locks[lock] = false |
| for _, s := range generateRWMutexOutputs(locks, rlocks, runlocks, llocks, lunlocks) { |
| result = append(result, fmt.Sprintf("%d:Lock()%d:Unlock()%s", lock, lock, s)) |
| } |
| locks[lock] = true |
| } |
| } |
| } |
| for rlock, ok := range rlocks { |
| if ok { |
| // rw.RLock() can happen next any time. |
| rlocks[rlock] = false |
| for _, s := range generateRWMutexOutputs(locks, rlocks, runlocks, llocks, lunlocks) { |
| result = append(result, fmt.Sprintf("%d:RLock()%s", rlock, s)) |
| } |
| rlocks[rlock] = true |
| } |
| } |
| for runlock, ok := range runlocks { |
| if ok { |
| if ok := rlocks[runlock]; !ok { |
| // rw.RUnlock() can happen next as long as the same thread |
| // already invoked rw.RLock(). |
| runlocks[runlock] = false |
| for _, s := range generateRWMutexOutputs(locks, rlocks, runlocks, llocks, lunlocks) { |
| result = append(result, fmt.Sprintf("%d:RUnlock()%s", runlock, s)) |
| } |
| runlocks[runlock] = true |
| } |
| } |
| } |
| for llock, ok := range llocks { |
| if ok { |
| // rw.RLocker().Lock() can happen next any time. |
| llocks[llock] = false |
| for _, s := range generateRWMutexOutputs(locks, rlocks, runlocks, llocks, lunlocks) { |
| result = append(result, fmt.Sprintf("%d:RLocker().Lock()%s", llock, s)) |
| } |
| llocks[llock] = true |
| } |
| } |
| for lunlock, ok := range lunlocks { |
| if ok { |
| if ok := llocks[lunlock]; !ok { |
| // rw.RLocker().Unlock() can happen next as long as the same thread |
| // already invoked rw.RLocker().Lock(). |
| lunlocks[lunlock] = false |
| for _, s := range generateRWMutexOutputs(locks, rlocks, runlocks, llocks, lunlocks) { |
| result = append(result, fmt.Sprintf("%d:RLocker().Unlock()%s", lunlock, s)) |
| } |
| lunlocks[lunlock] = true |
| } |
| } |
| } |
| return result |
| } |
| |
| // rwMutexThreadClosure folds the input arguments inside of the |
| // function body as the testing framework only supports functions with |
| // no arguments. |
| func rwMutexThreadClosure(t *testing.T, n, max int, out *os.File) func() { |
| return func() { |
| defer concurrency.Exit() |
| if n < max { |
| child := rwMutexThreadClosure(t, n+1, max, out) |
| concurrency.Start(child) |
| } |
| switch n % 3 { |
| case 0: |
| rw.Lock() |
| fmt.Fprintf(out, "%d:Lock()", n) |
| case 1: |
| rw.RLock() |
| fmt.Fprintf(out, "%d:RLock()", n) |
| case 2: |
| rw.RLocker().Lock() |
| fmt.Fprintf(out, "%d:RLocker().Lock()", n) |
| } |
| switch n % 3 { |
| case 0: |
| rw.Unlock() |
| fmt.Fprintf(out, "%d:Unlock()", n) |
| case 1: |
| rw.RUnlock() |
| fmt.Fprintf(out, "%d:RUnlock()", n) |
| case 2: |
| rw.RLocker().Unlock() |
| fmt.Fprintf(out, "%d:RLocker().Unlock()", n) |
| } |
| } |
| } |
| |
| // TestRWMutex runs rwMutexThreadClosure() without systematically |
| // testing concurrency. |
| func TestRWMutex(t *testing.T) { |
| for n := 2; n < 5; n++ { |
| thread := rwMutexThreadClosure(t, 1, n, nil) |
| thread() |
| } |
| } |
| |
| // TestRWMutexExplore runs rwMutexThreadClosure() using the framework |
| // for systematic testing of concurrency, checking that the |
| // exploration explores the correct number of interleavings. |
| func TestRWMutexExplore(t *testing.T) { |
| for n := 2; n < 5; n++ { |
| out, err := ioutil.TempFile("", "") |
| if err != nil { |
| t.Fatalf("TempFile() failed: %v", err) |
| } |
| defer os.Remove(out.Name()) |
| defer out.Close() |
| body := rwMutexThreadClosure(t, 1, n, out) |
| tester := concurrency.Init(setup, body, cleanupClosure(out)) |
| defer concurrency.Finish() |
| niterations, err := tester.Explore() |
| if err != nil { |
| t.Fatalf("Unexpected error encountered: %v", err) |
| } |
| outputs := processOutput(t, out) |
| expectedOutputs := generateRWMutexOutputs(createRWMutexSets(n)) |
| checkExpectedOutputs(t, outputs, expectedOutputs) |
| checkUnexpectedOutputs(t, outputs, expectedOutputs) |
| vlog.VI(1).Infof("Explored %v iterations.", niterations) |
| } |
| } |
| |
| // TestRWMutexExploreN runs rwMutexThreadClosure() using the framework |
| // for systematic testing of concurrency, checking that the |
| // exploration explores at most the given number of interleavings. |
| func TestRWMutexExploreN(t *testing.T) { |
| stopAfter := 100 |
| for n := 2; n < 5; n++ { |
| out, err := ioutil.TempFile("", "") |
| if err != nil { |
| t.Fatalf("TempFile() failed: %v", err) |
| } |
| defer os.Remove(out.Name()) |
| defer out.Close() |
| body := rwMutexThreadClosure(t, 1, n, out) |
| tester := concurrency.Init(setup, body, cleanupClosure(out)) |
| defer concurrency.Finish() |
| niterations, err := tester.ExploreN(stopAfter) |
| if err != nil { |
| t.Fatalf("Unexpected error encountered: %v", err) |
| } |
| outputs := processOutput(t, out) |
| expectedOutputs := generateRWMutexOutputs(createRWMutexSets(n)) |
| checkUnexpectedOutputs(t, outputs, expectedOutputs) |
| if niterations < stopAfter { |
| checkExpectedOutputs(t, outputs, expectedOutputs) |
| } |
| if niterations > stopAfter { |
| t.Fatalf("Unexpected number of iterations: expected at most %v, got %v", stopAfter, niterations) |
| } |
| vlog.VI(1).Infof("Explored %v iterations.", niterations) |
| } |
| } |
| |
| // TestRWMutexExploreFor runs rwMutexThreadClosure() using the |
| // framework for systematic testing of concurrency, checking that the |
| // exploration respects the given "soft" deadline. |
| func TestRWMutexExploreFor(t *testing.T) { |
| deadline := 10 * time.Millisecond |
| for n := 2; n < 5; n++ { |
| out, err := ioutil.TempFile("", "") |
| if err != nil { |
| t.Fatalf("TempFile() failed: %v", err) |
| } |
| defer os.Remove(out.Name()) |
| defer out.Close() |
| body := rwMutexThreadClosure(t, 1, n, out) |
| tester := concurrency.Init(setup, body, cleanupClosure(out)) |
| defer concurrency.Finish() |
| start := time.Now() |
| niterations, err := tester.ExploreFor(deadline) |
| if err != nil { |
| t.Fatalf("Unexpected error encountered: %v", err) |
| } |
| end := time.Now() |
| outputs := processOutput(t, out) |
| expectedOutputs := generateRWMutexOutputs(createRWMutexSets(n)) |
| checkUnexpectedOutputs(t, outputs, expectedOutputs) |
| if start.Add(deadline).After(end) { |
| checkExpectedOutputs(t, outputs, expectedOutputs) |
| } |
| vlog.VI(1).Infof("Explored %v iterations.", niterations) |
| } |
| } |