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.
 
 
 

351 lines
9.6 KiB

  1. /*
  2. Copyright 2016 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 bigtable
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "io/ioutil"
  18. "strings"
  19. "testing"
  20. "cloud.google.com/go/internal/testutil"
  21. "github.com/golang/protobuf/proto"
  22. "github.com/golang/protobuf/ptypes/wrappers"
  23. btspb "google.golang.org/genproto/googleapis/bigtable/v2"
  24. )
  25. // Indicates that a field in the proto should be omitted, rather than included
  26. // as a wrapped empty string.
  27. const nilStr = "<>"
  28. func TestSingleCell(t *testing.T) {
  29. cr := newChunkReader()
  30. // All in one cell
  31. row, err := cr.Process(cc("rk", "fm", "col", 1, "value", 0, true))
  32. if err != nil {
  33. t.Fatalf("Processing chunk: %v", err)
  34. }
  35. if row == nil {
  36. t.Fatalf("Missing row")
  37. }
  38. if len(row["fm"]) != 1 {
  39. t.Fatalf("Family name length mismatch %d, %d", 1, len(row["fm"]))
  40. }
  41. want := []ReadItem{ri("rk", "fm", "col", 1, "value")}
  42. if !testutil.Equal(row["fm"], want) {
  43. t.Fatalf("Incorrect ReadItem: got: %v\nwant: %v\n", row["fm"], want)
  44. }
  45. if err := cr.Close(); err != nil {
  46. t.Fatalf("Close: %v", err)
  47. }
  48. }
  49. func TestMultipleCells(t *testing.T) {
  50. cr := newChunkReader()
  51. mustProcess(t, cr, cc("rs", "fm1", "col1", 0, "val1", 0, false))
  52. mustProcess(t, cr, cc("rs", "fm1", "col1", 1, "val2", 0, false))
  53. mustProcess(t, cr, cc("rs", "fm1", "col2", 0, "val3", 0, false))
  54. mustProcess(t, cr, cc("rs", "fm2", "col1", 0, "val4", 0, false))
  55. row, err := cr.Process(cc("rs", "fm2", "col2", 1, "extralongval5", 0, true))
  56. if err != nil {
  57. t.Fatalf("Processing chunk: %v", err)
  58. }
  59. if row == nil {
  60. t.Fatalf("Missing row")
  61. }
  62. want := []ReadItem{
  63. ri("rs", "fm1", "col1", 0, "val1"),
  64. ri("rs", "fm1", "col1", 1, "val2"),
  65. ri("rs", "fm1", "col2", 0, "val3"),
  66. }
  67. if !testutil.Equal(row["fm1"], want) {
  68. t.Fatalf("Incorrect ReadItem: got: %v\nwant: %v\n", row["fm1"], want)
  69. }
  70. want = []ReadItem{
  71. ri("rs", "fm2", "col1", 0, "val4"),
  72. ri("rs", "fm2", "col2", 1, "extralongval5"),
  73. }
  74. if !testutil.Equal(row["fm2"], want) {
  75. t.Fatalf("Incorrect ReadItem: got: %v\nwant: %v\n", row["fm2"], want)
  76. }
  77. if err := cr.Close(); err != nil {
  78. t.Fatalf("Close: %v", err)
  79. }
  80. }
  81. func TestSplitCells(t *testing.T) {
  82. cr := newChunkReader()
  83. mustProcess(t, cr, cc("rs", "fm1", "col1", 0, "hello ", 11, false))
  84. mustProcess(t, cr, ccData("world", 0, false))
  85. row, err := cr.Process(cc("rs", "fm1", "col2", 0, "val2", 0, true))
  86. if err != nil {
  87. t.Fatalf("Processing chunk: %v", err)
  88. }
  89. if row == nil {
  90. t.Fatalf("Missing row")
  91. }
  92. want := []ReadItem{
  93. ri("rs", "fm1", "col1", 0, "hello world"),
  94. ri("rs", "fm1", "col2", 0, "val2"),
  95. }
  96. if !testutil.Equal(row["fm1"], want) {
  97. t.Fatalf("Incorrect ReadItem: got: %v\nwant: %v\n", row["fm1"], want)
  98. }
  99. if err := cr.Close(); err != nil {
  100. t.Fatalf("Close: %v", err)
  101. }
  102. }
  103. func TestMultipleRows(t *testing.T) {
  104. cr := newChunkReader()
  105. row, err := cr.Process(cc("rs1", "fm1", "col1", 1, "val1", 0, true))
  106. if err != nil {
  107. t.Fatalf("Processing chunk: %v", err)
  108. }
  109. want := []ReadItem{ri("rs1", "fm1", "col1", 1, "val1")}
  110. if !testutil.Equal(row["fm1"], want) {
  111. t.Fatalf("Incorrect ReadItem: got: %v\nwant: %v\n", row["fm1"], want)
  112. }
  113. row, err = cr.Process(cc("rs2", "fm2", "col2", 2, "val2", 0, true))
  114. if err != nil {
  115. t.Fatalf("Processing chunk: %v", err)
  116. }
  117. want = []ReadItem{ri("rs2", "fm2", "col2", 2, "val2")}
  118. if !testutil.Equal(row["fm2"], want) {
  119. t.Fatalf("Incorrect ReadItem: got: %v\nwant: %v\n", row["fm2"], want)
  120. }
  121. if err := cr.Close(); err != nil {
  122. t.Fatalf("Close: %v", err)
  123. }
  124. }
  125. func TestBlankQualifier(t *testing.T) {
  126. cr := newChunkReader()
  127. row, err := cr.Process(cc("rs1", "fm1", "", 1, "val1", 0, true))
  128. if err != nil {
  129. t.Fatalf("Processing chunk: %v", err)
  130. }
  131. want := []ReadItem{ri("rs1", "fm1", "", 1, "val1")}
  132. if !testutil.Equal(row["fm1"], want) {
  133. t.Fatalf("Incorrect ReadItem: got: %v\nwant: %v\n", row["fm1"], want)
  134. }
  135. row, err = cr.Process(cc("rs2", "fm2", "col2", 2, "val2", 0, true))
  136. if err != nil {
  137. t.Fatalf("Processing chunk: %v", err)
  138. }
  139. want = []ReadItem{ri("rs2", "fm2", "col2", 2, "val2")}
  140. if !testutil.Equal(row["fm2"], want) {
  141. t.Fatalf("Incorrect ReadItem: got: %v\nwant: %v\n", row["fm2"], want)
  142. }
  143. if err := cr.Close(); err != nil {
  144. t.Fatalf("Close: %v", err)
  145. }
  146. }
  147. func TestReset(t *testing.T) {
  148. cr := newChunkReader()
  149. mustProcess(t, cr, cc("rs", "fm1", "col1", 0, "val1", 0, false))
  150. mustProcess(t, cr, cc("rs", "fm1", "col1", 1, "val2", 0, false))
  151. mustProcess(t, cr, cc("rs", "fm1", "col2", 0, "val3", 0, false))
  152. mustProcess(t, cr, ccReset())
  153. row := mustProcess(t, cr, cc("rs1", "fm1", "col1", 1, "val1", 0, true))
  154. want := []ReadItem{ri("rs1", "fm1", "col1", 1, "val1")}
  155. if !testutil.Equal(row["fm1"], want) {
  156. t.Fatalf("Reset: got: %v\nwant: %v\n", row["fm1"], want)
  157. }
  158. if err := cr.Close(); err != nil {
  159. t.Fatalf("Close: %v", err)
  160. }
  161. }
  162. func TestNewFamEmptyQualifier(t *testing.T) {
  163. cr := newChunkReader()
  164. mustProcess(t, cr, cc("rs", "fm1", "col1", 0, "val1", 0, false))
  165. _, err := cr.Process(cc(nilStr, "fm2", nilStr, 0, "val2", 0, true))
  166. if err == nil {
  167. t.Fatalf("Expected error on second chunk with no qualifier set")
  168. }
  169. }
  170. func mustProcess(t *testing.T, cr *chunkReader, cc *btspb.ReadRowsResponse_CellChunk) Row {
  171. row, err := cr.Process(cc)
  172. if err != nil {
  173. t.Fatal(err)
  174. }
  175. return row
  176. }
  177. // The read rows acceptance test reads a json file specifying a number of tests,
  178. // each consisting of one or more cell chunk text protos and one or more resulting
  179. // cells or errors.
  180. type AcceptanceTest struct {
  181. Tests []TestCase `json:"tests"`
  182. }
  183. type TestCase struct {
  184. Name string `json:"name"`
  185. Chunks []string `json:"chunks"`
  186. Results []TestResult `json:"results"`
  187. }
  188. type TestResult struct {
  189. RK string `json:"rk"`
  190. FM string `json:"fm"`
  191. Qual string `json:"qual"`
  192. TS int64 `json:"ts"`
  193. Value string `json:"value"`
  194. Error bool `json:"error"` // If true, expect an error. Ignore any other field.
  195. }
  196. func TestAcceptance(t *testing.T) {
  197. testJSON, err := ioutil.ReadFile("./testdata/read-rows-acceptance-test.json")
  198. if err != nil {
  199. t.Fatalf("could not open acceptance test file %v", err)
  200. }
  201. var accTest AcceptanceTest
  202. err = json.Unmarshal(testJSON, &accTest)
  203. if err != nil {
  204. t.Fatalf("could not parse acceptance test file: %v", err)
  205. }
  206. for _, test := range accTest.Tests {
  207. runTestCase(t, test)
  208. }
  209. }
  210. func runTestCase(t *testing.T, test TestCase) {
  211. // Increment an index into the result array as we get results
  212. cr := newChunkReader()
  213. var results []TestResult
  214. var seenErr bool
  215. for _, chunkText := range test.Chunks {
  216. // Parse and pass each cell chunk to the ChunkReader
  217. cc := &btspb.ReadRowsResponse_CellChunk{}
  218. err := proto.UnmarshalText(chunkText, cc)
  219. if err != nil {
  220. t.Errorf("[%s] failed to unmarshal text proto: %s\n%s", test.Name, chunkText, err)
  221. return
  222. }
  223. row, err := cr.Process(cc)
  224. if err != nil {
  225. results = append(results, TestResult{Error: true})
  226. seenErr = true
  227. break
  228. } else {
  229. // Turn the Row into TestResults
  230. for fm, ris := range row {
  231. for _, ri := range ris {
  232. tr := TestResult{
  233. RK: ri.Row,
  234. FM: fm,
  235. Qual: strings.Split(ri.Column, ":")[1],
  236. TS: int64(ri.Timestamp),
  237. Value: string(ri.Value),
  238. }
  239. results = append(results, tr)
  240. }
  241. }
  242. }
  243. }
  244. // Only Close if we don't have an error yet, otherwise Close: is expected.
  245. if !seenErr {
  246. err := cr.Close()
  247. if err != nil {
  248. results = append(results, TestResult{Error: true})
  249. }
  250. }
  251. got := toSet(results)
  252. want := toSet(test.Results)
  253. if !testutil.Equal(got, want) {
  254. t.Fatalf("[%s]: got: %v\nwant: %v\n", test.Name, got, want)
  255. }
  256. }
  257. func toSet(res []TestResult) map[TestResult]bool {
  258. set := make(map[TestResult]bool)
  259. for _, tr := range res {
  260. set[tr] = true
  261. }
  262. return set
  263. }
  264. // ri returns a ReadItem for the given components
  265. func ri(rk string, fm string, qual string, ts int64, val string) ReadItem {
  266. return ReadItem{Row: rk, Column: fmt.Sprintf("%s:%s", fm, qual), Value: []byte(val), Timestamp: Timestamp(ts)}
  267. }
  268. // cc returns a CellChunk proto
  269. func cc(rk string, fm string, qual string, ts int64, val string, size int32, commit bool) *btspb.ReadRowsResponse_CellChunk {
  270. // The components of the cell key are wrapped and can be null or empty
  271. var rkWrapper []byte
  272. if rk == nilStr {
  273. rkWrapper = nil
  274. } else {
  275. rkWrapper = []byte(rk)
  276. }
  277. var fmWrapper *wrappers.StringValue
  278. if fm != nilStr {
  279. fmWrapper = &wrappers.StringValue{Value: fm}
  280. } else {
  281. fmWrapper = nil
  282. }
  283. var qualWrapper *wrappers.BytesValue
  284. if qual != nilStr {
  285. qualWrapper = &wrappers.BytesValue{Value: []byte(qual)}
  286. } else {
  287. qualWrapper = nil
  288. }
  289. return &btspb.ReadRowsResponse_CellChunk{
  290. RowKey: rkWrapper,
  291. FamilyName: fmWrapper,
  292. Qualifier: qualWrapper,
  293. TimestampMicros: ts,
  294. Value: []byte(val),
  295. ValueSize: size,
  296. RowStatus: &btspb.ReadRowsResponse_CellChunk_CommitRow{CommitRow: commit}}
  297. }
  298. // ccData returns a CellChunk with only a value and size
  299. func ccData(val string, size int32, commit bool) *btspb.ReadRowsResponse_CellChunk {
  300. return cc(nilStr, nilStr, nilStr, 0, val, size, commit)
  301. }
  302. // ccReset returns a CellChunk with RestRow set to true
  303. func ccReset() *btspb.ReadRowsResponse_CellChunk {
  304. return &btspb.ReadRowsResponse_CellChunk{
  305. RowStatus: &btspb.ReadRowsResponse_CellChunk_ResetRow{ResetRow: true}}
  306. }