From d87852d0611208b5059ccaf28cb0b827c57349bb Mon Sep 17 00:00:00 2001 From: Jaehyun Yeom Date: Mon, 2 Jun 2025 10:19:33 +0900 Subject: [PATCH] feat(tests): add new example test cases - Added comprehensive tests for `CursorToOffset`, `RecordIOReader`, `PrefixSum64Hash`, and `NewOSFileWriterFactory` in `sstable` package. - Enhanced `shard_test.go` with better error handling and additional test cases. - Introduced sorting tests in `interface_test.go` for `Entries` using custom comparison logic. - Improved test example clarity and expanded coverage for `Entry` functionality, including `WriteTo` and `Size` methods. --- go/shard/shard_test.go | 55 ++++++++++++++++++++++++++-- go/sort/interface_test.go | 29 +++++++++++++++ go/sstable/cursor_test.go | 71 +++++++++++++++++++++++++++++++++++++ go/sstable/entry_test.go | 40 +++++++++++++++++++++ go/sstable/header_test.go | 4 +-- go/sstable/index_test.go | 14 ++++---- go/sstable/recordio_test.go | 57 +++++++++++++++++++++++++++++ 7 files changed, 259 insertions(+), 11 deletions(-) create mode 100644 go/sstable/cursor_test.go create mode 100644 go/sstable/recordio_test.go diff --git a/go/shard/shard_test.go b/go/shard/shard_test.go index c2da958..af1893c 100644 --- a/go/shard/shard_test.go +++ b/go/shard/shard_test.go @@ -3,6 +3,7 @@ package shard import ( "crypto/sha512" "fmt" + "io" "io/ioutil" "os" "path" @@ -14,7 +15,7 @@ func ExampleWriter() { fmt.Println(err) return } - defer os.RemoveAll(name) //nolint:wsl + defer func() { _ = os.RemoveAll(name) }() //nolint:wsl w := NewWriter(5, &PrefixSum64Hash{sha512.New()}, NewOSFileWriterFactory(path.Join(name, "test-"))) records := []string{ @@ -35,7 +36,7 @@ func ExampleWriter() { } } - w.Close() + _ = w.Close() // Changed this line for i := 0; i < 5; i++ { filename := fmt.Sprintf("test-%05d-of-00005", i) @@ -49,3 +50,53 @@ func ExampleWriter() { // test-00003-of-00005: // test-00004-of-00005:test1test2test3test1 } + +func ExamplePrefixSum64Hash_Sum64() { + hasher := &PrefixSum64Hash{sha512.New()} + hasher.Write([]byte("hello")) + fmt.Println(hasher.Sum64()) + // Output: 11200964803485168504 +} + +func ExampleNewOSFileWriterFactory() { + tempDir, err := ioutil.TempDir("", "examplefactory") + if err != nil { + fmt.Println("Failed to create temp dir:", err) + return + } + defer func() { _ = os.RemoveAll(tempDir) }() + + factoryPrefix := "testfactory-" + factory := NewOSFileWriterFactory(path.Join(tempDir, factoryPrefix)) + + numFiles := 3 + for i := 0; i < numFiles; i++ { + writer := factory(i, numFiles) + _, err := fmt.Fprintf(writer, "data for file %d", i) + if err != nil { + fmt.Printf("Error writing to file %d: %v\n", i, err) + } + if closer, ok := writer.(io.Closer); ok { + err := closer.Close() + if err != nil { + fmt.Printf("Error closing file %d: %v\n", i, err) + } + } + } + + for i := 0; i < numFiles; i++ { + fileName := fmt.Sprintf("%s%05d-of-%05d", factoryPrefix, i, numFiles) + filePath := path.Join(tempDir, fileName) + content, err := ioutil.ReadFile(filePath) + if err != nil { + fmt.Printf("Error reading file %s: %v\n", fileName, err) + continue + } + fmt.Printf("%s:%s\n", fileName, string(content)) + } + + // Output: + // testfactory-00000-of-00003:data for file 0 + // testfactory-00001-of-00003:data for file 1 + // testfactory-00002-of-00003:data for file 2 +} diff --git a/go/sort/interface_test.go b/go/sort/interface_test.go index dc31451..f4bcbd3 100644 --- a/go/sort/interface_test.go +++ b/go/sort/interface_test.go @@ -3,6 +3,7 @@ package sort import ( "container/heap" "fmt" + "sort" "github.com/jaeyeom/sstable/go/sstable" ) @@ -41,3 +42,31 @@ func Example_heap() { // {{[107 101 121 51] [118 97 108 117 101]} 1} // {{[107 101 121 52] [118 97 108 117 101]} 3} } + +func ExampleEntries_sort() { + // Create a slice of HeapEntry. + // sstable.Entry has Key and Value as []byte. + data := Entries{ + {Entry: sstable.Entry{Key: []byte("key3"), Value: []byte("valueA")}}, + {Entry: sstable.Entry{Key: []byte("key1"), Value: []byte("valueB")}}, + {Entry: sstable.Entry{Key: []byte("key2"), Value: []byte("valueD")}}, + {Entry: sstable.Entry{Key: []byte("key1"), Value: []byte("valueC")}}, + } + + // Call sort.Sort on the Entries instance. + // This uses the Len, Less, Swap methods defined on Entries. + sort.Sort(data) + + // Iterate through the sorted Entries and print. + for _, heapEntry := range data { + fmt.Printf("%s %s\n", string(heapEntry.Entry.Key), string(heapEntry.Entry.Value)) + } + + // Define the expected output. + // Sorted first by key, then by value for identical keys. + // Output: + // key1 valueB + // key1 valueC + // key2 valueD + // key3 valueA +} diff --git a/go/sstable/cursor_test.go b/go/sstable/cursor_test.go new file mode 100644 index 0000000..1432e71 --- /dev/null +++ b/go/sstable/cursor_test.go @@ -0,0 +1,71 @@ +package sstable + +import ( + "bytes" + "fmt" + // "io" // Will add if ReadEntry or other functions require it directly for type matching. + // For now, bytes.Reader and the CursorToOffset itself will handle reader interfaces. +) + +func ExampleCursorToOffset() { + // 1. Prepare entries + entry1 := Entry{Key: []byte("key1"), Value: []byte("value1")} + entry2 := Entry{Key: []byte("key2"), Value: []byte("value22")} // Different length value + + // 2. Marshal entries into a buffer + var buf bytes.Buffer + data1, err := entry1.MarshalBinary() + if err != nil { + fmt.Println("Error marshalling entry1:", err) + return + } + buf.Write(data1) + + data2, err := entry2.MarshalBinary() + if err != nil { + fmt.Println("Error marshalling entry2:", err) + return + } + buf.Write(data2) + + serializedData := buf.Bytes() + + // 3. Calculate endOffset (total size of serialized data) + // This could also be entry1.Size() + entry2.Size() + endOffset := uint64(len(serializedData)) + + // 4. Create a bytes.NewReader + reader := bytes.NewReader(serializedData) + + // 5. Create CursorToOffset instance + // Reader can be io.Reader or io.ReaderAt. bytes.NewReader implements both. + // For this example, let's ensure it's treated as a general io.Reader + // by the cursor, though CursorToOffset will detect it as io.ReaderAt if not type asserted. + // The implementation of CursorToOffset.Entry() prefers io.ReaderAt if available. + cursor := &CursorToOffset{ + reader: reader, // bytes.NewReader is also an io.ReaderAt + offset: 0, + endOffset: endOffset, + entry: nil, // Starts with no entry loaded + } + + // 6. Loop through entries + for !cursor.Done() { + currentEntry := cursor.Entry() + if currentEntry == nil { + // This might happen if ReadEntry/ReadEntryAt fails, + // or if Done() condition is met but loop condition was already checked. + // Or if endOffset is 0. + // Given the Done() logic, if Entry() returns nil, Done() should usually be true. + // Let's assume valid entries for example purposes. + fmt.Println("Error: current entry is nil, but not done.") + break + } + fmt.Printf("Key: %s, Value: %s\n", string(currentEntry.Key), string(currentEntry.Value)) + cursor.Next() + } + + // Output: + // Key: key1, Value: value1 + // Key: key2, Value: value22 +} diff --git a/go/sstable/entry_test.go b/go/sstable/entry_test.go index 0cea252..8d1f904 100644 --- a/go/sstable/entry_test.go +++ b/go/sstable/entry_test.go @@ -42,6 +42,46 @@ func ExampleReadEntry() { // &{[1 2 3] [5 6 7 8]} } +func ExampleEntry_WriteTo() { + e := Entry{ + Key: []byte{1, 2}, // Key length 2 + Value: []byte{3, 4, 5}, // Value length 3 + } + + var buf bytes.Buffer + _, err := e.WriteTo(&buf) + if err != nil { + fmt.Println("Error:", err) + return + } + + // Optionally print n, but the main thing is the buffer content + // fmt.Printf("Bytes written: %d\n", n) + // For example consistency, usually only the primary output is shown. + + fmt.Println(buf.Bytes()) + // Expected format: [0 0 0 keyLen 0 0 0 valLen keyBytes valBytes] + // KeyLen = 2 -> [0 0 0 2] + // ValLen = 3 -> [0 0 0 3] + // Key = [1 2] + // Value = [3 4 5] + // Output: + // [0 0 0 2 0 0 0 3 1 2 3 4 5] +} + +func ExampleEntry_Size() { + e := Entry{ + Key: []byte("test"), // length 4 + Value: []byte("example"), // length 7 + } + + size := e.Size() + fmt.Println(size) + // Expected: 8 (for length fields) + 4 (len("test")) + 7 (len("example")) = 19 + // Output: + // 19 +} + func ExampleReadEntryAt() { f := bytes.NewReader([]byte{0, 0, 0, 3, 0, 0, 0, 4, 1, 2, 3, 5, 6, 7, 8}) e, _ := ReadEntryAt(f, 0) diff --git a/go/sstable/header_test.go b/go/sstable/header_test.go index 163595d..ccad579 100644 --- a/go/sstable/header_test.go +++ b/go/sstable/header_test.go @@ -5,7 +5,7 @@ import ( ) //nolint:govet -func ExampleHeaderMarshalBinary() { +func Example_headerMarshalBinary() { h := header{ version: 1, numBlocks: 2, @@ -18,7 +18,7 @@ func ExampleHeaderMarshalBinary() { } //nolint:govet -func ExampleHeaderUnmarshalBinary() { +func Example_headerUnmarshalBinary() { h := header{} err := h.UnmarshalBinary([]byte{ diff --git a/go/sstable/index_test.go b/go/sstable/index_test.go index 25f5463..90ed46f 100644 --- a/go/sstable/index_test.go +++ b/go/sstable/index_test.go @@ -7,7 +7,7 @@ import ( ) //nolint:govet -func ExampleIndexBufferWrite() { +func Example_indexBufferWrite() { w := &indexBuffer{ maxBlockLength: 64 * 1024, } @@ -20,7 +20,7 @@ func ExampleIndexBufferWrite() { } //nolint:govet -func ExampleIndexEntryIndexOf() { +func Example_indexEntryIndexOf() { i := &index{ {0, 60023, []byte{1, 2, 3}}, {60023, 30011, []byte{2, 3, 4}}, @@ -39,7 +39,7 @@ func ExampleIndexEntryIndexOf() { } //nolint:govet -func ExampleIndexReadFrom() { +func Example_indexReadFrom() { var i index buf := bytes.NewBuffer([]byte{ @@ -56,7 +56,7 @@ func ExampleIndexReadFrom() { } //nolint:govet -func ExampleIndexReadAt() { +func Example_indexReadAt() { var i index f := bytes.NewReader([]byte{ @@ -73,7 +73,7 @@ func ExampleIndexReadAt() { } //nolint:govet -func ExampleIndexWriteTo() { +func Example_indexWriteTo() { i := &index{ {0, 60023, []byte{1, 2, 3}}, {60023, 30011, []byte{2, 3, 4}}, @@ -92,7 +92,7 @@ func ExampleIndexWriteTo() { } //nolint:govet -func ExampleIndexEntryMarshalBinary() { +func Example_indexEntryMarshalBinary() { b := indexEntry{ blockOffset: 1, blockLength: 10, @@ -110,7 +110,7 @@ func ExampleIndexEntryMarshalBinary() { } //nolint:govet -func ExampleIndexEntryUnmarshalBinary() { +func Example_indexEntryUnmarshalBinary() { var b indexEntry err := b.UnmarshalBinary([]byte{0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 10, 5, 6, 7}) diff --git a/go/sstable/recordio_test.go b/go/sstable/recordio_test.go new file mode 100644 index 0000000..f58fe9d --- /dev/null +++ b/go/sstable/recordio_test.go @@ -0,0 +1,57 @@ +package sstable + +import ( + "bytes" + "fmt" +) + +func ExampleNewRecordIOReader() { + // 1. Prepare entries + entry1 := Entry{Key: []byte("keyA"), Value: []byte("valueA")} + entry2 := Entry{Key: []byte("keyB"), Value: []byte("valueBB")} // Different length value + + // 2. Marshal entries into a buffer + var dataBuf bytes.Buffer + data1Bytes, err := entry1.MarshalBinary() + if err != nil { + fmt.Println("Error marshalling entry1:", err) + return + } + dataBuf.Write(data1Bytes) + + data2Bytes, err := entry2.MarshalBinary() + if err != nil { + fmt.Println("Error marshalling entry2:", err) + return + } + dataBuf.Write(data2Bytes) + + serializedEntries := dataBuf.Bytes() + + // 3. Create a bytes.NewReader (which implements io.Reader and io.ReaderAt) + reader := bytes.NewReader(serializedEntries) + + // 4. Calculate the total size of the serialized data + totalSize := uint64(len(serializedEntries)) + + // 5. Call NewRecordIOReader + // NewRecordIOReader takes an io.Reader, but CursorToOffset (which it returns) + // will try to use it as io.ReaderAt if possible. bytes.NewReader supports this. + cursor := NewRecordIOReader(reader, totalSize) + + // 6. Loop through entries using the cursor + for !cursor.Done() { + currentEntry := cursor.Entry() + if currentEntry == nil { + // Should not happen in this example if data is valid and size is correct + fmt.Println("Error: current entry is nil, but not done.") + break + } + fmt.Printf("Key: %s, Value: %s\n", string(currentEntry.Key), string(currentEntry.Value)) + cursor.Next() + } + + // Output: + // Key: keyA, Value: valueA + // Key: keyB, Value: valueBB +}