blob: 7069526c1e79cf83711006f38851896372602c26 [file] [log] [blame]
// 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 m sync.Mutex
// createMutexSets returns sets of thread identifiers that match the
// logic of mutexThreadClosure.
func createMutexSet(n int) map[int]bool {
locks := make(map[int]bool)
for i := 1; i <= n; i++ {
locks[i] = true
}
return locks
}
// generateMutexOutputs generates all legal outputs of sequencing
// calls to rw.Lock(), rw.Unlock(). The input identifies the threads
// that wish to invoke these functions.
func generateMutexOutputs(locks map[int]bool) []string {
if length(locks) == 0 {
return []string{""}
}
result := make([]string, 0)
for lock, ok := range locks {
if ok {
locks[lock] = false
for _, s := range generateMutexOutputs(locks) {
result = append(result, fmt.Sprintf("%d:Lock()%d:Unlock()%s", lock, lock, s))
}
locks[lock] = true
}
}
return result
}
// mutexThreadClosure folds the input arguments inside of the function body
// as the testing framework only supports functions with no arguments.
func mutexThreadClosure(t *testing.T, n, max int, out *os.File) func() {
return func() {
defer concurrency.Exit()
if n < max {
child := mutexThreadClosure(t, n+1, max, out)
concurrency.Start(child)
}
m.Lock()
fmt.Fprintf(out, "%d:Lock()", n)
m.Unlock()
fmt.Fprintf(out, "%d:Unlock()", n)
}
}
// TestMutex runs mutexThreadCLosure() without systematically testing
// concurrency.
func TestMutex(t *testing.T) {
for n := 2; n < 6; n++ {
thread := mutexThreadClosure(t, 1, n, nil)
thread()
}
}
// TestMutexExplore runs mutexThreadClosure() using the framework for systematic
// testing of concurrency, checking that the exploration explores the
// correct number of interleavings.
func TestMutexExplore(t *testing.T) {
for n := 2; n < 6; n++ {
out, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("TempFile() failed: %v", err)
}
defer os.Remove(out.Name())
defer out.Close()
body := mutexThreadClosure(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 := generateMutexOutputs(createMutexSet(n))
checkExpectedOutputs(t, outputs, expectedOutputs)
checkUnexpectedOutputs(t, outputs, expectedOutputs)
vlog.VI(1).Infof("Explored %v iterations.", niterations)
}
}
// TestMutexExploreN runs mutexThreadClosure() using the framework for
// systematic testing of concurrency, checking that the exploration
// explores at most the given number of interleavings.
func TestMutexExploreN(t *testing.T) {
for n := 2; n < 6; n++ {
out, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("TempFile() failed: %v", err)
}
defer os.Remove(out.Name())
defer out.Close()
body := mutexThreadClosure(t, 1, n, out)
tester := concurrency.Init(setup, body, cleanupClosure(out))
defer concurrency.Finish()
stopAfter := 100
niterations, err := tester.ExploreN(stopAfter)
if err != nil {
t.Fatalf("Unexpected error encountered: %v", err)
}
outputs := processOutput(t, out)
expectedOutputs := generateMutexOutputs(createMutexSet(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)
}
}
// TestMutexExploreFor runs mutexThreadClosure() using the framework
// for systematic testing of concurrency, checking that the
// exploration respects the given "soft" deadline.
func TestMutexExploreFor(t *testing.T) {
for n := 2; n < 6; n++ {
out, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("TempFile() failed: %v", err)
}
defer os.Remove(out.Name())
defer out.Close()
body := mutexThreadClosure(t, 1, n, out)
tester := concurrency.Init(setup, body, cleanupClosure(out))
defer concurrency.Finish()
start := time.Now()
deadline := 10 * time.Millisecond
niterations, err := tester.ExploreFor(deadline)
end := time.Now()
if err != nil {
t.Fatalf("Unexpected error encountered: %v", err)
}
outputs := processOutput(t, out)
expectedOutputs := generateMutexOutputs(createMutexSet(n))
checkUnexpectedOutputs(t, outputs, expectedOutputs)
if start.Add(deadline).After(end) {
checkExpectedOutputs(t, outputs, expectedOutputs)
}
vlog.VI(1).Infof("Explored %v iterations.", niterations)
}
}