kubernetes/contrib/mesos/pkg/queue/delay_test.go
2016-06-29 17:47:36 -07:00

409 lines
8.8 KiB
Go

/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package queue
import (
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
const (
tolerance = 100 * time.Millisecond // go time delays aren't perfect, this is our tolerance for errors WRT expected timeouts
)
func timedPriority(t time.Time) Priority {
return Priority{ts: t}
}
func TestPQ(t *testing.T) {
t.Parallel()
var pq priorityQueue
if pq.Len() != 0 {
t.Fatalf("pq should be empty")
}
now := timedPriority(time.Now())
now2 := timedPriority(now.ts.Add(2 * time.Second))
pq.Push(&qitem{priority: now2})
if pq.Len() != 1 {
t.Fatalf("pq.len should be 1")
}
x := pq.Pop()
if x == nil {
t.Fatalf("x is nil")
}
if pq.Len() != 0 {
t.Fatalf("pq should be empty")
}
item := x.(*qitem)
if !item.priority.Equal(now2) {
t.Fatalf("item.priority != now2")
}
pq.Push(&qitem{priority: now2})
pq.Push(&qitem{priority: now2})
pq.Push(&qitem{priority: now2})
pq.Push(&qitem{priority: now2})
pq.Push(&qitem{priority: now2})
pq.Pop()
pq.Pop()
pq.Pop()
pq.Pop()
pq.Pop()
if pq.Len() != 0 {
t.Fatalf("pq should be empty")
}
now4 := timedPriority(now.ts.Add(4 * time.Second))
now6 := timedPriority(now.ts.Add(4 * time.Second))
pq.Push(&qitem{priority: now2})
pq.Push(&qitem{priority: now4})
pq.Push(&qitem{priority: now6})
pq.Swap(0, 2)
if !pq[0].priority.Equal(now6) || !pq[2].priority.Equal(now2) {
t.Fatalf("swap failed")
}
if pq.Less(1, 2) {
t.Fatalf("now4 < now2")
}
}
func TestPopEmptyPQ(t *testing.T) {
t.Parallel()
defer func() {
if r := recover(); r == nil {
t.Fatalf("Expected panic from popping an empty PQ")
}
}()
var pq priorityQueue
pq.Pop()
}
type testjob struct {
d time.Duration
t time.Time
deadline *time.Time
uid string
instance int
}
func (j *testjob) GetDelay() time.Duration {
return j.d
}
func (j testjob) GetUID() string {
return j.uid
}
func (td *testjob) Deadline() (deadline time.Time, ok bool) {
if td.deadline != nil {
return *td.deadline, true
} else {
return time.Now(), false
}
}
func TestDQ_sanity_check(t *testing.T) {
t.Parallel()
dq := NewDelayQueue()
delay := 2 * time.Second
dq.Add(&testjob{d: delay})
before := time.Now()
x := dq.Pop()
now := time.Now()
waitPeriod := now.Sub(before)
if waitPeriod+tolerance < delay {
t.Fatalf("delay too short: %v, expected: %v", waitPeriod, delay)
}
if x == nil {
t.Fatalf("x is nil")
}
item := x.(*testjob)
if item.d != delay {
t.Fatalf("d != delay")
}
}
func TestDQ_Offer(t *testing.T) {
t.Parallel()
assert := assert.New(t)
dq := NewDelayQueue()
delay := time.Second
added := dq.Offer(&testjob{})
if added {
t.Fatalf("DelayQueue should not add offered job without deadline")
}
deadline := time.Now().Add(delay)
added = dq.Offer(&testjob{deadline: &deadline})
if !added {
t.Fatalf("DelayQueue should add offered job with deadline")
}
before := time.Now()
x := dq.Pop()
now := time.Now()
waitPeriod := now.Sub(before)
if waitPeriod+tolerance < delay {
t.Fatalf("delay too short: %v, expected: %v", waitPeriod, delay)
}
assert.NotNil(x)
assert.Equal(x.(*testjob).deadline, &deadline)
}
func TestDQ_ordered_add_pop(t *testing.T) {
t.Parallel()
dq := NewDelayQueue()
dq.Add(&testjob{d: 2 * time.Second})
dq.Add(&testjob{d: 1 * time.Second})
dq.Add(&testjob{d: 3 * time.Second})
var finished [3]*testjob
before := time.Now()
idx := int32(-1)
ch := make(chan bool, 3)
//TODO: replace with `for range finished` once Go 1.3 support is dropped
for n := 0; n < len(finished); n++ {
go func() {
var ok bool
x := dq.Pop()
i := atomic.AddInt32(&idx, 1)
if finished[i], ok = x.(*testjob); !ok {
t.Fatalf("expected a *testjob, not %v", x)
}
finished[i].t = time.Now()
ch <- true
}()
}
<-ch
<-ch
<-ch
after := time.Now()
totalDelay := after.Sub(before)
if totalDelay+tolerance < (3 * time.Second) {
t.Fatalf("totalDelay < 3s: %v", totalDelay)
}
for i, v := range finished {
if v == nil {
t.Fatalf("task %d was nil", i)
}
expected := time.Duration(i+1) * time.Second
if v.d != expected {
t.Fatalf("task %d had delay-priority %v, expected %v", i, v.d, expected)
}
actualDelay := v.t.Sub(before)
if actualDelay+tolerance < v.d {
t.Fatalf("task %d had actual-delay %v < expected delay %v", i, actualDelay, v.d)
}
}
}
func TestDQ_always_pop_earliest_deadline(t *testing.T) {
t.Skip("disabled due to flakiness; see #11857")
t.Parallel()
// add a testjob with delay of 2s
// spawn a func f1 that attempts to Pop() and wait for f1 to begin
// add a testjob with a delay of 1s
// check that the func f1 actually popped the 1s task (not the 2s task)
dq := NewDelayQueue()
dq.Add(&testjob{d: 2 * time.Second})
ch := make(chan *testjob)
started := make(chan bool)
go func() {
started <- true
x := dq.Pop()
job := x.(*testjob)
job.t = time.Now()
ch <- job
}()
<-started
time.Sleep(500 * time.Millisecond) // give plently of time for Pop() to enter
expected := 1 * time.Second
dq.Add(&testjob{d: expected})
job := <-ch
if expected != job.d {
t.Fatalf("Expected delay-prority of %v got instead got %v", expected, job.d)
}
job = dq.Pop().(*testjob)
expected = 2 * time.Second
if expected != job.d {
t.Fatalf("Expected delay-prority of %v got instead got %v", expected, job.d)
}
}
func TestDQ_always_pop_earliest_deadline_multi(t *testing.T) {
t.Skip("disabled due to flakiness; see #11821")
t.Parallel()
dq := NewDelayQueue()
dq.Add(&testjob{d: 2 * time.Second})
ch := make(chan *testjob)
multi := 10
started := make(chan bool, multi)
go func() {
started <- true
for i := 0; i < multi; i++ {
x := dq.Pop()
job := x.(*testjob)
job.t = time.Now()
ch <- job
}
}()
<-started
time.Sleep(500 * time.Millisecond) // give plently of time for Pop() to enter
expected := 1 * time.Second
for i := 0; i < multi; i++ {
dq.Add(&testjob{d: expected})
}
for i := 0; i < multi; i++ {
job := <-ch
if expected != job.d {
t.Fatalf("Expected delay-prority of %v got instead got %v", expected, job.d)
}
}
job := dq.Pop().(*testjob)
expected = 2 * time.Second
if expected != job.d {
t.Fatalf("Expected delay-prority of %v got instead got %v", expected, job.d)
}
}
func TestDQ_negative_delay(t *testing.T) {
t.Parallel()
dq := NewDelayQueue()
delay := -2 * time.Second
dq.Add(&testjob{d: delay})
before := time.Now()
x := dq.Pop()
now := time.Now()
waitPeriod := now.Sub(before)
if waitPeriod > tolerance {
t.Fatalf("delay too long: %v, expected something less than: %v", waitPeriod, tolerance)
}
if x == nil {
t.Fatalf("x is nil")
}
item := x.(*testjob)
if item.d != delay {
t.Fatalf("d != delay")
}
}
func TestDFIFO_sanity_check(t *testing.T) {
t.Parallel()
assert := assert.New(t)
df := NewDelayFIFO()
delay := 2 * time.Second
df.Add(&testjob{d: delay, uid: "a", instance: 1}, ReplaceExisting)
assert.True(df.ContainedIDs().Has("a"))
// re-add by ReplaceExisting
df.Add(&testjob{d: delay, uid: "a", instance: 2}, ReplaceExisting)
assert.True(df.ContainedIDs().Has("a"))
a, ok := df.Get("a")
assert.True(ok)
assert.Equal(a.(*testjob).instance, 2)
// re-add by KeepExisting
df.Add(&testjob{d: delay, uid: "a", instance: 3}, KeepExisting)
assert.True(df.ContainedIDs().Has("a"))
a, ok = df.Get("a")
assert.True(ok)
assert.Equal(a.(*testjob).instance, 2)
// pop last
before := time.Now()
x := df.Pop(WithoutCancel())
assert.Equal(a.(*testjob).instance, 2)
now := time.Now()
waitPeriod := now.Sub(before)
if waitPeriod+tolerance < delay {
t.Fatalf("delay too short: %v, expected: %v", waitPeriod, delay)
}
if x == nil {
t.Fatalf("x is nil")
}
item := x.(*testjob)
if item.d != delay {
t.Fatalf("d != delay")
}
}
func TestDFIFO_Offer(t *testing.T) {
t.Parallel()
assert := assert.New(t)
dq := NewDelayFIFO()
delay := time.Second
added := dq.Offer(&testjob{instance: 1}, ReplaceExisting)
if added {
t.Fatalf("DelayFIFO should not add offered job without deadline")
}
deadline := time.Now().Add(delay)
added = dq.Offer(&testjob{deadline: &deadline, instance: 2}, ReplaceExisting)
if !added {
t.Fatalf("DelayFIFO should add offered job with deadline")
}
before := time.Now()
x := dq.Pop(WithoutCancel())
now := time.Now()
waitPeriod := now.Sub(before)
if waitPeriod+tolerance < delay {
t.Fatalf("delay too short: %v, expected: %v", waitPeriod, delay)
}
assert.NotNil(x)
assert.Equal(x.(*testjob).instance, 2)
}