-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
test: add test case for memiterator.go #22725
Conversation
📝 WalkthroughWalkthroughThis pull request introduces unit tests for the Changes
Possibly related PRs
Suggested labels
Suggested reviewers
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
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.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (3)
store/cachekv/internal/memiterator_test.go (3)
9-9
: Remove unnecessary commentThe commented line
// db.set()
appears to be leftover debug/development code.- // db.set()
7-32
: Enhance test coverage and robustnessConsider the following improvements to make the test more comprehensive:
- Add verification of values, not just keys
- Use
reflect.DeepEqual
for slice comparison- Consider using table-driven tests for better coverage of edge cases
- Add cleanup of test resources
Here's a suggested improvement:
func TestMemIterator_Ascending(t *testing.T) { + tests := []struct { + name string + setup map[string]string + start []byte + end []byte + expected []string + values []string + }{ + { + name: "basic ascending", + setup: map[string]string{ + "a": "value_a", + "b": "value_b", + "c": "value_c", + }, + start: []byte("a"), + end: []byte("c"), + expected: []string{"a", "b", "c"}, + values: []string{"value_a", "value_b", "value_c"}, + }, + // Add more test cases here + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := NewBTree() + defer db.Close() // if Close method exists + + for k, v := range tt.setup { + db.Set([]byte(k), []byte(v)) + } + + iterator := newMemIterator(tt.start, tt.end, db, true) + + var keys, values []string + for iterator.Valid() { + keys = append(keys, string(iterator.Key())) + values = append(values, string(iterator.Value())) + iterator.Next() + } + + if !reflect.DeepEqual(keys, tt.expected) { + t.Errorf("Expected keys %v, got %v", tt.expected, keys) + } + + if !reflect.DeepEqual(values, tt.values) { + t.Errorf("Expected values %v, got %v", tt.values, values) + } + + if iterator.Valid() { + t.Errorf("Iterator should be invalid after last item") + } + }) + } }
62-73
: Consider additional empty range test casesWhile the current test covers a basic empty range scenario, consider adding more cases:
- Empty range at the start of the keyspace
- Empty range at the end of the keyspace
- Zero-length range (start == end)
- Invalid range (start > end)
Here's a suggested improvement:
func TestMemIterator_EmptyRange(t *testing.T) { + tests := []struct { + name string + start []byte + end []byte + }{ + {"empty range middle", []byte("d"), []byte("e")}, + {"empty range start", []byte("0"), []byte("a")}, + {"empty range end", []byte("d"), []byte("z")}, + {"zero length range", []byte("a"), []byte("a")}, + {"invalid range", []byte("z"), []byte("a")}, + } + db := NewBTree() db.Set([]byte("a"), []byte("value_a")) db.Set([]byte("b"), []byte("value_b")) db.Set([]byte("c"), []byte("value_c")) - iterator := newMemIterator([]byte("d"), []byte("e"), db, true) - - if iterator.Valid() { - t.Errorf("Iterator should be invalid for empty range") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + iterator := newMemIterator(tt.start, tt.end, db, true) + if iterator.Valid() { + t.Errorf("%s: Iterator should be invalid for empty range", tt.name) + } + }) } }
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
📒 Files selected for processing (1)
store/cachekv/internal/memiterator_test.go
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
store/cachekv/internal/memiterator_test.go (2)
Pattern **/*.go
: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.
Pattern **/*_test.go
: "Assess the unit test code assessing sufficient code coverage for the changes associated in the pull request"
🔇 Additional comments (1)
store/cachekv/internal/memiterator_test.go (1)
1-6
: LGTM!
Package declaration and imports are clean and appropriate for the test file.
func TestMemIterator_Descending(t *testing.T) { | ||
db := NewBTree() | ||
|
||
db.Set([]byte("a"), []byte("value_a")) | ||
db.Set([]byte("b"), []byte("value_b")) | ||
db.Set([]byte("c"), []byte("value_c")) | ||
db.Set([]byte("d"), []byte("value_d")) | ||
|
||
iterator := newMemIterator([]byte("a"), []byte("d"), db, false) | ||
|
||
var result []string | ||
for iterator.Valid() { | ||
result = append(result, string(iterator.Key())) | ||
iterator.Next() | ||
} | ||
|
||
expected := []string{"c", "b", "a"} | ||
for i, key := range result { | ||
if key != expected[i] { | ||
t.Errorf("Expected %s, got %s", expected[i], key) | ||
} | ||
} | ||
|
||
if iterator.Valid() { | ||
t.Errorf("Iterator should be invalid after last item") | ||
} | ||
} |
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.
💡 Codebase verification
Test expectations are incorrect for descending iterator range
The test has a bug in its expectations. For a descending iterator:
- When initialized with
start=a
andend=d
, it should iterate through all values< d
and>= a
- The implementation in
keyInRange
correctly enforces these bounds - Therefore, the expected result should be
["d", "c", "b", "a"]
instead of["c", "b", "a"]
The current test is missing the "d" key in its expectations, even though:
- The key is properly set in the database
- It falls within the iterator's range (being less than the end bound "d")
- The implementation would include it based on the
keyInRange
logic
🔗 Analysis chain
Fix inconsistency in descending iterator test
There appears to be a discrepancy in the test logic:
- The test sets up keys "a", "b", "c", "d"
- Iterator range is from "a" to "d"
- Expected result is ["c", "b", "a"] which excludes "d"
This could indicate either:
- A bug in the test expectations
- A bug in the iterator implementation
- Unclear requirements about boundary handling
Let's verify the expected behavior:
Let me gather more information about the iterator's behavior by checking its implementation.
Let me check one more thing about the iterator's implementation, specifically focusing on the descending case.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for memIterator implementation and documentation
ast-grep --pattern 'type memIterator struct {
$$$
}'
# Look for any related tests or documentation
rg -A 5 'memIterator'
Length of output: 18909
Script:
#!/bin/bash
# Search for the implementation of Next() and keyInRange() for descending iterator
ast-grep --pattern 'func (mi *memIterator) Next() {
$$$
if mi.ascending {
$$$
} else {
$$$
}
}'
# Look for any other descending iterator tests
rg -A 10 'TestMemIterator.*Descending'
Length of output: 942
Script:
#!/bin/bash
# Search for the complete Next() implementation
ast-grep --pattern 'func (mi *memIterator) Next() {
$$$
}'
# Look for the keyInRange implementation
ast-grep --pattern 'func (mi *memIterator) keyInRange(key []byte) bool {
$$$
}'
Length of output: 4008
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.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (4)
store/cachekv/internal/memiterator_test.go (4)
9-9
: Remove commented codeRemove the commented line
// db.set()
as it doesn't provide any value and could confuse readers.
7-32
: Consider enhancing test coverage and structureSeveral improvements could make this test more robust:
- Use table-driven tests to test multiple scenarios
- Verify values along with keys
- Add cleanup using
t.Cleanup()
Here's a suggested refactor:
func TestMemIterator_Ascending(t *testing.T) { - db := NewBTree() - db.Set([]byte("a"), []byte("value_a")) - db.Set([]byte("b"), []byte("value_b")) - db.Set([]byte("c"), []byte("value_c")) - - iterator := newMemIterator([]byte("a"), []byte("c"), db, true) - - var result []string - for iterator.Valid() { - result = append(result, string(iterator.Key())) - iterator.Next() - } - - expected := []string{"a", "b", "c"} - for i, key := range result { - if key != expected[i] { - t.Errorf("Expected %s, got %s", expected[i], key) - } - } + tests := []struct { + name string + entries map[string]string + start string + end string + expected []struct{ key, value string } + }{ + { + name: "basic ascending order", + entries: map[string]string{ + "a": "value_a", + "b": "value_b", + "c": "value_c", + }, + start: "a", + end: "c", + expected: []struct{ key, value string }{ + {"a", "value_a"}, + {"b", "value_b"}, + {"c", "value_c"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := NewBTree() + t.Cleanup(func() { + // Add cleanup if needed + }) + + for k, v := range tt.entries { + db.Set([]byte(k), []byte(v)) + } + + iterator := newMemIterator([]byte(tt.start), []byte(tt.end), db, true) + + var idx int + for iterator.Valid() { + if idx >= len(tt.expected) { + t.Fatalf("Iterator returned more items than expected") + } + + gotKey := string(iterator.Key()) + gotValue := string(iterator.Value()) + + if gotKey != tt.expected[idx].key { + t.Errorf("key at position %d = %s, want %s", idx, gotKey, tt.expected[idx].key) + } + if gotValue != tt.expected[idx].value { + t.Errorf("value at position %d = %s, want %s", idx, gotValue, tt.expected[idx].value) + } + + iterator.Next() + idx++ + } + + if idx != len(tt.expected) { + t.Errorf("got %d items, want %d items", idx, len(tt.expected)) + } + + if iterator.Valid() { + t.Error("Iterator should be invalid after last item") + } + }) + } }
62-73
: Enhance empty range test coverageWhile the current test covers a basic empty range scenario, consider adding more test cases:
- Empty range at the beginning (before any keys)
- Empty range between existing keys
- Verify that calling Next() on an invalid iterator maintains invalidity
Here's a suggested enhancement:
func TestMemIterator_EmptyRange(t *testing.T) { - db := NewBTree() - db.Set([]byte("a"), []byte("value_a")) - db.Set([]byte("b"), []byte("value_b")) - db.Set([]byte("c"), []byte("value_c")) - - iterator := newMemIterator([]byte("d"), []byte("e"), db, true) - - if iterator.Valid() { - t.Errorf("Iterator should be invalid for empty range") - } + tests := []struct { + name string + setup []struct{ key, value string } + start string + end string + }{ + { + name: "empty range after keys", + setup: []struct{ key, value string }{ + {"a", "value_a"}, + {"b", "value_b"}, + {"c", "value_c"}, + }, + start: "d", + end: "e", + }, + { + name: "empty range before keys", + setup: []struct{ key, value string }{ + {"c", "value_c"}, + {"d", "value_d"}, + }, + start: "a", + end: "b", + }, + { + name: "empty range between keys", + setup: []struct{ key, value string }{ + {"a", "value_a"}, + {"c", "value_c"}, + }, + start: "b", + end: "b", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := NewBTree() + for _, kv := range tt.setup { + db.Set([]byte(kv.key), []byte(kv.value)) + } + + iterator := newMemIterator([]byte(tt.start), []byte(tt.end), db, true) + + if iterator.Valid() { + t.Error("Iterator should be invalid for empty range") + } + + // Verify Next() doesn't change validity + iterator.Next() + if iterator.Valid() { + t.Error("Iterator should remain invalid after Next()") + } + }) + } }
1-73
: Consider adding tests for edge cases and error scenariosWhile the current tests cover the basic functionality, consider adding tests for:
- Invalid ranges where end < start
- Zero-length keys
- Nil keys
- Large datasets to verify performance characteristics
- Boundary conditions at the edges of the keyspace
Would you like me to provide example test cases for these scenarios?
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
📒 Files selected for processing (1)
store/cachekv/internal/memiterator_test.go
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
store/cachekv/internal/memiterator_test.go (2)
Pattern **/*.go
: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.
Pattern **/*_test.go
: "Assess the unit test code assessing sufficient code coverage for the changes associated in the pull request"
func TestMemIterator_Descending(t *testing.T) { | ||
db := NewBTree() | ||
|
||
db.Set([]byte("a"), []byte("value_a")) | ||
db.Set([]byte("b"), []byte("value_b")) | ||
db.Set([]byte("c"), []byte("value_c")) | ||
db.Set([]byte("d"), []byte("value_d")) | ||
|
||
iterator := newMemIterator([]byte("a"), []byte("d"), db, false) | ||
|
||
var result []string | ||
for iterator.Valid() { | ||
result = append(result, string(iterator.Key())) | ||
iterator.Next() | ||
} | ||
|
||
expected := []string{"c", "b", "a"} | ||
for i, key := range result { | ||
if key != expected[i] { | ||
t.Errorf("Expected %s, got %s", expected[i], key) | ||
} | ||
} | ||
|
||
if iterator.Valid() { | ||
t.Errorf("Iterator should be invalid after last item") | ||
} | ||
} |
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.
Fix inconsistency in test data and expectations
There's an inconsistency in the test:
- The key "d" is set but not included in the expected results
- The iterator range includes "d" (in
newMemIterator([]byte("a"), []byte("d"), db, false)
) but "d" is not checked
Additionally, this test would benefit from the same improvements suggested for the ascending test.
Either adjust the expected results to include "d":
- expected := []string{"c", "b", "a"}
+ expected := []string{"d", "c", "b", "a"}
Or adjust the iterator range to exclude "d":
- iterator := newMemIterator([]byte("a"), []byte("d"), db, false)
+ iterator := newMemIterator([]byte("a"), []byte("c"), db, false)
Committable suggestion skipped: line range outside the PR's diff.
Description
Closes: #XXXX
Author Checklist
All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.
I have...
!
in the type prefix if API or client breaking changeCHANGELOG.md
Reviewers Checklist
All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.
Please see Pull Request Reviewer section in the contributing guide for more information on how to review a pull request.
I have...
Summary by CodeRabbit
memIterator
functionality, validating behavior for ascending and descending orders, as well as handling of empty ranges.