Skip to content

Commit 514b0d0

Browse files
DerekBumoleg-jukovec
authored andcommitted
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 c59e514 commit 514b0d0

25 files changed

+1027
-348
lines changed

CHANGELOG.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
3333
- `Response` method added to the `Request` interface (#237)
3434
- New `LogAppendPushFailed` connection log constant (#237).
3535
It is logged when connection fails to append a push response.
36+
- `ErrorNo` constant that indicates that no error has occurred while getting
37+
the response (#237)
38+
- Ability to mock connections for tests (#237). Added new types `MockDoer`,
39+
`MockRequest` to `test_helpers`.
3640

3741
### Changed
3842

@@ -88,6 +92,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
8892
- Operations `Ping`, `Select`, `Insert`, `Replace`, `Delete`, `Update`, `Upsert`,
8993
`Call`, `Call16`, `Call17`, `Eval`, `Execute` of a `Connector` and `Pooler`
9094
return response data instead of an actual responses (#237)
95+
- Renamed `StrangerResponse` to `MockResponse` (#237)
9196

9297
### Deprecated
9398

@@ -110,7 +115,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
110115
- IPROTO constants (#158)
111116
- Code() method from the Request interface (#158)
112117
- `Schema` field from the `Connection` struct (#7)
113-
- `PushCode` constant (#237)
118+
- `OkCode` and `PushCode` constants (#237)
114119

115120
### Fixed
116121

README.md

+12-5
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ The subpackage has been deleted. You could use `pool` instead.
211211
* `crud` operations `Timeout` option has `crud.OptFloat64` type
212212
instead of `crud.OptUint`.
213213

214+
#### test_helpers package
215+
216+
Renamed `StrangerResponse` to `MockResponse`.
217+
214218
#### msgpack.v5
215219

216220
Most function names and argument types in `msgpack.v5` and `msgpack.v2`
@@ -248,6 +252,8 @@ of the requests is an array instead of array of arrays.
248252
* IPROTO constants have been moved to a separate package [go-iproto](https://github.com/tarantool/go-iproto).
249253
* `PushCode` constant is removed. To check whether the current response is
250254
a push response, use `IsPush()` method of the response iterator instead.
255+
* `ErrorNo` constant is added to indicate that no error has occurred while
256+
getting the response. It should be used instead of the removed `OkCode`.
251257

252258
#### Request changes
253259

@@ -285,9 +291,10 @@ for an `ops` field. `*Operations` needs to be used instead.
285291

286292
#### Connector changes
287293

288-
Operations `Ping`, `Select`, `Insert`, `Replace`, `Delete`, `Update`, `Upsert`,
289-
`Call`, `Call16`, `Call17`, `Eval`, `Execute` of a `Connector` return
290-
response data instead of an actual responses.
294+
* Operations `Ping`, `Select`, `Insert`, `Replace`, `Delete`, `Update`, `Upsert`,
295+
`Call`, `Call16`, `Call17`, `Eval`, `Execute` of a `Connector` return
296+
response data instead of an actual responses.
297+
* New interface `Doer` is added as a child-interface instead of a `Do` method.
291298

292299
#### Connect function
293300

@@ -304,8 +311,8 @@ for creating a connection are now stored in corresponding `Dialer`, not in `Opts
304311
#### Connection schema
305312

306313
* Removed `Schema` field from the `Connection` struct. Instead, new
307-
`GetSchema(Connector)` function was added to get the actual connection
308-
schema on demand.
314+
`GetSchema(Doer)` function was added to get the actual connection
315+
schema on demand.
309316
* `OverrideSchema(*Schema)` method replaced with the `SetSchema(Schema)`.
310317

311318
#### Protocol changes

connection.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ func (conn *Connection) reader(r io.Reader, c Conn) {
813813
return
814814
}
815815
buf := smallBuf{b: respBytes}
816-
header, err := decodeHeader(conn.dec, &buf)
816+
header, code, err := decodeHeader(conn.dec, &buf)
817817
if err != nil {
818818
err = ClientError{
819819
ErrProtocolError,
@@ -824,7 +824,7 @@ func (conn *Connection) reader(r io.Reader, c Conn) {
824824
}
825825

826826
var fut *Future = nil
827-
if iproto.Type(header.Code) == iproto.IPROTO_EVENT {
827+
if code == iproto.IPROTO_EVENT {
828828
if event, err := readWatchEvent(&buf); err == nil {
829829
events <- event
830830
} else {
@@ -835,7 +835,7 @@ func (conn *Connection) reader(r io.Reader, c Conn) {
835835
conn.opts.Logger.Report(LogWatchEventReadFailed, conn, err)
836836
}
837837
continue
838-
} else if header.Code == uint32(iproto.IPROTO_CHUNK) {
838+
} else if code == iproto.IPROTO_CHUNK {
839839
if fut = conn.peekFuture(header.RequestId); fut != nil {
840840
if err := fut.AppendPush(header, &buf); err != nil {
841841
err = ClientError{
@@ -887,8 +887,7 @@ func (conn *Connection) eventer(events <-chan connWatchEvent) {
887887

888888
func (conn *Connection) newFuture(req Request) (fut *Future) {
889889
ctx := req.Ctx()
890-
fut = NewFuture()
891-
fut.SetRequest(req)
890+
fut = NewFuture(req)
892891
if conn.rlimit != nil && conn.opts.RLimitAction == RLimitDrop {
893892
select {
894893
case conn.rlimit <- struct{}{}:
@@ -1069,7 +1068,7 @@ func (conn *Connection) putFuture(fut *Future, req Request, streamId uint64) {
10691068
if fut = conn.fetchFuture(reqid); fut != nil {
10701069
header := Header{
10711070
RequestId: reqid,
1072-
Code: OkCode,
1071+
Error: ErrorNo,
10731072
}
10741073
fut.SetResponse(header, nil)
10751074
conn.markDone(fut)
@@ -1217,7 +1216,7 @@ func (conn *Connection) nextRequestId(context bool) (requestId uint32) {
12171216
func (conn *Connection) Do(req Request) *Future {
12181217
if connectedReq, ok := req.(ConnectedRequest); ok {
12191218
if connectedReq.Conn() != conn {
1220-
fut := NewFuture()
1219+
fut := NewFuture(req)
12211220
fut.SetError(errUnknownRequest)
12221221
return fut
12231222
}

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-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ const (
99
)
1010

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

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
}

example_test.go

+55-6
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)
@@ -992,6 +1015,26 @@ func ExampleBeginRequest_TxnIsolation() {
9921015
fmt.Printf("Select after Rollback: response is %#v\n", data)
9931016
}
9941017

1018+
func ExampleErrorNo() {
1019+
conn := exampleConnect(dialer, opts)
1020+
defer conn.Close()
1021+
1022+
req := tarantool.NewPingRequest()
1023+
resp, err := conn.Do(req).GetResponse()
1024+
if err != nil {
1025+
fmt.Printf("error getting the response: %s\n", err)
1026+
return
1027+
}
1028+
1029+
if resp.Header().Error != tarantool.ErrorNo {
1030+
fmt.Printf("response error code: %s\n", resp.Header().Error)
1031+
} else {
1032+
fmt.Println("Success.")
1033+
}
1034+
// Output:
1035+
// Success.
1036+
}
1037+
9951038
func ExampleFuture_GetIterator() {
9961039
conn := exampleConnect(dialer, opts)
9971040
defer conn.Close()
@@ -1008,11 +1051,11 @@ func ExampleFuture_GetIterator() {
10081051
if it.IsPush() {
10091052
// It is a push message.
10101053
fmt.Printf("push message: %v\n", data[0])
1011-
} else if resp.Header().Code == tarantool.OkCode {
1054+
} else if resp.Header().Error == tarantool.ErrorNo {
10121055
// It is a regular response.
10131056
fmt.Printf("response: %v", data[0])
10141057
} else {
1015-
fmt.Printf("an unexpected response code %d", resp.Header().Code)
1058+
fmt.Printf("an unexpected response code %d", resp.Header().Error)
10161059
}
10171060
}
10181061
if err := it.Err(); err != nil {
@@ -1224,6 +1267,11 @@ func ExampleConnection_Do_failure() {
12241267
if err != nil {
12251268
fmt.Printf("Error in the future: %s\n", err)
12261269
}
1270+
// Optional step: check a response error.
1271+
// It allows checking that response has or hasn't an error without decoding.
1272+
if resp.Header().Error != tarantool.ErrorNo {
1273+
fmt.Printf("Response error: %s\n", resp.Header().Error)
1274+
}
12271275

12281276
data, err := future.Get()
12291277
if err != nil {
@@ -1239,8 +1287,8 @@ func ExampleConnection_Do_failure() {
12391287
} else {
12401288
// Response exist. So it could be a Tarantool error or a decode
12411289
// error. We need to check the error code.
1242-
fmt.Printf("Error code from the response: %d\n", resp.Header().Code)
1243-
if resp.Header().Code == tarantool.OkCode {
1290+
fmt.Printf("Error code from the response: %d\n", resp.Header().Error)
1291+
if resp.Header().Error == tarantool.ErrorNo {
12441292
fmt.Printf("Decode error: %s\n", err)
12451293
} else {
12461294
code := err.(tarantool.Error).Code
@@ -1251,6 +1299,7 @@ func ExampleConnection_Do_failure() {
12511299
}
12521300

12531301
// Output:
1302+
// Response error: ER_NO_SUCH_PROC
12541303
// Data: []
12551304
// Error code from the response: 33
12561305
// Error code from the error: 33

0 commit comments

Comments
 (0)