You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

223 lines
7.0 KiB

  1. /*
  2. Copyright 2017 Google LLC
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package spanner
  14. import (
  15. "errors"
  16. "sync"
  17. "testing"
  18. "time"
  19. "cloud.google.com/go/spanner/internal/testutil"
  20. "golang.org/x/net/context"
  21. sppb "google.golang.org/genproto/googleapis/spanner/v1"
  22. "google.golang.org/grpc/codes"
  23. )
  24. var (
  25. errAbrt = spannerErrorf(codes.Aborted, "")
  26. errUsr = errors.New("error")
  27. )
  28. // setup sets up a Client using mockclient
  29. func mockClient(t *testing.T) (*sessionPool, *testutil.MockCloudSpannerClient, *Client) {
  30. var (
  31. mc = testutil.NewMockCloudSpannerClient(t)
  32. spc = SessionPoolConfig{}
  33. database = "mockdb"
  34. )
  35. spc.getRPCClient = func() (sppb.SpannerClient, error) {
  36. return mc, nil
  37. }
  38. sp, err := newSessionPool(database, spc, nil)
  39. if err != nil {
  40. t.Fatalf("cannot create session pool: %v", err)
  41. }
  42. return sp, mc, &Client{
  43. database: database,
  44. idleSessions: sp,
  45. }
  46. }
  47. // TestReadOnlyAcquire tests acquire for ReadOnlyTransaction.
  48. func TestReadOnlyAcquire(t *testing.T) {
  49. t.Parallel()
  50. _, mc, client := mockClient(t)
  51. defer client.Close()
  52. mc.SetActions(
  53. testutil.Action{"BeginTransaction", errUsr},
  54. testutil.Action{"BeginTransaction", nil},
  55. testutil.Action{"BeginTransaction", nil},
  56. )
  57. // Singleuse should only be used once.
  58. txn := client.Single()
  59. defer txn.Close()
  60. _, _, e := txn.acquire(context.Background())
  61. if e != nil {
  62. t.Errorf("Acquire for single use, got %v, want nil.", e)
  63. }
  64. _, _, e = txn.acquire(context.Background())
  65. if wantErr := errTxClosed(); !testEqual(e, wantErr) {
  66. t.Errorf("Second acquire for single use, got %v, want %v.", e, wantErr)
  67. }
  68. // Multiuse can recover from acquire failure.
  69. txn = client.ReadOnlyTransaction()
  70. _, _, e = txn.acquire(context.Background())
  71. if wantErr := toSpannerError(errUsr); !testEqual(e, wantErr) {
  72. t.Errorf("Acquire for multi use, got %v, want %v.", e, wantErr)
  73. }
  74. _, _, e = txn.acquire(context.Background())
  75. if e != nil {
  76. t.Errorf("Acquire for multi use, got %v, want nil.", e)
  77. }
  78. txn.Close()
  79. // Multiuse can not be used after close.
  80. _, _, e = txn.acquire(context.Background())
  81. if wantErr := errTxClosed(); !testEqual(e, wantErr) {
  82. t.Errorf("Second acquire for multi use, got %v, want %v.", e, wantErr)
  83. }
  84. // Multiuse can be acquired concurrently.
  85. txn = client.ReadOnlyTransaction()
  86. defer txn.Close()
  87. mc.Freeze()
  88. var (
  89. sh1 *sessionHandle
  90. sh2 *sessionHandle
  91. ts1 *sppb.TransactionSelector
  92. ts2 *sppb.TransactionSelector
  93. wg = sync.WaitGroup{}
  94. )
  95. acquire := func(sh **sessionHandle, ts **sppb.TransactionSelector) {
  96. defer wg.Done()
  97. var e error
  98. *sh, *ts, e = txn.acquire(context.Background())
  99. if e != nil {
  100. t.Errorf("Concurrent acquire for multiuse, got %v, expect nil.", e)
  101. }
  102. }
  103. wg.Add(2)
  104. go acquire(&sh1, &ts1)
  105. go acquire(&sh2, &ts2)
  106. <-time.After(100 * time.Millisecond)
  107. mc.Unfreeze()
  108. wg.Wait()
  109. if !testEqual(sh1.session, sh2.session) {
  110. t.Errorf("Expect acquire to get same session handle, got %v and %v.", sh1, sh2)
  111. }
  112. if !testEqual(ts1, ts2) {
  113. t.Errorf("Expect acquire to get same transaction selector, got %v and %v.", ts1, ts2)
  114. }
  115. }
  116. // TestRetryOnAbort tests transaction retries on abort.
  117. func TestRetryOnAbort(t *testing.T) {
  118. t.Parallel()
  119. _, mc, client := mockClient(t)
  120. defer client.Close()
  121. // commit in writeOnlyTransaction
  122. mc.SetActions(
  123. testutil.Action{"Commit", errAbrt}, // abort on first commit
  124. testutil.Action{"Commit", nil},
  125. )
  126. ms := []*Mutation{
  127. Insert("Accounts", []string{"AccountId", "Nickname", "Balance"}, []interface{}{int64(1), "Foo", int64(50)}),
  128. Insert("Accounts", []string{"AccountId", "Nickname", "Balance"}, []interface{}{int64(2), "Bar", int64(1)}),
  129. }
  130. if _, e := client.Apply(context.Background(), ms, ApplyAtLeastOnce()); e != nil {
  131. t.Errorf("applyAtLeastOnce retry on abort, got %v, want nil.", e)
  132. }
  133. // begin and commit in ReadWriteTransaction
  134. mc.SetActions(
  135. testutil.Action{"BeginTransaction", nil}, // let takeWriteSession succeed and get a session handle
  136. testutil.Action{"Commit", errAbrt}, // let first commit fail and retry will begin new transaction
  137. testutil.Action{"BeginTransaction", errAbrt}, // this time we can fail the begin attempt
  138. testutil.Action{"BeginTransaction", nil},
  139. testutil.Action{"Commit", nil},
  140. )
  141. if _, e := client.Apply(context.Background(), ms); e != nil {
  142. t.Errorf("ReadWriteTransaction retry on abort, got %v, want nil.", e)
  143. }
  144. }
  145. // TestBadSession tests bad session (session not found error).
  146. // TODO: session closed from transaction close
  147. func TestBadSession(t *testing.T) {
  148. t.Parallel()
  149. ctx := context.Background()
  150. sp, mc, client := mockClient(t)
  151. defer client.Close()
  152. var sid string
  153. // Prepare a session, get the session id for use in testing.
  154. if s, e := sp.take(ctx); e != nil {
  155. t.Fatal("Prepare session failed.")
  156. } else {
  157. sid = s.getID()
  158. s.recycle()
  159. }
  160. wantErr := spannerErrorf(codes.NotFound, "Session not found: %v", sid)
  161. // ReadOnlyTransaction
  162. mc.SetActions(
  163. testutil.Action{"BeginTransaction", wantErr},
  164. testutil.Action{"BeginTransaction", wantErr},
  165. testutil.Action{"BeginTransaction", wantErr},
  166. )
  167. txn := client.ReadOnlyTransaction()
  168. defer txn.Close()
  169. if _, _, got := txn.acquire(ctx); !testEqual(wantErr, got) {
  170. t.Errorf("Expect acquire to fail, got %v, want %v.", got, wantErr)
  171. }
  172. // The failure should recycle the session, we expect it to be used in following requests.
  173. if got := txn.Query(ctx, NewStatement("SELECT 1")); !testEqual(wantErr, got.err) {
  174. t.Errorf("Expect Query to fail, got %v, want %v.", got.err, wantErr)
  175. }
  176. if got := txn.Read(ctx, "Users", KeySets(Key{"alice"}, Key{"bob"}), []string{"name", "email"}); !testEqual(wantErr, got.err) {
  177. t.Errorf("Expect Read to fail, got %v, want %v.", got.err, wantErr)
  178. }
  179. // writeOnlyTransaction
  180. ms := []*Mutation{
  181. Insert("Accounts", []string{"AccountId", "Nickname", "Balance"}, []interface{}{int64(1), "Foo", int64(50)}),
  182. Insert("Accounts", []string{"AccountId", "Nickname", "Balance"}, []interface{}{int64(2), "Bar", int64(1)}),
  183. }
  184. mc.SetActions(testutil.Action{"Commit", wantErr})
  185. if _, got := client.Apply(context.Background(), ms, ApplyAtLeastOnce()); !testEqual(wantErr, got) {
  186. t.Errorf("Expect applyAtLeastOnce to fail, got %v, want %v.", got, wantErr)
  187. }
  188. }
  189. func TestFunctionErrorReturned(t *testing.T) {
  190. t.Parallel()
  191. _, mc, client := mockClient(t)
  192. defer client.Close()
  193. mc.SetActions(
  194. testutil.Action{"BeginTransaction", nil},
  195. testutil.Action{"Rollback", nil},
  196. )
  197. want := errors.New("an error")
  198. _, got := client.ReadWriteTransaction(context.Background(),
  199. func(context.Context, *ReadWriteTransaction) error { return want })
  200. if got != want {
  201. t.Errorf("got <%v>, want <%v>", got, want)
  202. }
  203. mc.CheckActionsConsumed()
  204. }