github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/changefeedccl/helpers_test.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 package changefeedccl 10 11 import ( 12 "context" 13 gosql "database/sql" 14 gojson "encoding/json" 15 "fmt" 16 "net/url" 17 "reflect" 18 "sort" 19 "strings" 20 "testing" 21 22 "github.com/cockroachdb/apd" 23 "github.com/cockroachdb/cockroach/pkg/base" 24 "github.com/cockroachdb/cockroach/pkg/ccl/changefeedccl/cdctest" 25 "github.com/cockroachdb/cockroach/pkg/security" 26 "github.com/cockroachdb/cockroach/pkg/sql/execinfra" 27 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 28 "github.com/cockroachdb/cockroach/pkg/testutils" 29 "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" 30 "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" 31 "github.com/cockroachdb/cockroach/pkg/util/hlc" 32 "github.com/cockroachdb/cockroach/pkg/util/log" 33 ) 34 35 func waitForSchemaChange( 36 t testing.TB, sqlDB *sqlutils.SQLRunner, stmt string, arguments ...interface{}, 37 ) { 38 sqlDB.Exec(t, stmt, arguments...) 39 row := sqlDB.QueryRow(t, "SELECT job_id FROM [SHOW JOBS] ORDER BY created DESC LIMIT 1") 40 var jobID string 41 row.Scan(&jobID) 42 43 testutils.SucceedsSoon(t, func() error { 44 row := sqlDB.QueryRow(t, "SELECT status FROM [SHOW JOBS] WHERE job_id = $1", jobID) 45 var status string 46 row.Scan(&status) 47 if status != "succeeded" { 48 return fmt.Errorf("Job %s had status %s, wanted 'succeeded'", jobID, status) 49 } 50 return nil 51 }) 52 } 53 54 func readNextMessages(t testing.TB, f cdctest.TestFeed, numMessages int, stripTs bool) []string { 55 t.Helper() 56 57 var actual []string 58 var value []byte 59 var message map[string]interface{} 60 for len(actual) < numMessages { 61 m, err := f.Next() 62 if log.V(1) { 63 log.Infof(context.Background(), `%v %s: %s->%s`, err, m.Topic, m.Key, m.Value) 64 } 65 if err != nil { 66 t.Fatal(err) 67 } else if m == nil { 68 t.Fatal(`expected message`) 69 } else if len(m.Key) > 0 || len(m.Value) > 0 { 70 if stripTs { 71 if err := gojson.Unmarshal(m.Value, &message); err != nil { 72 t.Fatalf(`%s: %s`, m.Value, err) 73 } 74 delete(message, "updated") 75 value, err = cdctest.ReformatJSON(message) 76 if err != nil { 77 t.Fatal(err) 78 } 79 } else { 80 value = m.Value 81 } 82 actual = append(actual, fmt.Sprintf(`%s: %s->%s`, m.Topic, m.Key, value)) 83 } 84 } 85 return actual 86 } 87 88 func assertPayloadsBase(t testing.TB, f cdctest.TestFeed, expected []string, stripTs bool) { 89 t.Helper() 90 actual := readNextMessages(t, f, len(expected), stripTs) 91 sort.Strings(expected) 92 sort.Strings(actual) 93 if !reflect.DeepEqual(expected, actual) { 94 t.Fatalf("expected\n %s\ngot\n %s", 95 strings.Join(expected, "\n "), strings.Join(actual, "\n ")) 96 } 97 } 98 99 func assertPayloads(t testing.TB, f cdctest.TestFeed, expected []string) { 100 t.Helper() 101 assertPayloadsBase(t, f, expected, false) 102 } 103 104 func assertPayloadsStripTs(t testing.TB, f cdctest.TestFeed, expected []string) { 105 t.Helper() 106 assertPayloadsBase(t, f, expected, true) 107 } 108 109 func avroToJSON(t testing.TB, reg *testSchemaRegistry, avroBytes []byte) []byte { 110 if len(avroBytes) == 0 { 111 return nil 112 } 113 native, err := reg.encodedAvroToNative(avroBytes) 114 if err != nil { 115 t.Fatal(err) 116 } 117 // The avro textual format is a more natural fit, but it's non-deterministic 118 // because of go's randomized map ordering. Instead, we use gojson.Marshal, 119 // which sorts its object keys and so is deterministic. 120 json, err := gojson.Marshal(native) 121 if err != nil { 122 t.Fatal(err) 123 } 124 return json 125 } 126 127 func assertPayloadsAvro( 128 t testing.TB, reg *testSchemaRegistry, f cdctest.TestFeed, expected []string, 129 ) { 130 t.Helper() 131 132 var actual []string 133 for len(actual) < len(expected) { 134 m, err := f.Next() 135 if err != nil { 136 t.Fatal(err) 137 } else if m == nil { 138 t.Fatal(`expected message`) 139 } else if m.Key != nil { 140 key, value := avroToJSON(t, reg, m.Key), avroToJSON(t, reg, m.Value) 141 actual = append(actual, fmt.Sprintf(`%s: %s->%s`, m.Topic, key, value)) 142 } 143 } 144 145 // The tests that use this aren't concerned with order, just that these are 146 // the next len(expected) messages. 147 sort.Strings(expected) 148 sort.Strings(actual) 149 if !reflect.DeepEqual(expected, actual) { 150 t.Fatalf("expected\n %s\ngot\n %s", 151 strings.Join(expected, "\n "), strings.Join(actual, "\n ")) 152 } 153 } 154 155 func parseTimeToHLC(t testing.TB, s string) hlc.Timestamp { 156 t.Helper() 157 d, _, err := apd.NewFromString(s) 158 if err != nil { 159 t.Fatal(err) 160 } 161 ts, err := tree.DecimalToHLC(d) 162 if err != nil { 163 t.Fatal(err) 164 } 165 return ts 166 } 167 168 func expectResolvedTimestamp(t testing.TB, f cdctest.TestFeed) hlc.Timestamp { 169 t.Helper() 170 m, err := f.Next() 171 if err != nil { 172 t.Fatal(err) 173 } else if m == nil { 174 t.Fatal(`expected message`) 175 } 176 if m.Key != nil { 177 t.Fatalf(`unexpected row %s: %s -> %s`, m.Topic, m.Key, m.Value) 178 } 179 if m.Resolved == nil { 180 t.Fatal(`expected a resolved timestamp notification`) 181 } 182 183 var resolvedRaw struct { 184 Resolved string `json:"resolved"` 185 } 186 if err := gojson.Unmarshal(m.Resolved, &resolvedRaw); err != nil { 187 t.Fatal(err) 188 } 189 190 return parseTimeToHLC(t, resolvedRaw.Resolved) 191 } 192 193 func expectResolvedTimestampAvro( 194 t testing.TB, reg *testSchemaRegistry, f cdctest.TestFeed, 195 ) hlc.Timestamp { 196 t.Helper() 197 m, err := f.Next() 198 if err != nil { 199 t.Fatal(err) 200 } else if m == nil { 201 t.Fatal(`expected message`) 202 } 203 if m.Key != nil { 204 key, value := avroToJSON(t, reg, m.Key), avroToJSON(t, reg, m.Value) 205 t.Fatalf(`unexpected row %s: %s -> %s`, m.Topic, key, value) 206 } 207 if m.Resolved == nil { 208 t.Fatal(`expected a resolved timestamp notification`) 209 } 210 resolvedNative, err := reg.encodedAvroToNative(m.Resolved) 211 if err != nil { 212 t.Fatal(err) 213 } 214 resolved := resolvedNative.(map[string]interface{})[`resolved`] 215 return parseTimeToHLC(t, resolved.(map[string]interface{})[`string`].(string)) 216 } 217 218 func sinklessTest(testFn func(*testing.T, *gosql.DB, cdctest.TestFeedFactory)) func(*testing.T) { 219 return func(t *testing.T) { 220 ctx := context.Background() 221 knobs := base.TestingKnobs{DistSQL: &execinfra.TestingKnobs{Changefeed: &TestingKnobs{}}} 222 s, db, _ := serverutils.StartServer(t, base.TestServerArgs{ 223 Knobs: knobs, 224 UseDatabase: `d`, 225 }) 226 defer s.Stopper().Stop(ctx) 227 sqlDB := sqlutils.MakeSQLRunner(db) 228 sqlDB.Exec(t, `SET CLUSTER SETTING kv.rangefeed.enabled = true`) 229 // TODO(dan): We currently have to set this to an extremely conservative 230 // value because otherwise schema changes become flaky (they don't commit 231 // their txn in time, get pushed by closed timestamps, and retry forever). 232 // This is more likely when the tests run slower (race builds or inside 233 // docker). The conservative value makes our tests take a lot longer, 234 // though. Figure out some way to speed this up. 235 sqlDB.Exec(t, `SET CLUSTER SETTING kv.closed_timestamp.target_duration = '1s'`) 236 // TODO(dan): This is still needed to speed up table_history, that should be 237 // moved to RangeFeed as well. 238 sqlDB.Exec(t, `SET CLUSTER SETTING changefeed.experimental_poll_interval = '10ms'`) 239 sqlDB.Exec(t, `CREATE DATABASE d`) 240 241 sink, cleanup := sqlutils.PGUrl(t, s.ServingSQLAddr(), t.Name(), url.User(security.RootUser)) 242 defer cleanup() 243 f := cdctest.MakeSinklessFeedFactory(s, sink) 244 testFn(t, db, f) 245 } 246 } 247 248 func enterpriseTest(testFn func(*testing.T, *gosql.DB, cdctest.TestFeedFactory)) func(*testing.T) { 249 return enterpriseTestWithServerArgs(nil, testFn) 250 } 251 252 func enterpriseTestWithServerArgs( 253 argsFn func(args *base.TestServerArgs), 254 testFn func(*testing.T, *gosql.DB, cdctest.TestFeedFactory), 255 ) func(*testing.T) { 256 return func(t *testing.T) { 257 ctx := context.Background() 258 259 flushCh := make(chan struct{}, 1) 260 defer close(flushCh) 261 knobs := base.TestingKnobs{DistSQL: &execinfra.TestingKnobs{Changefeed: &TestingKnobs{ 262 AfterSinkFlush: func() error { 263 select { 264 case flushCh <- struct{}{}: 265 default: 266 } 267 return nil 268 }, 269 }}} 270 args := base.TestServerArgs{ 271 UseDatabase: "d", 272 Knobs: knobs, 273 } 274 if argsFn != nil { 275 argsFn(&args) 276 } 277 s, db, _ := serverutils.StartServer(t, args) 278 defer s.Stopper().Stop(ctx) 279 sqlDB := sqlutils.MakeSQLRunner(db) 280 sqlDB.Exec(t, `SET CLUSTER SETTING kv.rangefeed.enabled = true`) 281 sqlDB.Exec(t, `SET CLUSTER SETTING kv.closed_timestamp.target_duration = '1s'`) 282 sqlDB.Exec(t, `SET CLUSTER SETTING changefeed.experimental_poll_interval = '10ms'`) 283 sqlDB.Exec(t, `CREATE DATABASE d`) 284 sink, cleanup := sqlutils.PGUrl(t, s.ServingSQLAddr(), t.Name(), url.User(security.RootUser)) 285 defer cleanup() 286 f := cdctest.MakeTableFeedFactory(s, db, flushCh, sink) 287 288 testFn(t, db, f) 289 } 290 } 291 292 func cloudStorageTest( 293 testFn func(*testing.T, *gosql.DB, cdctest.TestFeedFactory), 294 ) func(*testing.T) { 295 return func(t *testing.T) { 296 ctx := context.Background() 297 298 dir, dirCleanupFn := testutils.TempDir(t) 299 defer dirCleanupFn() 300 301 flushCh := make(chan struct{}, 1) 302 defer close(flushCh) 303 knobs := base.TestingKnobs{DistSQL: &execinfra.TestingKnobs{Changefeed: &TestingKnobs{ 304 AfterSinkFlush: func() error { 305 select { 306 case flushCh <- struct{}{}: 307 default: 308 } 309 return nil 310 }, 311 }}} 312 313 s, db, _ := serverutils.StartServer(t, base.TestServerArgs{ 314 UseDatabase: "d", 315 ExternalIODir: dir, 316 Knobs: knobs, 317 }) 318 defer s.Stopper().Stop(ctx) 319 sqlDB := sqlutils.MakeSQLRunner(db) 320 sqlDB.Exec(t, `SET CLUSTER SETTING kv.rangefeed.enabled = true`) 321 sqlDB.Exec(t, `SET CLUSTER SETTING kv.closed_timestamp.target_duration = '1s'`) 322 sqlDB.Exec(t, `SET CLUSTER SETTING changefeed.experimental_poll_interval = '10ms'`) 323 sqlDB.Exec(t, `CREATE DATABASE d`) 324 325 f := cdctest.MakeCloudFeedFactory(s, db, dir, flushCh) 326 testFn(t, db, f) 327 } 328 } 329 330 func feed( 331 t testing.TB, f cdctest.TestFeedFactory, create string, args ...interface{}, 332 ) cdctest.TestFeed { 333 t.Helper() 334 feed, err := f.Feed(create, args...) 335 if err != nil { 336 t.Fatal(err) 337 } 338 return feed 339 } 340 341 func closeFeed(t testing.TB, f cdctest.TestFeed) { 342 t.Helper() 343 if err := f.Close(); err != nil { 344 t.Fatal(err) 345 } 346 } 347 348 func forceTableGC( 349 t testing.TB, 350 tsi serverutils.TestServerInterface, 351 sqlDB *sqlutils.SQLRunner, 352 database, table string, 353 ) { 354 t.Helper() 355 if err := tsi.ForceTableGC(context.Background(), database, table, tsi.Clock().Now()); err != nil { 356 t.Fatal(err) 357 } 358 }