Skip to content
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

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
356 changes: 356 additions & 0 deletions pkg/stanza/fileconsumer/internal/trie/trie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Copy link
Owner

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, as ABC doesn't exist anymore.

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"),
},
Copy link
Owner

Choose a reason for hiding this comment

The 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
},
Copy link
Owner

Choose a reason for hiding this comment

The 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"),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's true that ABCDEF was pushed down, but we still have ABC in trie.
If we match ABCDEF, it will recognize ABC \w DEF appended

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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this won't necessarily be false.
Consider, A-B-C-D(true)-E-F(true)
Deleting ABCDEF will cause A-B-C-D(true).
Now, if we do HasKey(ABCDEF), it will return true coz ABCD \w EF appended

Copy link
Owner

Choose a reason for hiding this comment

The 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.
Will close out readers after emitting which haven't been redectected in last 3 poll cycles.

}
}

// 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))
}
}
}
Loading