From 47c2421f7b10e9ae0fe2b30b2fe6f23aa5403f10 Mon Sep 17 00:00:00 2001 From: Jonathan Boulle Date: Thu, 16 Oct 2014 12:06:47 -0700 Subject: [PATCH] godeps: add clockwork --- Godeps/Godeps.json | 4 + .../github.com/jonboulle/clockwork/.gitignore | 25 +++ .../github.com/jonboulle/clockwork/LICENSE | 201 ++++++++++++++++++ .../github.com/jonboulle/clockwork/README.md | 58 +++++ .../jonboulle/clockwork/clockwork.go | 161 ++++++++++++++ .../jonboulle/clockwork/clockwork_test.go | 106 +++++++++ .../jonboulle/clockwork/example_test.go | 49 +++++ discovery/discovery.go | 2 +- discovery/discovery_test.go | 2 +- 9 files changed, 606 insertions(+), 2 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/jonboulle/clockwork/.gitignore create mode 100644 Godeps/_workspace/src/github.com/jonboulle/clockwork/LICENSE create mode 100644 Godeps/_workspace/src/github.com/jonboulle/clockwork/README.md create mode 100644 Godeps/_workspace/src/github.com/jonboulle/clockwork/clockwork.go create mode 100644 Godeps/_workspace/src/github.com/jonboulle/clockwork/clockwork_test.go create mode 100644 Godeps/_workspace/src/github.com/jonboulle/clockwork/example_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 4c61d34e8..44d4010fd 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -14,6 +14,10 @@ "ImportPath": "code.google.com/p/gogoprotobuf/proto", "Rev": "7fd1620f09261338b6b1ca1289ace83aee0ec946" }, + { + "ImportPath": "github.com/jonboulle/clockwork", + "Rev": "46fde511e4fda2f685792de1700f20e1c45bfe41" + }, { "ImportPath": "github.com/stretchr/testify/assert", "Rev": "9cc77fa25329013ce07362c7742952ff887361f2" diff --git a/Godeps/_workspace/src/github.com/jonboulle/clockwork/.gitignore b/Godeps/_workspace/src/github.com/jonboulle/clockwork/.gitignore new file mode 100644 index 000000000..010c242bd --- /dev/null +++ b/Godeps/_workspace/src/github.com/jonboulle/clockwork/.gitignore @@ -0,0 +1,25 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test + +*.swp diff --git a/Godeps/_workspace/src/github.com/jonboulle/clockwork/LICENSE b/Godeps/_workspace/src/github.com/jonboulle/clockwork/LICENSE new file mode 100644 index 000000000..5c304d1a4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jonboulle/clockwork/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/Godeps/_workspace/src/github.com/jonboulle/clockwork/README.md b/Godeps/_workspace/src/github.com/jonboulle/clockwork/README.md new file mode 100644 index 000000000..99823200f --- /dev/null +++ b/Godeps/_workspace/src/github.com/jonboulle/clockwork/README.md @@ -0,0 +1,58 @@ +clockwork +========= + +a simple fake clock for golang + +# Usage + +Replace uses of the `time` package with the `clockwork.Clock` interface instead. + +For example, instead of using `time.Sleep` directly: + +``` +func my_func() { + time.Sleep(3 * time.Second) + do_something() +} +``` + +inject a clock and use its `Sleep` method instead: + +``` +func my_func(clock clockwork.Clock) { + clock.Sleep(3 * time.Second) + do_something() +} +``` + +Now you can easily test `my_func` with a `FakeClock`: + +``` +func TestMyFunc(t *testing.T) { + c := clockwork.NewFakeClock() + + // Start our sleepy function + my_func(c) + + // Ensure we wait until my_func is sleeping + c.BlockUntil(1) + + assert_state() + + // Tick the FakeClock forward in time + c.Tick(3) + + assert_state() +} +``` + +and in production builds, simply inject the real clock instead: +``` +my_func(clockwork.NewRealClock()) +``` + +See [example_test.go](example_test.go) for a full example. + +# Credits + +Inspired by @wickman's [threaded fake clock](https://gist.github.com/wickman/3840816), and the [Golang playground](http://blog.golang.org/playground#Faking time) diff --git a/Godeps/_workspace/src/github.com/jonboulle/clockwork/clockwork.go b/Godeps/_workspace/src/github.com/jonboulle/clockwork/clockwork.go new file mode 100644 index 000000000..e2ea72ba9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jonboulle/clockwork/clockwork.go @@ -0,0 +1,161 @@ +package clockwork + +import ( + "sync" + "time" +) + +// Clock provides an interface that packages can use instead of directly +// using the time module, so that chronology-related behavior can be tested +type Clock interface { + After(d time.Duration) <-chan time.Time + Sleep(d time.Duration) + Now() time.Time +} + +// FakeClock provides an interface for a clock which can be +// manually ticked through time +type FakeClock interface { + Clock + // Tick advances the FakeClock to a new point in time, ensuring any existing + // sleepers are notified appropriately before returning + Tick(d time.Duration) + // BlockUntil will block until the FakeClock has the given number of + // sleepers (callers of Sleep or After) + BlockUntil(n int) +} + +// NewRealClock returns a Clock which simply delegates calls to the actual time +// package; it should be used by packages in production. +func NewRealClock() Clock { + return &realClock{} +} + +// NewFakeClock returns a FakeClock implementation which can be +// manually ticked through time for testing. +func NewFakeClock() FakeClock { + return &fakeClock{ + l: sync.RWMutex{}, + } +} + +type realClock struct{} + +func (rc *realClock) After(d time.Duration) <-chan time.Time { + return time.After(d) +} + +func (rc *realClock) Sleep(d time.Duration) { + time.Sleep(d) +} + +func (rc *realClock) Now() time.Time { + return time.Now() +} + +type fakeClock struct { + sleepers []*sleeper + blockers []*blocker + time time.Time + + l sync.RWMutex +} + +// sleeper represents a caller of After or Sleep +type sleeper struct { + until time.Time + done chan time.Time +} + +// blocker represents a caller of BlockUntil +type blocker struct { + count int + ch chan struct{} +} + +// After mimics time.After; it waits for the given duration to elapse on the +// fakeClock, then sends the current time on the returned channel. +func (fc *fakeClock) After(d time.Duration) <-chan time.Time { + fc.l.Lock() + defer fc.l.Unlock() + now := fc.time + done := make(chan time.Time, 1) + if d.Nanoseconds() == 0 { + // special case - trigger immediately + done <- now + } else { + // otherwise, add to the set of sleepers + s := &sleeper{ + until: now.Add(d), + done: done, + } + fc.sleepers = append(fc.sleepers, s) + // and notify any blockers + fc.blockers = notifyBlockers(fc.blockers, len(fc.sleepers)) + } + return done +} + +// notifyBlockers notifies all the blockers waiting until the +// given number of sleepers are waiting on the fakeClock. It +// returns an updated slice of blockers (i.e. those still waiting) +func notifyBlockers(blockers []*blocker, count int) (newBlockers []*blocker) { + for _, b := range blockers { + if b.count == count { + close(b.ch) + } else { + newBlockers = append(newBlockers, b) + } + } + return +} + +// Sleep blocks until the given duration has passed on the fakeClock +func (fc *fakeClock) Sleep(d time.Duration) { + <-fc.After(d) +} + +// Time returns the current time of the fakeClock +func (fc *fakeClock) Now() time.Time { + fc.l.Lock() + defer fc.l.Unlock() + return fc.time +} + +// Tick advances fakeClock to a new point in time, ensuring channels from any +// previous invocations of After are notified appropriately before returning +func (fc *fakeClock) Tick(d time.Duration) { + fc.l.Lock() + end := fc.time.Add(d) + var newSleepers []*sleeper + for _, s := range fc.sleepers { + if end.Sub(s.until) >= 0 { + s.done <- end + } else { + newSleepers = append(newSleepers, s) + } + } + fc.sleepers = newSleepers + fc.blockers = notifyBlockers(fc.blockers, len(fc.sleepers)) + fc.time = end + fc.l.Unlock() +} + +// BlockUntil will block until the fakeClock has the given number of sleepers +// (callers of Sleep or After) +func (fc *fakeClock) BlockUntil(n int) { + fc.l.Lock() + // Fast path: current number of sleepers is what we're looking for + if len(fc.sleepers) == n { + fc.l.Unlock() + return + } + // Otherwise, set up a new blocker + b := &blocker{ + count: n, + ch: make(chan struct{}), + } + fc.blockers = append(fc.blockers, b) + fc.l.Unlock() + <-b.ch +} diff --git a/Godeps/_workspace/src/github.com/jonboulle/clockwork/clockwork_test.go b/Godeps/_workspace/src/github.com/jonboulle/clockwork/clockwork_test.go new file mode 100644 index 000000000..a37eb9138 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jonboulle/clockwork/clockwork_test.go @@ -0,0 +1,106 @@ +package clockwork + +import ( + "testing" + "time" +) + +func TestFakeClockAfter(t *testing.T) { + fc := &fakeClock{} + + zero := fc.After(0) + select { + case <-zero: + default: + t.Errorf("zero did not return!") + } + one := fc.After(1) + two := fc.After(2) + six := fc.After(6) + ten := fc.After(10) + fc.Tick(1) + select { + case <-one: + default: + t.Errorf("one did not return!") + } + select { + case <-two: + t.Errorf("two returned prematurely!") + case <-six: + t.Errorf("six returned prematurely!") + case <-ten: + t.Errorf("ten returned prematurely!") + default: + } + fc.Tick(1) + select { + case <-two: + default: + t.Errorf("two did not return!") + } + select { + case <-six: + t.Errorf("six returned prematurely!") + case <-ten: + t.Errorf("ten returned prematurely!") + default: + } + fc.Tick(1) + select { + case <-six: + t.Errorf("six returned prematurely!") + case <-ten: + t.Errorf("ten returned prematurely!") + default: + } + fc.Tick(3) + select { + case <-six: + default: + t.Errorf("six did not return!") + } + select { + case <-ten: + t.Errorf("ten returned prematurely!") + default: + } + fc.Tick(100) + select { + case <-ten: + default: + t.Errorf("ten did not return!") + } +} + +func TestNotifyBlockers(t *testing.T) { + b1 := &blocker{1, make(chan struct{})} + b2 := &blocker{2, make(chan struct{})} + b3 := &blocker{5, make(chan struct{})} + b4 := &blocker{10, make(chan struct{})} + b5 := &blocker{10, make(chan struct{})} + bs := []*blocker{b1, b2, b3, b4, b5} + bs1 := notifyBlockers(bs, 2) + if n := len(bs1); n != 4 { + t.Fatalf("got %d blockers, want %d", n, 4) + } + select { + case <-b2.ch: + case <-time.After(time.Second): + t.Fatalf("timed out waiting for channel close!") + } + bs2 := notifyBlockers(bs1, 10) + if n := len(bs2); n != 2 { + t.Fatalf("got %d blockers, want %d", n, 2) + } + select { + case <-b4.ch: + case <-time.After(time.Second): + t.Fatalf("timed out waiting for channel close!") + } + select { + case <-b5.ch: + case <-time.After(time.Second): + t.Fatalf("timed out waiting for channel close!") + } +} diff --git a/Godeps/_workspace/src/github.com/jonboulle/clockwork/example_test.go b/Godeps/_workspace/src/github.com/jonboulle/clockwork/example_test.go new file mode 100644 index 000000000..fb58a7388 --- /dev/null +++ b/Godeps/_workspace/src/github.com/jonboulle/clockwork/example_test.go @@ -0,0 +1,49 @@ +package clockwork + +import ( + "sync" + "testing" + "time" +) + +// my_func is an example of a time-dependent function, using an +// injected clock +func my_func(clock Clock, i *int) { + clock.Sleep(3 * time.Second) + *i += 1 +} + +// assert_state is an example of a state assertion in a test +func assert_state(t *testing.T, i, j int) { + if i != j { + t.Fatalf("i %d, j %d", i, j) + } +} + +// TestMyFunc tests my_func's behaviour with a FakeClock +func TestMyFunc(t *testing.T) { + var i int + c := NewFakeClock() + + var wg sync.WaitGroup + wg.Add(1) + go func() { + my_func(c, &i) + wg.Done() + }() + + // Wait until my_func is actually sleeping on the clock + c.BlockUntil(1) + + // Assert the initial state + assert_state(t, i, 0) + + // Now tick the clock forward in time + c.Tick(1 * time.Hour) + + // Wait until the function completes + wg.Wait() + + // Assert the final state + assert_state(t, i, 1) +} diff --git a/discovery/discovery.go b/discovery/discovery.go index 87ecfe2d9..1c7af396a 100644 --- a/discovery/discovery.go +++ b/discovery/discovery.go @@ -14,7 +14,7 @@ import ( "time" "github.com/coreos/etcd/client" - "github.com/jonboulle/clockwork" + "github.com/coreos/etcd/Godeps/_workspace/src/github.com/jonboulle/clockwork" ) var ( diff --git a/discovery/discovery_test.go b/discovery/discovery_test.go index 831564559..5496240c6 100644 --- a/discovery/discovery_test.go +++ b/discovery/discovery_test.go @@ -13,7 +13,7 @@ import ( "time" "github.com/coreos/etcd/client" - "github.com/jonboulle/clockwork" + "github.com/coreos/etcd/Godeps/_workspace/src/github.com/jonboulle/clockwork" ) func TestProxyFuncFromEnvUnset(t *testing.T) {