blob: ff21dd9227c06e84cc910f7d8a4e91e35d7e4b6c [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.
package context_test
import (
"bytes"
"fmt"
"sync"
"testing"
"time"
"v.io/v23/context"
"v.io/v23/logging"
)
func testCancel(t *testing.T, ctx *context.T, cancel context.CancelFunc) {
select {
case <-ctx.Done():
t.Errorf("Done closed when deadline not yet passed")
default:
}
ch := make(chan bool, 0)
go func() {
cancel()
close(ch)
}()
select {
case <-ch:
case <-time.After(3 * time.Second):
t.Fatal("timed out waiting for cancel.")
}
select {
case <-ctx.Done():
case <-time.After(3 * time.Second):
t.Fatal("timed out waiting for cancellation.")
}
if err := ctx.Err(); err != context.Canceled {
t.Errorf("Unexpected error want %v, got %v", context.Canceled, err)
}
}
func TestRootContext(t *testing.T) {
var ctx *context.T
if ctx.Initialized() {
t.Error("Nil context should be uninitialized")
}
if got := ctx.Err(); got != nil {
t.Errorf("Expected nil error, got: %v", got)
}
ctx = &context.T{}
if ctx.Initialized() {
t.Error("Zero context should be uninitialized")
}
if got := ctx.Err(); got != nil {
t.Errorf("Expected nil error, got: %v", got)
}
}
func TestCancelContext(t *testing.T) {
root, _ := context.RootContext()
ctx, cancel := context.WithCancel(root)
testCancel(t, ctx, cancel)
// Test cancelling a cancel context which is the child
// of a cancellable context.
parent, _ := context.WithCancel(root)
child, cancel := context.WithCancel(parent)
cancel()
<-child.Done()
// Test adding a cancellable child context after the parent is
// already cancelled.
parent, cancel = context.WithCancel(root)
cancel()
child, _ = context.WithCancel(parent)
<-child.Done() // The child should have been cancelled right away.
}
func TestMultiLevelCancelContext(t *testing.T) {
root, _ := context.RootContext()
c0, c0Cancel := context.WithCancel(root)
c1, _ := context.WithCancel(c0)
c2, _ := context.WithCancel(c1)
c3, _ := context.WithCancel(c2)
testCancel(t, c3, c0Cancel)
}
func testDeadline(t *testing.T, ctx *context.T, start time.Time, desiredTimeout time.Duration) {
<-ctx.Done()
if delta := time.Now().Sub(start); delta < desiredTimeout {
t.Errorf("Deadline too short want %s got %s", desiredTimeout, delta)
}
if err := ctx.Err(); err != context.DeadlineExceeded {
t.Errorf("Unexpected error want %s, got %s", context.DeadlineExceeded, err)
}
}
func TestDeadlineContext(t *testing.T) {
cases := []time.Duration{
3 * time.Millisecond,
0,
}
rootCtx, _ := context.RootContext()
cancelCtx, _ := context.WithCancel(rootCtx)
deadlineCtx, _ := context.WithDeadline(rootCtx, time.Now().Add(time.Hour))
for _, desiredTimeout := range cases {
// Test all the various ways of getting deadline contexts.
start := time.Now()
ctx, _ := context.WithDeadline(rootCtx, start.Add(desiredTimeout))
testDeadline(t, ctx, start, desiredTimeout)
start = time.Now()
ctx, _ = context.WithDeadline(cancelCtx, start.Add(desiredTimeout))
testDeadline(t, ctx, start, desiredTimeout)
start = time.Now()
ctx, _ = context.WithDeadline(deadlineCtx, start.Add(desiredTimeout))
testDeadline(t, ctx, start, desiredTimeout)
start = time.Now()
ctx, _ = context.WithTimeout(rootCtx, desiredTimeout)
testDeadline(t, ctx, start, desiredTimeout)
start = time.Now()
ctx, _ = context.WithTimeout(cancelCtx, desiredTimeout)
testDeadline(t, ctx, start, desiredTimeout)
start = time.Now()
ctx, _ = context.WithTimeout(deadlineCtx, desiredTimeout)
testDeadline(t, ctx, start, desiredTimeout)
}
ctx, cancel := context.WithDeadline(rootCtx, time.Now().Add(100*time.Hour))
testCancel(t, ctx, cancel)
}
func TestDeadlineContextWithRace(t *testing.T) {
root, _ := context.RootContext()
ctx, cancel := context.WithDeadline(root, time.Now().Add(100*time.Hour))
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
cancel()
wg.Done()
}()
}
wg.Wait()
<-ctx.Done()
if err := ctx.Err(); err != context.Canceled {
t.Errorf("Unexpected error want %v, got %v", context.Canceled, err)
}
}
func TestValueContext(t *testing.T) {
type testContextKey int
const (
key1 = testContextKey(iota)
key2
key3
key4
)
const (
val1 = iota
val2
val3
)
root, _ := context.RootContext()
ctx1 := context.WithValue(root, key1, val1)
ctx2 := context.WithValue(ctx1, key2, val2)
ctx3 := context.WithValue(ctx2, key3, val3)
expected := map[interface{}]interface{}{
key1: val1,
key2: val2,
key3: val3,
key4: nil,
}
for k, v := range expected {
if got := ctx3.Value(k); got != v {
t.Errorf("Got wrong value for %v: want %v got %v", k, v, got)
}
}
}
func TestRootCancel(t *testing.T) {
root, rootcancel := context.RootContext()
a, acancel := context.WithCancel(root)
b := context.WithValue(a, "key", "value")
c, ccancel := context.WithRootCancel(b)
d, _ := context.WithCancel(c)
e, _ := context.WithRootCancel(b)
if s, ok := d.Value("key").(string); !ok || s != "value" {
t.Error("Lost a value but shouldn't have.")
}
// If we cancel a, b will get canceled but c will not.
acancel()
<-a.Done()
<-b.Done()
select {
case <-c.Done():
t.Error("C should not yet be canceled.")
case <-e.Done():
t.Error("E should not yet be canceled.")
case <-time.After(100 * time.Millisecond):
}
// Cancelling c should still cancel d.
ccancel()
<-c.Done()
<-d.Done()
// Cancelling the root should cancel e.
rootcancel()
<-root.Done()
<-e.Done()
}
type stringLogger struct {
bytes.Buffer
}
func (*stringLogger) Info(args ...interface{}) {}
func (sl *stringLogger) InfoDepth(depth int, args ...interface{}) { sl.WriteString(fmt.Sprint(args...)) }
func (sl *stringLogger) Infof(format string, args ...interface{}) {}
func (*stringLogger) InfoStack(all bool) {}
func (*stringLogger) Error(args ...interface{}) {}
func (*stringLogger) ErrorDepth(depth int, args ...interface{}) {}
func (*stringLogger) Errorf(format string, args ...interface{}) {}
func (*stringLogger) Fatal(args ...interface{}) {}
func (*stringLogger) FatalDepth(depth int, args ...interface{}) {}
func (*stringLogger) Fatalf(format string, args ...interface{}) {}
func (*stringLogger) Panic(args ...interface{}) {}
func (*stringLogger) PanicDepth(depth int, args ...interface{}) {}
func (*stringLogger) Panicf(format string, args ...interface{}) {}
func (*stringLogger) V(level int) bool { return false }
func (*stringLogger) VDepth(depth int, level int) bool { return false }
func (d *stringLogger) VI(level int) interface {
Info(args ...interface{})
Infof(format string, args ...interface{})
InfoDepth(depth int, args ...interface{})
InfoStack(all bool)
} {
return d
}
func (d *stringLogger) VIDepth(depth int, level int) interface {
Info(args ...interface{})
Infof(format string, args ...interface{})
InfoDepth(depth int, args ...interface{})
InfoStack(all bool)
} {
return d
}
func (*stringLogger) FlushLog() {}
func (*stringLogger) LogDir() string { return "" }
func (*stringLogger) Stats() (Info, Error struct{ Lines, Bytes int64 }) {
return struct{ Lines, Bytes int64 }{0, 0}, struct{ Lines, Bytes int64 }{0, 0}
}
func (*stringLogger) ConfigureFromFlags() error { return nil }
func (*stringLogger) ExplicitlySetFlags() map[string]string { return nil }
func TestLogging(t *testing.T) {
root, rootcancel := context.RootContext()
var _ logging.Logger = root
root.Infof("this message should be silently discarded")
logger := &stringLogger{}
ctx := context.WithLogger(root, logger)
ctx.Infof("Oh, %s", "hello there")
if got, want := logger.String(), "Oh, hello there"; got != want {
t.Fatalf("got %v, want %v", got, want)
}
rootcancel()
}