Skip to content

Commit

Permalink
storage/engine: introduce a datadriven framework to run MVCC tests
Browse files Browse the repository at this point in the history
Previously MVCC tests were hand-coded in Go.
This was making it hard(er) to introduce new tests or modify existing
tests.

This commit improves upon this situation by introducing a new
datadriven test, `TestMVCCHistories`, which runs MVCC
tests written using a DSL:

```
begin_txn      t=<name> [ts=<int>[,<int>]]
remove_txn     t=<name>
resolve_intent t=<name> k=<key> [status=<txnstatus>]
restart_txn    t=<name>
update_txn     t=<name> t2=<name>
step_txn       t=<name> [n=<int>]
advance_txn    t=<name> ts=<int>[,<int>]
txn_status     t=<name> status=<txnstatus>

check_intent   k=<key> [none]

put       [t=<name>] [ts=<int>[,<int>]] [resolve] k=<key> v=<string> [raw]
cput      [t=<name>] [ts=<int>[,<int>]] [resolve] k=<key> v=<string> [raw] [cond=<string>]
increment [t=<name>] [ts=<int>[,<int>]] [resolve] k=<key> [inc=<val>]
del       [t=<name>] [ts=<int>[,<int>]] [resolve] k=<key>
get       [t=<name>] [ts=<int>[,<int>]] [resolve] k=<key> [inconsistent] [tombstones]
scan      [t=<name>] [ts=<int>[,<int>]] [resolve] k=<key> [end=<key>] [inconsistent] [tombstones] [reverse]

merge     [ts=<int>[,<int>]] k=<key> v=<string> [raw]

clear_range    k=<key> end=<key>
```

Where `<key>` can be a simple string, or a string
prefixed by the following characters:

- `=foo` means exactly key `foo`
- `+foo` means `Key(foo).Next()`
- `-foo` means `Key(foo).PrefixEnd()`

Additionally, the pseudo-command `with` enables sharing
a group of arguments between multiple commands, for example:

```
with t=A
   begin_txn
   with k=a
     put v=b
     resolve_intent
```

Release note: None
  • Loading branch information
knz committed Nov 12, 2019
1 parent 0e9dd73 commit 037261e
Show file tree
Hide file tree
Showing 25 changed files with 2,096 additions and 1,214 deletions.
900 changes: 900 additions & 0 deletions pkg/storage/engine/mvcc_history_test.go

Large diffs are not rendered by default.

1,225 changes: 11 additions & 1,214 deletions pkg/storage/engine/mvcc_test.go

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions pkg/storage/engine/testdata/mvcc_histories/clear_range
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@

# Populate some values

run ok
with t=A v=abc resolve
begin_txn ts=44
put k=a
put k=a/123
put k=b
put k=b/123
put k=c
----
>> at end:
txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=0.000000044,0 min=0.000000000,0 seq=0} rw=true stat=PENDING orig=0.000000044,0 max=0.000000000,0 rts=0.000000000,0 wto=false
data: "a"/0.000000044,0 -> /BYTES/abc
data: "a/123"/0.000000044,0 -> /BYTES/abc
data: "b"/0.000000044,0 -> /BYTES/abc
data: "b/123"/0.000000044,0 -> /BYTES/abc
data: "c"/0.000000044,0 -> /BYTES/abc


run ok
clear_range k=a end=+a
----
>> at end:
data: "a/123"/0.000000044,0 -> /BYTES/abc
data: "b"/0.000000044,0 -> /BYTES/abc
data: "b/123"/0.000000044,0 -> /BYTES/abc
data: "c"/0.000000044,0 -> /BYTES/abc

run ok
clear_range k=a end=-a
----
>> at end:
data: "b"/0.000000044,0 -> /BYTES/abc
data: "b/123"/0.000000044,0 -> /BYTES/abc
data: "c"/0.000000044,0 -> /BYTES/abc

run ok
clear_range k=a end==b
----
>> at end:
data: "b"/0.000000044,0 -> /BYTES/abc
data: "b/123"/0.000000044,0 -> /BYTES/abc
data: "c"/0.000000044,0 -> /BYTES/abc

run ok
clear_range k=a end=+b
----
>> at end:
data: "b/123"/0.000000044,0 -> /BYTES/abc
data: "c"/0.000000044,0 -> /BYTES/abc

run ok
clear_range k=a end=-b
----
>> at end:
data: "c"/0.000000044,0 -> /BYTES/abc

run ok
clear_range k=a end=-c
----
>> at end:
<no data>
113 changes: 113 additions & 0 deletions pkg/storage/engine/testdata/mvcc_histories/conditional_put
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
run error
cput k=k v=v cond=v2 ts=123
----
>> at end:
<no data>
error: (*roachpb.ConditionFailedError:) unexpected value: <nil>

# Verify the difference between missing value and empty value.

run error
cput k=k v=v cond= ts=123,1
----
>> at end:
<no data>
error: (*roachpb.ConditionFailedError:) unexpected value: <nil>

# Do a conditional put with expectation that the value is completely missing; will succeed.

run ok
cput k=k v=v ts=123,2
----
>> at end:
data: "k"/0.000000123,2 -> /BYTES/v

# Another conditional put expecting value missing will fail, now that value1 is written.

run error
cput k=k v=v ts=123,3
----
>> at end:
data: "k"/0.000000123,2 -> /BYTES/v
error: (*roachpb.ConditionFailedError:) unexpected value: raw_bytes:"\000\000\000\000\003v" timestamp:<wall_time:123 logical:2 >

# Conditional put expecting wrong value2, will fail.

run error
cput k=k v=v cond=v2 ts=123,4
----
>> at end:
data: "k"/0.000000123,2 -> /BYTES/v
error: (*roachpb.ConditionFailedError:) unexpected value: raw_bytes:"\000\000\000\000\003v" timestamp:<wall_time:123 logical:2 >

# Move to an empty value. Will succeed.

run ok
cput k=k v= cond=v ts=123,5
----
>> at end:
data: "k"/0.000000123,5 -> /BYTES/
data: "k"/0.000000123,2 -> /BYTES/v

# Move key2 (which does not exist) to from value1 to value2.
# Expect it to fail since it does not exist with value1.

run error
cput k=k2 v=v2 cond=v ts=123,6
----
>> at end:
data: "k"/0.000000123,5 -> /BYTES/
data: "k"/0.000000123,2 -> /BYTES/v
error: (*roachpb.ConditionFailedError:) unexpected value: <nil>

# Move key2 (which does not yet exist) to from value1 to value2, but
# allowing for it not existing.

run ok
cput k=k2 v=v2 cond=v ts=123,7 allow_missing
----
>> at end:
data: "k"/0.000000123,5 -> /BYTES/
data: "k"/0.000000123,2 -> /BYTES/v
data: "k2"/0.000000123,7 -> /BYTES/v2

# Try to move key2 (which has value2) from value1 to empty. Expect error.

run error
cput k=k2 v= cond=v allow_missing ts=123,8
----
>> at end:
data: "k"/0.000000123,5 -> /BYTES/
data: "k"/0.000000123,2 -> /BYTES/v
data: "k2"/0.000000123,7 -> /BYTES/v2
error: (*roachpb.ConditionFailedError:) unexpected value: raw_bytes:"\000\000\000\000\003v2" timestamp:<wall_time:123 logical:7 >

# Try to move key2 (which has value2) from value2 to empty. Expect success.

run ok
cput k=k2 v= cond=v2 allow_missing ts=123,9
----
>> at end:
data: "k"/0.000000123,5 -> /BYTES/
data: "k"/0.000000123,2 -> /BYTES/v
data: "k2"/0.000000123,9 -> /BYTES/
data: "k2"/0.000000123,7 -> /BYTES/v2

# Now move to value2 from expected empty value.

run ok
cput k=k v=v2 cond= ts=123,10
----
>> at end:
data: "k"/0.000000123,10 -> /BYTES/v2
data: "k"/0.000000123,5 -> /BYTES/
data: "k"/0.000000123,2 -> /BYTES/v
data: "k2"/0.000000123,9 -> /BYTES/
data: "k2"/0.000000123,7 -> /BYTES/v2

# Verify we get value2 as expected.

run ok
get k=k ts=123,11
----
get: "k" -> /BYTES/v2
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
run ok
begin_txn t=A ts=123
----
>> at end:
txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=0.000000123,0 min=0.000000000,0 seq=0} rw=true stat=PENDING orig=0.000000123,0 max=0.000000000,0 rts=0.000000000,0 wto=false

# Write value1.

run ok
with t=A
step_txn
cput k=k v=v
----
>> at end:
txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=0.000000123,0 min=0.000000000,0 seq=1} rw=true stat=PENDING orig=0.000000123,0 max=0.000000000,0 rts=0.000000000,0 wto=false
meta: "k"/0.000000000,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=0.000000123,0 min=0.000000000,0 seq=1} ts=0.000000123,0 del=false klen=12 vlen=6
data: "k"/0.000000123,0 -> /BYTES/v

# Now, overwrite value1 with value2 from same txn; should see value1
# as pre-existing value.

run ok
with t=A
step_txn
cput k=k v=v2 cond=v
----
>> at end:
txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=0.000000123,0 min=0.000000000,0 seq=2} rw=true stat=PENDING orig=0.000000123,0 max=0.000000000,0 rts=0.000000000,0 wto=false
meta: "k"/0.000000000,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=0.000000123,0 min=0.000000000,0 seq=2} ts=0.000000123,0 del=false klen=12 vlen=7 ih={{1 /BYTES/v}}
data: "k"/0.000000123,0 -> /BYTES/v2

# Writing value3 from a new epoch should see nil again.

run ok
with t=A
restart_txn
step_txn
cput k=k v=v3
----
>> at end:
txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=1 ts=0.000000123,0 min=0.000000000,0 seq=1} rw=true stat=PENDING orig=0.000000123,0 max=0.000000000,0 rts=0.000000000,0 wto=false
meta: "k"/0.000000000,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=1 ts=0.000000123,0 min=0.000000000,0 seq=1} ts=0.000000123,0 del=false klen=12 vlen=7
data: "k"/0.000000123,0 -> /BYTES/v3

# Commit value3 at a later timestamp.

run ok
with t=A
advance_txn ts=124
resolve_intent k=k
----
>> at end:
txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=1 ts=0.000000124,0 min=0.000000000,0 seq=1} rw=true stat=PENDING orig=0.000000123,0 max=0.000000000,0 rts=0.000000000,0 wto=false
data: "k"/0.000000124,0 -> /BYTES/v3

# Write value4 with an old timestamp without txn...should get a write
# too old error.

run error
cput k=k v=v4 cond=v3 ts=123
----
>> at end:
data: "k"/0.000000124,1 -> /BYTES/v4
data: "k"/0.000000124,0 -> /BYTES/v3
error: (*roachpb.WriteTooOldError:) WriteTooOldError: write at timestamp 0.000000123,0 too old; wrote at 0.000000124,1
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# This test verifies the differing behavior
# of conditional puts when writing with an older timestamp than the
# existing write. If there's no transaction, the conditional put
# should use the latest value. When there's a transaction, then it
# should use the value at the specified timestamp.

run ok
put ts=10 k=k v=v1
----
>> at end:
data: "k"/0.000000010,0 -> /BYTES/v1

# Try a non-transactional put @t=1 with expectation of nil; should fail.
run error
cput ts=1 k=k v=v2
----
>> at end:
data: "k"/0.000000010,0 -> /BYTES/v1
error: (*roachpb.ConditionFailedError:) unexpected value: raw_bytes:"\000\000\000\000\003v1" timestamp:<wall_time:10 >

# Now do a non-transactional put @t=1 with expectation of value1; will "succeed" @t=10,1 with WriteTooOld.
run error
cput ts=1 k=k v=v2 cond=v1
----
>> at end:
data: "k"/0.000000010,1 -> /BYTES/v2
data: "k"/0.000000010,0 -> /BYTES/v1
error: (*roachpb.WriteTooOldError:) WriteTooOldError: write at timestamp 0.000000001,0 too old; wrote at 0.000000010,1

# Try a transactional put @t=1 with expectation of value2; should fail.
run error
with t=a
begin_txn ts=1
cput k=k v=v2 cond=v1
----
>> at end:
txn: "a" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=0.000000001,0 min=0.000000000,0 seq=0} rw=true stat=PENDING orig=0.000000001,0 max=0.000000000,0 rts=0.000000000,0 wto=false
data: "k"/0.000000010,1 -> /BYTES/v2
data: "k"/0.000000010,0 -> /BYTES/v1
error: (*roachpb.ConditionFailedError:) unexpected value: <nil>

# Now do a transactional put @t=1 with expectation of nil; will "succeed" @t=10,2 with WriteTooOld.
run error
with t=a
cput k=k v=v3
----
>> at end:
meta: "k"/0.000000000,0 -> txn={id=00000000 key=/Min pri=0.00000000 epo=0 ts=0.000000001,0 min=0.000000000,0 seq=0} ts=0.000000010,2 del=false klen=12 vlen=7
data: "k"/0.000000010,2 -> /BYTES/v3
data: "k"/0.000000010,1 -> /BYTES/v2
data: "k"/0.000000010,0 -> /BYTES/v1
error: (*roachpb.WriteTooOldError:) WriteTooOldError: write at timestamp 0.000000001,0 too old; wrote at 0.000000010,2
33 changes: 33 additions & 0 deletions pkg/storage/engine/testdata/mvcc_histories/deletes
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## A simple txn that deletes.
## The delete tombstone is placed alongside the previous value, at the newer timestamp.

run ok
with t=A
begin_txn ts=44
del k=a resolve
remove_txn
----
>> at end:
data: "a"/0.000000044,0 -> /<empty>

# Show the value disappears from gets.

run ok
with t=A
begin_txn ts=45
get k=a
remove_txn
----
get: "a" -> <no data>
>> at end:

# Show the tombstone.

run ok
with t=A
begin_txn ts=45
get k=a tombstones
remove_txn
----
get: "a" -> /<empty>
>> at end:
31 changes: 31 additions & 0 deletions pkg/storage/engine/testdata/mvcc_histories/empty_key
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
run error
get ts=0,1 k=
----
error: (*errors.fundamental:) attempted access to empty key

run error
put ts=0,1 k= v=a
----
>> at end:
<no data>
error: (*errors.fundamental:) attempted access to empty key

run ok
scan ts=0,1 k= end=a
----
scan: /Min-"a" -> <no data>


run error
scan ts=0,1 k=a end=
----
error: (*errors.fundamental:) attempted access to empty key

run error
begin_txn t=A
resolve_intent t=A k=
----
>> at end:
txn: "A" meta={id=00000000 key=/Min pri=0.00000000 epo=0 ts=0.000000000,0 min=0.000000000,0 seq=0} rw=true stat=PENDING orig=0.000000000,0 max=0.000000000,0 rts=0.000000000,0 wto=false
<no data>
error: (*errors.fundamental:) attempted access to empty key
18 changes: 18 additions & 0 deletions pkg/storage/engine/testdata/mvcc_histories/get_negative_timestamp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
run ok
put k=k v=v ts=1
----
>> at end:
data: "k"/0.000000001,0 -> /BYTES/v

run error
get k=k ts=-1
----
error: (*withstack.withStack:) cannot write to "k" at timestamp 0.-00000001,0


run error
put k=k v=v ts=-1
----
>> at end:
data: "k"/0.000000001,0 -> /BYTES/v
error: (*withstack.withStack:) cannot write to "k" at timestamp 0.-00000001,0
Loading

0 comments on commit 037261e

Please sign in to comment.