-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add tree-based test suite that explores many possible sequences of operations #2
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -198,3 +198,359 @@ func runTest(t *testing.T, trie *Trie, tc *testCase) { | |
} | ||
} | ||
} | ||
|
||
func TestTrieOpSequences(t *testing.T) { | ||
opTree{ | ||
continuations: map[string]opTree{ | ||
"Found:ABC": opTree{ // First poll finds only ABC. | ||
ops: []testOp{ | ||
put("ABC"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABC": opTree{ // Finish reading ABC and remove from trie | ||
ops: []testOp{ | ||
del("ABC", true, "was just added"), | ||
has("ABC", false, "was just deleted"), | ||
}, | ||
}, | ||
"Found:ABCDEF": opTree{ // Next poll finds ABCDEF | ||
ops: []testOp{ | ||
has("ABCDEF", true, "recognize ABC w/ DEF appended"), | ||
put("ABCDEF"), // TODO HasKey returns true, so how do we know to call this? | ||
has("ABC", false, "should push ABC down to ABCDEF"), | ||
}, | ||
// TODO When we started reading this file, we identified it as ABC. | ||
// However, we have an updated understanding that it is ABCDEF. | ||
// Should we only have to delete ABC, or only ABCDEF, or both? | ||
continuations: map[string]opTree{ | ||
"DeleteAs:ABC": opTree{ // Done reading the file, remove it as ABC | ||
ops: []testOp{ | ||
del("ABC", false, "should have been pushed down when ABCDEF was added"), // TODO incorrectly returns true | ||
has("ABCDEF", true, "should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"DeleteAs:ABCDEF": opTree{ // Also remove it as ABCDEF | ||
ops: []testOp{ | ||
del("ABCDEF", true, "just confirmed it exists"), // TODO this fails to delete | ||
}, | ||
}, | ||
}, | ||
}, | ||
"DeleteAs:ABCDEF": opTree{ // Done reading the file, remove it as ABC | ||
ops: []testOp{ | ||
del("ABCDEF", true, "trying to delete ABC should not affect ABCDEF"), | ||
has("ABC", true, "should not have been deleted"), | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will return false, as we previously pushed down the value at #219 |
||
continuations: map[string]opTree{ | ||
"DeleteAs:ABC": opTree{ // Also remove it as ABC | ||
ops: []testOp{ | ||
del("ABC", true, "just confirmed it exists"), // TODO this fails to delete | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. Trie will be basically empty, I just confirmed it. |
||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"Found:ABC,ABCDEF": opTree{ // First poll finds ABCDEF and ABC. | ||
ops: []testOp{ | ||
// In order to avoid overwriting ABC with ABCDEF, we need to add ABCDEF first. | ||
// TODO Should poll results be sorted by decreasing length before adding to trie? | ||
put("ABCDEF"), | ||
put("ABC"), | ||
has("ABCDEF", true, "adding ABC after ABCDEF shouldn't affect ABCDEF"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABC": opTree{ // Finish reading ABC and remove from trie | ||
ops: []testOp{ | ||
del("ABC", true, "just confirmed ABC exists"), | ||
has("ABCDEF", true, "ABCDEF should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCDEF": opTree{ // Finish reading ABCDEF and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEF", true, "just confirmed ABCDEF exists"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
"Done:ABCDEF": opTree{ // Finish reading ABCDEF and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEF", true, "just confirmed ABCDEF exists"), | ||
has("ABC", true, "should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABC": opTree{ // Finish reading ABC and remove from trie | ||
ops: []testOp{ | ||
del("ABC", true, "just confirmed ABC exists"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
"Found:ABCxyz,ABCDEF": opTree{ // Next poll finds ABCxyz and ABCDEF | ||
ops: []testOp{ | ||
has("ABCxyz", true, "recognize ABC w/ xyz appended"), | ||
put("ABCxyz"), // TODO how do we know we need to call this? | ||
has("ABC", false, "ABC should have been pushed down to ABCxyz"), | ||
has("ABCDEF", true, "ABCDEF should not have been affected"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCDEF": opTree{ // Finish reading ABCDEF and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEF", true, "just confirmed ABCDEF exists"), | ||
has("ABCxyz", true, "ABCxyz should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCxyz": opTree{ // Finish reading ABCxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCxyz", true, "just confirmed ABCxyz exists"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
"Done:ABCxyz": opTree{ // Finish reading ABCxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCxyz", true, "just confirmed ABCxyz exists"), | ||
has("ABCDEF", true, "ABCDEF should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCDEF": opTree{ // Finish reading ABCDEF and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEF", true, "just confirmed ABCDEF exists"), | ||
}, | ||
}, | ||
"Found:ABCDEFxyz": opTree{ // Next poll finds ABCDEFxyz | ||
ops: []testOp{ | ||
has("ABCDEFxyz", true, "recognize ABCDEF w/ xyz appended"), | ||
put("ABCDEFxyz"), // TODO how do we know we need to call this? | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCDEFxyz": opTree{ // Finish reading ABCDEFxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEFxyz", true, "just confirmed ABCDEFxyz exists"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"Found:ABCxyz,ABCDEFxyz": opTree{ // Next poll finds ABCxyz and ABCDEFxyz | ||
ops: []testOp{ | ||
has("ABCDEFxyz", true, "recognize ABCDEF w/ xyz appended"), | ||
put("ABCDEFxyz"), // TODO how do we know we need to call this? | ||
has("ABCxyz", true, "ABCxyz should not have been affected"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCxyz": opTree{ // Finish reading ABCxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCxyz", true, "just confirmed ABCxyz exists"), | ||
has("ABCDEFxyz", true, "ABCDEFxyz should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCDEFxyz": opTree{ // Finish reading ABCDEFxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEFxyz", true, "just confirmed ABCDEFxyz exists"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
"Done:ABCDEFxyz": opTree{ // Finish reading ABCDEFxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEFxyz", true, "just confirmed ABCDEFxyz exists"), | ||
has("ABCxyz", true, "ABCxyz should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCxyz": opTree{ // Finish reading ABCxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCxyz", true, "just confirmed ABCxyz exists"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"Found:ABC,ABCDEFxyz": opTree{ // Next poll finds ABC and ABCDEFxyz | ||
ops: []testOp{ | ||
has("ABCDEFxyz", true, "recognize ABCDEF w/ xyz appended"), | ||
put("ABCDEFxyz"), // TODO how do we know we need to call this? | ||
has("ABCDEF", false, "ABCDEF should have been pushed down to ABCDEFxyz"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's true that |
||
has("ABC", true, "ABC should not have been affected"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABC": opTree{ // Finish reading ABC and remove from trie | ||
ops: []testOp{ | ||
del("ABC", true, "just confirmed ABC exists"), | ||
has("ABCDEFxyz", true, "ABCDEFxyz should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCDEFxyz": opTree{ // Finish reading ABCDEFxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEFxyz", true, "just confirmed ABCDEFxyz exists"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
"Done:ABCDEFxyz": opTree{ // Finish reading ABCDEFxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEFxyz", true, "just confirmed ABCDEFxyz exists"), | ||
has("ABC", true, "ABC should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABC": opTree{ // Finish reading ABC and remove from trie | ||
ops: []testOp{ | ||
del("ABC", true, "just confirmed ABC exists"), | ||
}, | ||
}, | ||
"Found:ABCxyz": opTree{ // Next poll finds ABCxyz | ||
ops: []testOp{ | ||
has("ABCxyz", true, "recognize ABC w/ xyz appended"), | ||
put("ABCxyz"), // TODO how do we know we need to call this? | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCxyz": opTree{ // Finish reading ABCxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCxyz", true, "just confirmed ABCxyz exists"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"Found:ABCxyz,ABCDEFxyz": opTree{ // Next poll finds ABCxyz and ABCDEFxyz | ||
ops: []testOp{ | ||
has("ABCxyz", true, "recognize ABC w/ xyz appended"), | ||
put("ABCxyz"), // TODO how do we know we need to call this? | ||
has("ABCDEFxyz", true, "ABCDEFxyz should not have been affected"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCxyz": opTree{ // Finish reading ABCxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCxyz", true, "just confirmed ABCxyz exists"), | ||
has("ABCDEFxyz", true, "ABCDEFxyz should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCDEFxyz": opTree{ // Finish reading ABCDEFxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEFxyz", true, "just confirmed ABCDEFxyz exists"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
"Done:ABCDEFxyz": opTree{ // Finish reading ABCDEFxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEFxyz", true, "just confirmed ABCDEFxyz exists"), | ||
has("ABCxyz", true, "ABCxyz should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCxyz": opTree{ // Finish reading ABCxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCxyz", true, "just confirmed ABCxyz exists"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"Found:ABCxyz,ABCDEFxyz": opTree{ // Next poll finds ABCxyz and ABCDEFxyz | ||
ops: []testOp{ | ||
// Inserting longer string first | ||
has("ABCDEFxyz", true, "recognize ABCDEF w/ xyz appended"), | ||
put("ABCDEFxyz"), // TODO how do we know we need to call this? | ||
has("ABCDEF", false, "ABCDEF should have been pushed down to ABCDEFxyz"), | ||
has("ABC", true, "ABC should not have been affected"), | ||
|
||
has("ABCxyz", true, "recognize ABC w/ xyz appended"), | ||
put("ABCxyz"), // TODO how do we know we need to call this? | ||
has("ABC", false, "ABC should have been pushed down to ABCxyz"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCxyz": opTree{ // Finish reading ABCxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCxyz", true, "just confirmed ABCxyz exists"), | ||
has("ABCDEFxyz", true, "ABCDEFxyz should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCDEFxyz": opTree{ // Finish reading ABCDEFxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEFxyz", true, "just confirmed ABCDEFxyz exists"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
"Done:ABCDEFxyz": opTree{ // Finish reading ABCDEFxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCDEFxyz", true, "just confirmed ABCDEFxyz exists"), | ||
has("ABCxyz", true, "ABCxyz should not have been deleted"), | ||
}, | ||
continuations: map[string]opTree{ | ||
"Done:ABCxyz": opTree{ // Finish reading ABCxyz and remove from trie | ||
ops: []testOp{ | ||
del("ABCxyz", true, "just confirmed ABCxyz exists"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}.run(t, []testOp{})(t) | ||
} | ||
|
||
// testOp is one HasKey, Put, or Delete call to the trie, | ||
// along with validation of expectations. | ||
type testOp func(t *testing.T, trie *Trie) | ||
|
||
func has(key string, expect bool, why string) testOp { | ||
return func(t *testing.T, trie *Trie) { | ||
assert.Equalf(t, trie.HasKey([]byte(key)), expect, why) | ||
} | ||
} | ||
|
||
// put automatically asserts that the trie contains the key after adding. | ||
func put(key string) testOp { | ||
return func(t *testing.T, trie *Trie) { | ||
trie.Put([]byte(key)) | ||
assert.Truef(t, trie.HasKey([]byte(key)), "called Put(%s) but HasKey(%s) is still false", key, key) | ||
} | ||
} | ||
|
||
// del automatically asserts that the trie no longer contains the key after deleting it. | ||
func del(key string, expect bool, why string) testOp { | ||
return func(t *testing.T, trie *Trie) { | ||
assert.Equalf(t, trie.Delete([]byte(key)), expect, why) | ||
assert.Falsef(t, trie.HasKey([]byte(key)), "called Delete(%s) but HasKey(%s) is still true", key, key) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, this won't necessarily be false. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need now worry about losing the data, even if the file gets deleted, we have the reader stored. |
||
} | ||
} | ||
|
||
// opTree represents many possible sequences of operations that may be performed on a trie. | ||
// Each opTree represents a stage at which a concrete sequence of operations should occur "now". | ||
// An opTree's "continuations" are possible "futures" that may occur next, each of which may have a variety of further continuations. | ||
// The tree structure allows us to thoroughly explore the space of possible sequences without having to define the same setup steps over and over. | ||
type opTree struct { | ||
ops []testOp | ||
continuations map[string]opTree | ||
} | ||
|
||
func (ot opTree) run(t *testing.T, opSequence []testOp) func(*testing.T) { | ||
return func(t *testing.T) { | ||
trie := NewTrie() | ||
opSequence = append(opSequence, ot.ops...) | ||
for _, op := range opSequence { | ||
op(t, trie) | ||
} | ||
if t.Failed() { | ||
// All continuations will fail at the same point, so don't bother running them | ||
return | ||
} | ||
for name, continuation := range ot.continuations { | ||
t.Run(name, continuation.run(t, opSequence)) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should only allow deleting
ABCDEF
, asABC
doesn't exist anymore.