Skip to content

Commit c6394e3

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 structurem 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` usege are added to the `test_helpers` package. Added new structs `MockDoer`, `MockRequest` to `test_helpers`. Renamed `StrangerResponse` to `MockResponse`. Closes #237
1 parent e5b8a41 commit c6394e3

26 files changed

+977
-416
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ 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 structs `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.
3136

3237
### Changed
3338

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

7191
### Deprecated
7292

README.md

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +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)
137136
}
137+
fmt.Printf("Data: %v", data)
138138
}
139139
```
140140

@@ -201,12 +201,18 @@ The subpackage has been deleted. You could use `pool` instead.
201201
unique string ID, which allows them to be distinguished.
202202
* `pool.GetPoolInfo` has been renamed to `pool.GetInfo`. Return type has been changed
203203
to `map[string]ConnectionInfo`.
204+
* All deprecated asynchronous requests operations on a `Pooler` return response
205+
data instead of an actual responses.
204206

205207
#### crud package
206208

207209
* `crud` operations `Timeout` option has `crud.OptFloat64` type
208210
instead of `crud.OptUint`.
209211

212+
#### test_helpers package
213+
214+
Renamed `StrangerResponse` to `MockResponse`.
215+
210216
#### msgpack.v5
211217

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

242248
#### IPROTO constants
243249

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

246255
#### Request changes
247256

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

258295
#### Connect function
259296

@@ -270,7 +307,7 @@ for creating a connection are now stored in corresponding `Dialer`, not in `Opts
270307
#### Connection schema
271308

272309
* Removed `Schema` field from the `Connection` struct. Instead, new
273-
`GetSchema(Connector)` function was added to get the actual connection
310+
`GetSchema(Doer)` function was added to get the actual connection
274311
schema on demand.
275312
* `OverrideSchema(*Schema)` method replaced with the `SetSchema(Schema)`.
276313

connection.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,7 @@ func (conn *Connection) reader(r io.Reader, c Conn) {
830830
conn.opts.Logger.Report(LogWatchEventReadFailed, conn, err)
831831
}
832832
continue
833-
} else if header.Code == PushCode {
833+
} else if iproto.Type(header.Code) == iproto.IPROTO_CHUNK {
834834
if fut = conn.peekFuture(header.RequestId); fut != nil {
835835
fut.AppendPush(header, &buf)
836836
}
@@ -874,8 +874,7 @@ func (conn *Connection) eventer(events <-chan connWatchEvent) {
874874

875875
func (conn *Connection) newFuture(req Request) (fut *Future) {
876876
ctx := req.Ctx()
877-
fut = NewFuture()
878-
fut.SetRequest(req)
877+
fut = NewFuture(req)
879878
if conn.rlimit != nil && conn.opts.RLimitAction == RLimitDrop {
880879
select {
881880
case conn.rlimit <- struct{}{}:
@@ -1204,7 +1203,7 @@ func (conn *Connection) nextRequestId(context bool) (requestId uint32) {
12041203
func (conn *Connection) Do(req Request) *Future {
12051204
if connectedReq, ok := req.(ConnectedRequest); ok {
12061205
if connectedReq.Conn() != conn {
1207-
fut := NewFuture()
1206+
fut := NewFuture(req)
12081207
fut.SetError(errUnknownRequest)
12091208
return fut
12101209
}

connector.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@ package tarantool
22

33
import "time"
44

5+
// Doer if 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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ const (
99
)
1010

1111
const (
12-
OkCode = uint32(iproto.IPROTO_OK)
13-
PushCode = uint32(iproto.IPROTO_CHUNK)
12+
// OkCode is an iproto code for a successful request or command.
13+
OkCode = int(iproto.IPROTO_OK)
1414
)

crud/select.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ package crud
22

33
import (
44
"context"
5-
"io"
6-
7-
"github.com/vmihailenco/msgpack/v5"
85

96
"github.com/tarantool/go-tarantool/v2"
7+
"github.com/vmihailenco/msgpack/v5"
108
)
119

1210
// SelectOpts describes options for `crud.select` method.
@@ -134,9 +132,3 @@ func (req SelectRequest) Context(ctx context.Context) SelectRequest {
134132

135133
return req
136134
}
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

Lines changed: 23 additions & 10 deletions
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+
iproto.Error(resp.Header().Code) == 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}
513514
header, err := decodeHeader(msgpack.NewDecoder(&smallBuf{}), &buf)
514-
resp := &BaseResponse{header: header, buf: 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
}

example_test.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,16 +198,34 @@ func ExampleSelectRequest() {
198198
}
199199

200200
key := []interface{}{uint(1111)}
201-
data, err := conn.Do(tarantool.NewSelectRequest(617).
201+
resp, err := conn.Do(tarantool.NewSelectRequest(617).
202202
Limit(100).
203203
Iterator(tarantool.IterEq).
204204
Key(key),
205-
).Get()
205+
).GetResponse()
206206

207207
if err != nil {
208208
fmt.Printf("error in select is %v", err)
209209
return
210210
}
211+
selResp, ok := resp.(*tarantool.SelectResponse)
212+
if !ok {
213+
fmt.Print("wrong response type")
214+
return
215+
}
216+
217+
pos, err := selResp.Pos()
218+
if err != nil {
219+
fmt.Printf("error in Pos: %v", err)
220+
return
221+
}
222+
fmt.Printf("pos for Select is %v\n", pos)
223+
224+
data, err := resp.Decode()
225+
if err != nil {
226+
fmt.Printf("error while decoding: %v", err)
227+
return
228+
}
211229
fmt.Printf("response is %#v\n", data)
212230

213231
var res []Tuple
@@ -224,6 +242,7 @@ func ExampleSelectRequest() {
224242
fmt.Printf("response is %v\n", res)
225243

226244
// Output:
245+
// pos for Select is []
227246
// response is []interface {}{[]interface {}{0x457, "hello", "world"}}
228247
// response is [{{} 1111 hello world}]
229248
}
@@ -567,17 +586,21 @@ func ExampleExecuteRequest() {
567586
resp, err := conn.Do(req).GetResponse()
568587
fmt.Println("Execute")
569588
fmt.Println("Error", err)
589+
570590
data, err := resp.Decode()
571591
fmt.Println("Error", err)
572592
fmt.Println("Data", data)
593+
573594
exResp, ok := resp.(*tarantool.ExecuteResponse)
574595
if !ok {
575596
fmt.Printf("wrong response type")
576597
return
577598
}
599+
578600
metaData, err := exResp.MetaData()
579601
fmt.Println("MetaData", metaData)
580602
fmt.Println("Error", err)
603+
581604
sqlInfo, err := exResp.SQLInfo()
582605
fmt.Println("SQL Info", sqlInfo)
583606
fmt.Println("Error", err)

future.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,26 @@ func (it *asyncResponseIterator) nextResponse() (resp Response) {
123123
return resp
124124
}
125125

126-
// NewFuture creates a new empty Future.
127-
func NewFuture() (fut *Future) {
126+
// PushResponse is used for push requests for the Future.
127+
type PushResponse struct {
128+
baseResponse
129+
}
130+
131+
func createPushResponse(header Header, body io.Reader) (Response, error) {
132+
resp, err := createBaseResponse(header, body)
133+
if err != nil {
134+
return nil, err
135+
}
136+
return &PushResponse{resp}, nil
137+
}
138+
139+
// NewFuture creates a new empty Future for a given Request.
140+
func NewFuture(req Request) (fut *Future) {
128141
fut = &Future{}
129142
fut.ready = make(chan struct{}, 1000000000)
130143
fut.done = make(chan struct{})
131144
fut.pushes = make([]Response, 0)
145+
fut.req = req
132146
return fut
133147
}
134148

@@ -154,11 +168,6 @@ func (fut *Future) AppendPush(header Header, body io.Reader) error {
154168
return nil
155169
}
156170

157-
// SetRequest sets a request, for which the future was created.
158-
func (fut *Future) SetRequest(req Request) {
159-
fut.req = req
160-
}
161-
162171
// SetResponse sets a response for the future and finishes the future.
163172
func (fut *Future) SetResponse(header Header, body io.Reader) error {
164173
fut.mutex.Lock()

0 commit comments

Comments
 (0)