Skip to content

Commit 64c9b58

Browse files
committed
api: add ability to mock connections in tests
Create a mock implementations `MockRequest`, `MockResponse` and `MockDoer`. The last one allows to mock not the full `Connection`, but its part -- a structure, that implements new `Doer` interface (a `Do` function). Also added missing comments, all the changes are recorded in the `CHANGELOG` and `README` files. Added new tests and examples. So this entity is easier to implement and it is enough to mock tests that require working `Connection`. All new mock structs and an example for `MockDoer` usage are added to the `test_helpers` package. Added new structs `MockDoer`, `MockRequest` to `test_helpers`. Renamed `StrangerResponse` to `MockResponse`. Added new connection log constant: `LogAppendPushFailed`. It is logged when connection fails to append a push response. Closes #237
1 parent 819dccf commit 64c9b58

27 files changed

+1176
-438
lines changed

CHANGELOG.md

+22
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
2828
in requests instead of their IDs.
2929
- `GetSchema` function to get the actual schema (#7)
3030
- Support connection via an existing socket fd (#321)
31+
- Ability to mock connections for tests (#237). Added new types `MockDoer`,
32+
`MockRequest` to `test_helpers`.
33+
- `Response` method added to the `Request` interface (#237)
34+
- `Header` struct for the response header (#237). It can be accessed via
35+
`Header()` method of the `Response` interface.
36+
- New `LogAppendPushFailed` connection log constant (#237).
37+
It is logged when connection fails to append a push response.
3138

3239
### Changed
3340

@@ -67,6 +74,21 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
6774
it (#321)
6875
- Rename `pool.GetPoolInfo` to `pool.GetInfo`. Change return type to
6976
`map[string]ConnectionInfo` (#321)
77+
- All responses are now implementations of an `Response` interface (#237).
78+
`SelectResponse`, `ExecuteResponse`, `PrepareResponse`, `PushResponse` are part
79+
of a public API. `Pos()`, `MetaData()`, `SQLInfo()` methods created for them
80+
to get specific info.
81+
Special types of responses are used with special requests.
82+
- `IsPush()` method is added to the response iterator (#237). It returns
83+
the information if the current response is a `PushResponse`.
84+
`PushCode` constant is removed.
85+
- `Future` constructors now accept `Request` as their argument (#237).
86+
Method `Get` now returns response data. To get the actual response new method
87+
added `GetResponse`. Methods `AppendPush` and `SetResponse` accepts
88+
response `Header` and data as their arguments.
89+
- All deprecated asynchronous requests operations on a `Connector` return
90+
response data instead of an actual responses (#237)
91+
- Renamed `StrangerResponse` to `MockResponse` (#237)
7092

7193
### Deprecated
7294

README.md

+42-4
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,13 @@ func main() {
128128
if err != nil {
129129
fmt.Println("Connection refused:", err)
130130
}
131-
resp, err := conn.Do(tarantool.NewInsertRequest(999).
131+
data, err := conn.Do(tarantool.NewInsertRequest(999).
132132
Tuple([]interface{}{99999, "BB"}),
133133
).Get()
134134
if err != nil {
135135
fmt.Println("Error", err)
136-
fmt.Println("Code", resp.Code)
136+
} else {
137+
fmt.Printf("Data: %v", data)
137138
}
138139
}
139140
```
@@ -201,12 +202,18 @@ The subpackage has been deleted. You could use `pool` instead.
201202
unique string ID, which allows them to be distinguished.
202203
* `pool.GetPoolInfo` has been renamed to `pool.GetInfo`. Return type has been changed
203204
to `map[string]ConnectionInfo`.
205+
* All deprecated asynchronous requests operations on a `Pooler` return response
206+
data instead of an actual responses.
204207

205208
#### crud package
206209

207210
* `crud` operations `Timeout` option has `crud.OptFloat64` type
208211
instead of `crud.OptUint`.
209212

213+
#### test_helpers package
214+
215+
Renamed `StrangerResponse` to `MockResponse`.
216+
210217
#### msgpack.v5
211218

212219
Most function names and argument types in `msgpack.v5` and `msgpack.v2`
@@ -241,7 +248,10 @@ of the requests is an array instead of array of arrays.
241248

242249
#### IPROTO constants
243250

244-
IPROTO constants have been moved to a separate package [go-iproto](https://github.com/tarantool/go-iproto).
251+
* IPROTO constants have been moved to a separate package [go-iproto](https://github.com/tarantool/go-iproto).
252+
* `PushCode` constant is removed. To get information, if the current response is
253+
a push response, new `IsPush()` method of the response iterator could be used
254+
instead.
245255

246256
#### Request changes
247257

@@ -254,6 +264,34 @@ longer accept `ops` argument (operations) as an `interface{}`. `*Operations`
254264
needs to be passed instead.
255265
* `UpdateRequest` and `UpsertRequest` structs no longer accept `interface{}`
256266
for an `ops` field. `*Operations` needs to be used instead.
267+
* `Response` method added to the `Request` interface.
268+
269+
#### Response changes
270+
271+
* New interface `Response` added.
272+
* For each request type, a different response type is created. They all
273+
implement a `Response` interface. `SelectResponse`, `PrepareResponse`,
274+
`ExecuteResponse`, `PushResponse` are a part of a public API.
275+
`Pos()`, `MetaData()`, `SQLInfo()` methods created for them to get specific info.
276+
Special types of responses are used with special requests.
277+
* Response header stored in a new `Header` struct. It could be accessed via
278+
`Header()` method.
279+
* Method `IsPush()` is added to the `ResponseIterator` interface.
280+
It returns true if the current response is a push response.
281+
282+
#### Future changes
283+
284+
* `Future` constructors now accept `Request` as their argument.
285+
* Method `Get` now returns response data instead of the actual response.
286+
* New method `GetResponse` added to get an actual response.
287+
* Methods `AppendPush` and `SetResponse` accepts response `Header` and data
288+
as their arguments.
289+
290+
#### Connector changes
291+
292+
* All deprecated asynchronous requests operations on a `Connector` return
293+
response data instead of an actual responses.
294+
* New interface `Doer` is added as a child-interface instead of a `Do` method.
257295

258296
#### Connect function
259297

@@ -270,7 +308,7 @@ for creating a connection are now stored in corresponding `Dialer`, not in `Opts
270308
#### Connection schema
271309

272310
* Removed `Schema` field from the `Connection` struct. Instead, new
273-
`GetSchema(Connector)` function was added to get the actual connection
311+
`GetSchema(Doer)` function was added to get the actual connection
274312
schema on demand.
275313
* `OverrideSchema(*Schema)` method replaced with the `SetSchema(Schema)`.
276314

connection.go

+21-9
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ const (
6161
LogUnexpectedResultId
6262
// LogWatchEventReadFailed is logged when failed to read a watch event.
6363
LogWatchEventReadFailed
64+
// LogAppendPushFailed is logged when failed to append a push response.
65+
LogAppendPushFailed
6466
)
6567

6668
// ConnEvent is sent throw Notify channel specified in Opts.
@@ -103,6 +105,9 @@ func (d defaultLogger) Report(event ConnLogKind, conn *Connection, v ...interfac
103105
case LogWatchEventReadFailed:
104106
err := v[0].(error)
105107
log.Printf("tarantool: unable to parse watch event: %s", err)
108+
case LogAppendPushFailed:
109+
err := v[0].(error)
110+
log.Printf("tarantool: unable to append a push response: %s", err)
106111
default:
107112
args := append([]interface{}{"tarantool: unexpected event ", event, conn}, v...)
108113
log.Print(args...)
@@ -808,7 +813,7 @@ func (conn *Connection) reader(r io.Reader, c Conn) {
808813
return
809814
}
810815
buf := smallBuf{b: respBytes}
811-
header, err := decodeHeader(conn.dec, &buf)
816+
header, code, err := decodeHeader(conn.dec, &buf)
812817
if err != nil {
813818
err = ClientError{
814819
ErrProtocolError,
@@ -819,7 +824,7 @@ func (conn *Connection) reader(r io.Reader, c Conn) {
819824
}
820825

821826
var fut *Future = nil
822-
if iproto.Type(header.Code) == iproto.IPROTO_EVENT {
827+
if code == iproto.IPROTO_EVENT {
823828
if event, err := readWatchEvent(&buf); err == nil {
824829
events <- event
825830
} else {
@@ -830,13 +835,21 @@ func (conn *Connection) reader(r io.Reader, c Conn) {
830835
conn.opts.Logger.Report(LogWatchEventReadFailed, conn, err)
831836
}
832837
continue
833-
} else if header.Code == PushCode {
838+
} else if code == iproto.IPROTO_CHUNK {
834839
if fut = conn.peekFuture(header.RequestId); fut != nil {
835-
fut.AppendPush(header, &buf)
840+
if err := fut.AppendPush(header, &buf); err != nil {
841+
err = ClientError{
842+
ErrProtocolError,
843+
fmt.Sprintf("failed to append push response: %s", err),
844+
}
845+
conn.opts.Logger.Report(LogAppendPushFailed, conn, err)
846+
}
836847
}
837848
} else {
838849
if fut = conn.fetchFuture(header.RequestId); fut != nil {
839-
fut.SetResponse(header, &buf)
850+
if err := fut.SetResponse(header, &buf); err != nil {
851+
fut.SetError(fmt.Errorf("failed to set response: %w", err))
852+
}
840853
conn.markDone(fut)
841854
}
842855
}
@@ -874,8 +887,7 @@ func (conn *Connection) eventer(events <-chan connWatchEvent) {
874887

875888
func (conn *Connection) newFuture(req Request) (fut *Future) {
876889
ctx := req.Ctx()
877-
fut = NewFuture()
878-
fut.SetRequest(req)
890+
fut = NewFuture(req)
879891
if conn.rlimit != nil && conn.opts.RLimitAction == RLimitDrop {
880892
select {
881893
case conn.rlimit <- struct{}{}:
@@ -1056,7 +1068,7 @@ func (conn *Connection) putFuture(fut *Future, req Request, streamId uint64) {
10561068
if fut = conn.fetchFuture(reqid); fut != nil {
10571069
header := Header{
10581070
RequestId: reqid,
1059-
Code: OkCode,
1071+
Error: ErrorNo,
10601072
}
10611073
fut.SetResponse(header, nil)
10621074
conn.markDone(fut)
@@ -1204,7 +1216,7 @@ func (conn *Connection) nextRequestId(context bool) (requestId uint32) {
12041216
func (conn *Connection) Do(req Request) *Future {
12051217
if connectedReq, ok := req.(ConnectedRequest); ok {
12061218
if connectedReq.Conn() != conn {
1207-
fut := NewFuture()
1219+
fut := NewFuture(req)
12081220
fut.SetError(errUnknownRequest)
12091221
return fut
12101222
}

connector.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@ package tarantool
22

33
import "time"
44

5+
// Doer is an interface that performs requests asynchronously.
6+
type Doer interface {
7+
// Do performs a request asynchronously.
8+
Do(req Request) (fut *Future)
9+
}
10+
511
type Connector interface {
12+
Doer
613
ConnectedNow() bool
714
Close() error
815
ConfiguredTimeout() time.Duration
916
NewPrepared(expr string) (*Prepared, error)
1017
NewStream() (*Stream, error)
1118
NewWatcher(key string, callback WatchCallback) (Watcher, error)
12-
Do(req Request) (fut *Future)
1319

1420
// Deprecated: the method will be removed in the next major version,
1521
// use a PingRequest object + Do() instead.

const.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const (
99
)
1010

1111
const (
12-
OkCode = uint32(iproto.IPROTO_OK)
13-
PushCode = uint32(iproto.IPROTO_CHUNK)
12+
// ErrorNo indicates whether an error is occurred. It could be used to
13+
// check that a response has an error without the response body decoding.
14+
ErrorNo = iproto.ER_UNKNOWN
1415
)

crud/select.go

-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package crud
22

33
import (
44
"context"
5-
"io"
65

76
"github.com/vmihailenco/msgpack/v5"
87

@@ -134,9 +133,3 @@ func (req SelectRequest) Context(ctx context.Context) SelectRequest {
134133

135134
return req
136135
}
137-
138-
// Response creates a response for the SelectRequest.
139-
func (req SelectRequest) Response(header tarantool.Header,
140-
body io.Reader) (tarantool.Response, error) {
141-
return req.impl.Response(header, body)
142-
}

dial.go

+24-11
Original file line numberDiff line numberDiff line change
@@ -398,16 +398,17 @@ func identify(w writeFlusher, r io.Reader) (ProtocolInfo, error) {
398398
return info, err
399399
}
400400

401-
resp, err := readResponse(r)
401+
resp, err := readResponse(r, req)
402402
if err != nil {
403+
if resp != nil &&
404+
resp.Header().Error == iproto.ER_UNKNOWN_REQUEST_TYPE {
405+
// IPROTO_ID requests are not supported by server.
406+
return info, nil
407+
}
403408
return info, err
404409
}
405410
data, err := resp.Decode()
406411
if err != nil {
407-
if iproto.Error(resp.Header().Code) == iproto.ER_UNKNOWN_REQUEST_TYPE {
408-
// IPROTO_ID requests are not supported by server.
409-
return info, nil
410-
}
411412
return info, err
412413
}
413414

@@ -477,7 +478,7 @@ func authenticate(c Conn, auth Auth, user string, pass string, salt string) erro
477478
if err = writeRequest(c, req); err != nil {
478479
return err
479480
}
480-
if _, err = readResponse(c); err != nil {
481+
if _, err = readResponse(c, req); err != nil {
481482
return err
482483
}
483484
return nil
@@ -501,19 +502,31 @@ func writeRequest(w writeFlusher, req Request) error {
501502
}
502503

503504
// readResponse reads a response from the reader.
504-
func readResponse(r io.Reader) (Response, error) {
505+
func readResponse(r io.Reader, req Request) (Response, error) {
505506
var lenbuf [packetLengthBytes]byte
506507

507508
respBytes, err := read(r, lenbuf[:])
508509
if err != nil {
509-
return &BaseResponse{}, fmt.Errorf("read error: %w", err)
510+
return nil, fmt.Errorf("read error: %w", err)
510511
}
511512

512513
buf := smallBuf{b: respBytes}
513-
header, err := decodeHeader(msgpack.NewDecoder(&smallBuf{}), &buf)
514-
resp := &BaseResponse{header: header, buf: buf}
514+
header, _, err := decodeHeader(msgpack.NewDecoder(&smallBuf{}), &buf)
515515
if err != nil {
516-
return resp, fmt.Errorf("decode response header error: %w", err)
516+
return nil, fmt.Errorf("decode response header error: %w", err)
517+
}
518+
resp, err := req.Response(header, &buf)
519+
if err != nil {
520+
return nil, fmt.Errorf("creating response error: %w", err)
521+
}
522+
_, err = resp.Decode()
523+
if err != nil {
524+
switch err.(type) {
525+
case Error:
526+
return resp, err
527+
default:
528+
return resp, fmt.Errorf("decode response body error: %w", err)
529+
}
517530
}
518531
return resp, nil
519532
}

0 commit comments

Comments
 (0)