Skip to content

Commit 02f8ef2

Browse files
holimanshekhirin
authored andcommitted
accounts/keystore: faster tests (ethereum#25827)
This PR removes some optimistic tests -- a'la "do something, wait a while, and hope it has trickled through and continue" -- and instead uses some introspection to ensure that prerequisites are met.
1 parent 87b2bef commit 02f8ef2

File tree

6 files changed

+111
-76
lines changed

6 files changed

+111
-76
lines changed

accounts/keystore/account_cache.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ func (ac *accountCache) deleteByFile(path string) {
146146
}
147147
}
148148

149+
// watcherStarted returns true if the watcher loop started running (even if it
150+
// has since also ended).
151+
func (ac *accountCache) watcherStarted() bool {
152+
ac.mu.Lock()
153+
defer ac.mu.Unlock()
154+
return ac.watcher.running || ac.watcher.runEnded
155+
}
156+
149157
func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account {
150158
for i := range slice {
151159
if slice[i] == elem {

accounts/keystore/account_cache_test.go

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,48 @@ var (
5050
}
5151
)
5252

53+
// waitWatcherStarts waits up to 1s for the keystore watcher to start.
54+
func waitWatcherStart(ks *KeyStore) bool {
55+
// On systems where file watch is not supported, just return "ok".
56+
if !ks.cache.watcher.enabled() {
57+
return true
58+
}
59+
// The watcher should start, and then exit.
60+
for t0 := time.Now(); time.Since(t0) < 1*time.Second; time.Sleep(100 * time.Millisecond) {
61+
if ks.cache.watcherStarted() {
62+
return true
63+
}
64+
}
65+
return false
66+
}
67+
68+
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
69+
var list []accounts.Account
70+
for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(200 * time.Millisecond) {
71+
list = ks.Accounts()
72+
if reflect.DeepEqual(list, wantAccounts) {
73+
// ks should have also received change notifications
74+
select {
75+
case <-ks.changes:
76+
default:
77+
return fmt.Errorf("wasn't notified of new accounts")
78+
}
79+
return nil
80+
}
81+
}
82+
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
83+
}
84+
5385
func TestWatchNewFile(t *testing.T) {
5486
t.Parallel()
5587

5688
dir, ks := tmpKeyStore(t, false)
5789

5890
// Ensure the watcher is started before adding any files.
5991
ks.Accounts()
60-
time.Sleep(1000 * time.Millisecond)
61-
92+
if !waitWatcherStart(ks) {
93+
t.Fatal("keystore watcher didn't start in time")
94+
}
6295
// Move in the files.
6396
wantAccounts := make([]accounts.Account, len(cachetestAccounts))
6497
for i := range cachetestAccounts {
@@ -72,37 +105,25 @@ func TestWatchNewFile(t *testing.T) {
72105
}
73106

74107
// ks should see the accounts.
75-
var list []accounts.Account
76-
for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 {
77-
list = ks.Accounts()
78-
if reflect.DeepEqual(list, wantAccounts) {
79-
// ks should have also received change notifications
80-
select {
81-
case <-ks.changes:
82-
default:
83-
t.Fatalf("wasn't notified of new accounts")
84-
}
85-
return
86-
}
87-
time.Sleep(d)
108+
if err := waitForAccounts(wantAccounts, ks); err != nil {
109+
t.Error(err)
88110
}
89-
t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts))
90111
}
91112

92113
func TestWatchNoDir(t *testing.T) {
93114
t.Parallel()
94-
95115
// Create ks but not the directory that it watches.
96116
rand.Seed(time.Now().UnixNano())
97117
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watchnodir-test-%d-%d", os.Getpid(), rand.Int()))
98118
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
99-
100119
list := ks.Accounts()
101120
if len(list) > 0 {
102121
t.Error("initial account list not empty:", list)
103122
}
104-
time.Sleep(100 * time.Millisecond)
105-
123+
// The watcher should start, and then exit.
124+
if !waitWatcherStart(ks) {
125+
t.Fatal("keystore watcher didn't start in time")
126+
}
106127
// Create the directory and copy a key file into it.
107128
os.MkdirAll(dir, 0700)
108129
defer os.RemoveAll(dir)
@@ -295,24 +316,6 @@ func TestCacheFind(t *testing.T) {
295316
}
296317
}
297318

298-
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
299-
var list []accounts.Account
300-
for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
301-
list = ks.Accounts()
302-
if reflect.DeepEqual(list, wantAccounts) {
303-
// ks should have also received change notifications
304-
select {
305-
case <-ks.changes:
306-
default:
307-
return fmt.Errorf("wasn't notified of new accounts")
308-
}
309-
return nil
310-
}
311-
time.Sleep(d)
312-
}
313-
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
314-
}
315-
316319
// TestUpdatedKeyfileContents tests that updating the contents of a keystore file
317320
// is noticed by the watcher, and the account cache is updated accordingly
318321
func TestUpdatedKeyfileContents(t *testing.T) {
@@ -327,8 +330,9 @@ func TestUpdatedKeyfileContents(t *testing.T) {
327330
if len(list) > 0 {
328331
t.Error("initial account list not empty:", list)
329332
}
330-
time.Sleep(100 * time.Millisecond)
331-
333+
if !waitWatcherStart(ks) {
334+
t.Fatal("keystore watcher didn't start in time")
335+
}
332336
// Create the directory and copy a key file into it.
333337
os.MkdirAll(dir, 0700)
334338
defer os.RemoveAll(dir)
@@ -346,9 +350,8 @@ func TestUpdatedKeyfileContents(t *testing.T) {
346350
t.Error(err)
347351
return
348352
}
349-
350353
// needed so that modTime of `file` is different to its current value after forceCopyFile
351-
time.Sleep(1000 * time.Millisecond)
354+
time.Sleep(time.Second)
352355

353356
// Now replace file contents
354357
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
@@ -364,7 +367,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
364367
}
365368

366369
// needed so that modTime of `file` is different to its current value after forceCopyFile
367-
time.Sleep(1000 * time.Millisecond)
370+
time.Sleep(time.Second)
368371

369372
// Now replace file contents again
370373
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
@@ -380,7 +383,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
380383
}
381384

382385
// needed so that modTime of `file` is different to its current value after os.WriteFile
383-
time.Sleep(1000 * time.Millisecond)
386+
time.Sleep(time.Second)
384387

385388
// Now replace file contents with crap
386389
if err := os.WriteFile(file, []byte("foo"), 0600); err != nil {

accounts/keystore/keystore.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,14 @@ func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (account
498498
return a, nil
499499
}
500500

501+
// isUpdating returns whether the event notification loop is running.
502+
// This method is mainly meant for tests.
503+
func (ks *KeyStore) isUpdating() bool {
504+
ks.mu.RLock()
505+
defer ks.mu.RUnlock()
506+
return ks.updating
507+
}
508+
501509
// zeroKey zeroes a private key in memory.
502510
func zeroKey(k *ecdsa.PrivateKey) {
503511
b := k.D.Bits()

accounts/keystore/keystore_test.go

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ func TestSignWithPassphrase(t *testing.T) {
113113
}
114114

115115
func TestTimedUnlock(t *testing.T) {
116+
t.Parallel()
116117
_, ks := tmpKeyStore(t, true)
117118

118119
pass := "foo"
@@ -147,6 +148,7 @@ func TestTimedUnlock(t *testing.T) {
147148
}
148149

149150
func TestOverrideUnlock(t *testing.T) {
151+
t.Parallel()
150152
_, ks := tmpKeyStore(t, false)
151153

152154
pass := "foo"
@@ -187,6 +189,7 @@ func TestOverrideUnlock(t *testing.T) {
187189

188190
// This test should fail under -race if signing races the expiration goroutine.
189191
func TestSignRace(t *testing.T) {
192+
t.Parallel()
190193
_, ks := tmpKeyStore(t, false)
191194

192195
// Create a test account.
@@ -211,19 +214,33 @@ func TestSignRace(t *testing.T) {
211214
t.Errorf("Account did not lock within the timeout")
212215
}
213216

217+
// waitForKsUpdating waits until the updating-status of the ks reaches the
218+
// desired wantStatus.
219+
// It waits for a maximum time of maxTime, and returns false if it does not
220+
// finish in time
221+
func waitForKsUpdating(t *testing.T, ks *KeyStore, wantStatus bool, maxTime time.Duration) bool {
222+
t.Helper()
223+
// Wait max 250 ms, then return false
224+
for t0 := time.Now(); time.Since(t0) < maxTime; {
225+
if ks.isUpdating() == wantStatus {
226+
return true
227+
}
228+
time.Sleep(25 * time.Millisecond)
229+
}
230+
return false
231+
}
232+
214233
// Tests that the wallet notifier loop starts and stops correctly based on the
215234
// addition and removal of wallet event subscriptions.
216235
func TestWalletNotifierLifecycle(t *testing.T) {
236+
t.Parallel()
217237
// Create a temporary keystore to test with
218238
_, ks := tmpKeyStore(t, false)
219239

220240
// Ensure that the notification updater is not running yet
221241
time.Sleep(250 * time.Millisecond)
222-
ks.mu.RLock()
223-
updating := ks.updating
224-
ks.mu.RUnlock()
225242

226-
if updating {
243+
if ks.isUpdating() {
227244
t.Errorf("wallet notifier running without subscribers")
228245
}
229246
// Subscribe to the wallet feed and ensure the updater boots up
@@ -233,38 +250,26 @@ func TestWalletNotifierLifecycle(t *testing.T) {
233250
for i := 0; i < len(subs); i++ {
234251
// Create a new subscription
235252
subs[i] = ks.Subscribe(updates)
236-
237-
// Ensure the notifier comes online
238-
time.Sleep(250 * time.Millisecond)
239-
ks.mu.RLock()
240-
updating = ks.updating
241-
ks.mu.RUnlock()
242-
243-
if !updating {
253+
if !waitForKsUpdating(t, ks, true, 250*time.Millisecond) {
244254
t.Errorf("sub %d: wallet notifier not running after subscription", i)
245255
}
246256
}
247-
// Unsubscribe and ensure the updater terminates eventually
248-
for i := 0; i < len(subs); i++ {
257+
// Close all but one sub
258+
for i := 0; i < len(subs)-1; i++ {
249259
// Close an existing subscription
250260
subs[i].Unsubscribe()
261+
}
262+
// Check that it is still running
263+
time.Sleep(250 * time.Millisecond)
251264

252-
// Ensure the notifier shuts down at and only at the last close
253-
for k := 0; k < int(walletRefreshCycle/(250*time.Millisecond))+2; k++ {
254-
ks.mu.RLock()
255-
updating = ks.updating
256-
ks.mu.RUnlock()
257-
258-
if i < len(subs)-1 && !updating {
259-
t.Fatalf("sub %d: event notifier stopped prematurely", i)
260-
}
261-
if i == len(subs)-1 && !updating {
262-
return
263-
}
264-
time.Sleep(250 * time.Millisecond)
265-
}
265+
if !ks.isUpdating() {
266+
t.Fatal("event notifier stopped prematurely")
267+
}
268+
// Unsubscribe the last one and ensure the updater terminates eventually.
269+
subs[len(subs)-1].Unsubscribe()
270+
if !waitForKsUpdating(t, ks, false, 4*time.Second) {
271+
t.Errorf("wallet notifier didn't terminate after unsubscribe")
266272
}
267-
t.Errorf("wallet notifier didn't terminate after unsubscribe")
268273
}
269274

270275
type walletEvent struct {

accounts/keystore/watch.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ import (
2828

2929
type watcher struct {
3030
ac *accountCache
31-
starting bool
32-
running bool
31+
running bool // set to true when runloop begins
32+
runEnded bool // set to true when runloop ends
33+
starting bool // set to true prior to runloop starting
3334
ev chan notify.EventInfo
3435
quit chan struct{}
3536
}
@@ -42,6 +43,9 @@ func newWatcher(ac *accountCache) *watcher {
4243
}
4344
}
4445

46+
// enabled returns false on systems not supported.
47+
func (*watcher) enabled() bool { return true }
48+
4549
// starts the watcher loop in the background.
4650
// Start a watcher in the background if that's not already in progress.
4751
// The caller must hold w.ac.mu.
@@ -62,6 +66,7 @@ func (w *watcher) loop() {
6266
w.ac.mu.Lock()
6367
w.running = false
6468
w.starting = false
69+
w.runEnded = true
6570
w.ac.mu.Unlock()
6671
}()
6772
logger := log.New("path", w.ac.keydir)

accounts/keystore/watch_fallback.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@
2222

2323
package keystore
2424

25-
type watcher struct{ running bool }
25+
type watcher struct {
26+
running bool
27+
runEnded bool
28+
}
2629

2730
func newWatcher(*accountCache) *watcher { return new(watcher) }
2831
func (*watcher) start() {}
2932
func (*watcher) close() {}
33+
34+
// enabled returns false on systems not supported.
35+
func (*watcher) enabled() bool { return false }

0 commit comments

Comments
 (0)