@ -0,0 +1,31 @@ | |||||
package client | |||||
import ( | |||||
"time" | |||||
"github.com/pkg/errors" | |||||
) | |||||
// Wait for height will poll status at reasonable intervals until | |||||
// the block at the given height is available. | |||||
func WaitForHeight(c StatusClient, h int) error { | |||||
wait := 1 | |||||
for wait > 0 { | |||||
s, err := c.Status() | |||||
if err != nil { | |||||
return err | |||||
} | |||||
wait = h - s.LatestBlockHeight | |||||
if wait > 10 { | |||||
return errors.Errorf("Waiting for %d block... aborting", wait) | |||||
} else if wait > 0 { | |||||
// estimate of wait time.... | |||||
// wait half a second for the next block (in progress) | |||||
// plus one second for every full block | |||||
delay := time.Duration(wait-1)*time.Second + 500*time.Millisecond | |||||
time.Sleep(delay) | |||||
} | |||||
} | |||||
// guess we waited long enough | |||||
return nil | |||||
} |
@ -0,0 +1,75 @@ | |||||
package client_test | |||||
import ( | |||||
"errors" | |||||
"strings" | |||||
"testing" | |||||
"time" | |||||
"github.com/stretchr/testify/assert" | |||||
"github.com/stretchr/testify/require" | |||||
"github.com/tendermint/tendermint/rpc/client" | |||||
"github.com/tendermint/tendermint/rpc/client/mock" | |||||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | |||||
) | |||||
func TestWaitForHeight(t *testing.T) { | |||||
assert, require := assert.New(t), require.New(t) | |||||
// test with error result - immediate failure | |||||
m := &mock.StatusMock{ | |||||
Call: mock.Call{ | |||||
Error: errors.New("bye"), | |||||
}, | |||||
} | |||||
r := mock.NewStatusRecorder(m) | |||||
// connection failure always leads to error | |||||
err := client.WaitForHeight(r, 8) | |||||
require.NotNil(err) | |||||
require.Equal("bye", err.Error()) | |||||
// we called status once to check | |||||
require.Equal(1, len(r.Calls)) | |||||
// now set current block height to 10 | |||||
m.Call = mock.Call{ | |||||
Response: &ctypes.ResultStatus{LatestBlockHeight: 10}, | |||||
} | |||||
// we will not wait for more than 10 blocks | |||||
err = client.WaitForHeight(r, 40) | |||||
require.NotNil(err) | |||||
require.True(strings.Contains(err.Error(), "aborting")) | |||||
// we called status once more to check | |||||
require.Equal(2, len(r.Calls)) | |||||
// waiting for the past returns immediately | |||||
err = client.WaitForHeight(r, 5) | |||||
require.Nil(err) | |||||
// we called status once more to check | |||||
require.Equal(3, len(r.Calls)) | |||||
// next tick in background while we wait | |||||
go func() { | |||||
time.Sleep(500 * time.Millisecond) | |||||
m.Call.Response = &ctypes.ResultStatus{LatestBlockHeight: 15} | |||||
}() | |||||
// we wait for a few blocks | |||||
err = client.WaitForHeight(r, 12) | |||||
require.Nil(err) | |||||
// we called status once to check | |||||
require.Equal(5, len(r.Calls)) | |||||
pre := r.Calls[3] | |||||
require.Nil(pre.Error) | |||||
prer, ok := pre.Response.(*ctypes.ResultStatus) | |||||
require.True(ok) | |||||
assert.Equal(10, prer.LatestBlockHeight) | |||||
post := r.Calls[4] | |||||
require.Nil(post.Error) | |||||
postr, ok := post.Response.(*ctypes.ResultStatus) | |||||
require.True(ok) | |||||
assert.Equal(15, postr.LatestBlockHeight) | |||||
} |
@ -0,0 +1,55 @@ | |||||
package mock | |||||
import ( | |||||
"github.com/tendermint/tendermint/rpc/client" | |||||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | |||||
) | |||||
// StatusMock returns the result specified by the Call | |||||
type StatusMock struct { | |||||
Call | |||||
} | |||||
func (m *StatusMock) _assertStatusClient() client.StatusClient { | |||||
return m | |||||
} | |||||
func (m *StatusMock) Status() (*ctypes.ResultStatus, error) { | |||||
res, err := m.GetResponse(nil) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
return res.(*ctypes.ResultStatus), nil | |||||
} | |||||
// StatusRecorder can wrap another type (StatusMock, full client) | |||||
// and record the status calls | |||||
type StatusRecorder struct { | |||||
Client client.StatusClient | |||||
Calls []Call | |||||
} | |||||
func NewStatusRecorder(client client.StatusClient) *StatusRecorder { | |||||
return &StatusRecorder{ | |||||
Client: client, | |||||
Calls: []Call{}, | |||||
} | |||||
} | |||||
func (r *StatusRecorder) _assertStatusClient() client.StatusClient { | |||||
return r | |||||
} | |||||
func (r *StatusRecorder) addCall(call Call) { | |||||
r.Calls = append(r.Calls, call) | |||||
} | |||||
func (r *StatusRecorder) Status() (*ctypes.ResultStatus, error) { | |||||
res, err := r.Client.Status() | |||||
r.addCall(Call{ | |||||
Name: "status", | |||||
Response: res, | |||||
Error: err, | |||||
}) | |||||
return res, err | |||||
} |
@ -0,0 +1,45 @@ | |||||
package mock_test | |||||
import ( | |||||
"testing" | |||||
"github.com/stretchr/testify/assert" | |||||
"github.com/stretchr/testify/require" | |||||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | |||||
"github.com/tendermint/tendermint/rpc/client/mock" | |||||
) | |||||
func TestStatus(t *testing.T) { | |||||
assert, require := assert.New(t), require.New(t) | |||||
m := &mock.StatusMock{ | |||||
Call: mock.Call{ | |||||
Response: &ctypes.ResultStatus{ | |||||
LatestBlockHash: []byte("block"), | |||||
LatestAppHash: []byte("app"), | |||||
LatestBlockHeight: 10, | |||||
}}, | |||||
} | |||||
r := mock.NewStatusRecorder(m) | |||||
require.Equal(0, len(r.Calls)) | |||||
// make sure response works proper | |||||
status, err := r.Status() | |||||
require.Nil(err, "%+v", err) | |||||
assert.EqualValues("block", status.LatestBlockHash) | |||||
assert.EqualValues(10, status.LatestBlockHeight) | |||||
// make sure recorder works properly | |||||
require.Equal(1, len(r.Calls)) | |||||
rs := r.Calls[0] | |||||
assert.Equal("status", rs.Name) | |||||
assert.Nil(rs.Args) | |||||
assert.Nil(rs.Error) | |||||
require.NotNil(rs.Response) | |||||
st, ok := rs.Response.(*ctypes.ResultStatus) | |||||
require.True(ok) | |||||
assert.EqualValues("block", st.LatestBlockHash) | |||||
assert.EqualValues(10, st.LatestBlockHeight) | |||||
} |