Last active
November 14, 2020 20:40
-
-
Save svanellewee/878e4a9864c6f2b7b1f72c43a98b11c9 to your computer and use it in GitHub Desktop.
BoltDB/BBolt test code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main_test | |
import ( | |
"bytes" | |
"encoding/binary" | |
"fmt" | |
"os" | |
"testing" | |
"time" | |
"github.com/stretchr/testify/assert" | |
bolt "go.etcd.io/bbolt" | |
) | |
type history struct { | |
id int64 | |
historyID int64 | |
data string | |
time time.Time | |
} | |
type histories []history | |
type directoryName string | |
type HistoryStore map[directoryName]history | |
type Store struct { | |
db *bolt.DB | |
} | |
func (s *Store) Close() { | |
s.db.Close() | |
} | |
func (s *Store) Dump(bucket string) { | |
fmt.Println("Dump........", bucket) | |
s.db.View(func(tx *bolt.Tx) error { | |
// Assume bucket exists and has keys | |
b := tx.Bucket([]byte(bucket)) | |
if b != nil { | |
b.ForEach(func(k, v []byte) error { | |
snapshotTime, err := stringToTime(string(k)) | |
if err != nil { | |
return err | |
} | |
fmt.Println(">>>", k) | |
fmt.Printf("key=%s, value=%s\n", snapshotTime.Format(time.RFC3339), v) | |
return nil | |
}) | |
} | |
return nil | |
}) | |
} | |
func NewStore(dbFile string) (*Store, error) { | |
db, err := bolt.Open(dbFile, 0600, nil) | |
if err != nil { | |
return nil, err | |
} | |
return &Store{ | |
db: db, | |
}, nil | |
} | |
func (s *Store) Add(currentDirectory string, time time.Time, commandEntry []byte) error { | |
return s.db.Update(func(tx *bolt.Tx) error { | |
b, err := tx.CreateBucketIfNotExists([]byte(currentDirectory)) | |
if err != nil { | |
return err | |
} | |
ts := []byte(timeToString(time)) | |
err = b.Put(ts, commandEntry) | |
if err != nil { | |
return err | |
} | |
return nil | |
}) | |
} | |
func (s *Store) Get(bucket, key string) ([]byte, error) { | |
var result []byte | |
err := s.db.View(func(tx *bolt.Tx) error { | |
b := tx.Bucket([]byte(bucket)) | |
result = b.Get([]byte(key)) | |
return nil | |
}) | |
if err != nil { | |
return nil, err | |
} | |
return result, nil | |
} | |
func (s *Store) Range(bucket string, minTime, maxTime time.Time, handler func(t time.Time, data []byte)) { | |
s.db.View(func(tx *bolt.Tx) error { | |
mainBucket := tx.Bucket([]byte(bucket)) | |
c := mainBucket.Cursor() | |
min := []byte(timeToString(minTime)) | |
max := []byte(timeToString(maxTime)) | |
for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() { | |
tt, err := stringToTime(string(k)) | |
if err != nil { | |
return err | |
} | |
handler(tt, v) | |
} | |
return nil | |
}) | |
} | |
func (s *Store) Today(bucket string, prefixTime time.Time, handler func(string, []byte)) { | |
s.db.View(func(tx *bolt.Tx) error { | |
mainBucket := tx.Bucket([]byte(bucket)) | |
c := mainBucket.Cursor() | |
year, month, day := prefixTime.Date() | |
bod := time.Date(year, month, day, 0, 0, 0, 0, &time.Location{}) | |
prefix := timeToString(bod)[:10] | |
//fmt.Println("PREFIX::", prefix) | |
for k, v := c.Seek([]byte(prefix)); k != nil && bytes.HasPrefix(k, []byte(prefix)); k, v = c.Next() { | |
handler(prefix, v) | |
} | |
return nil | |
}) | |
} | |
func (s *Store) Last(directory string, numEntries int) ([]history, error) { | |
historyList := make([]history, 0, numEntries) | |
err := s.db.View(func(tx *bolt.Tx) error { | |
b := tx.Bucket([]byte(directory)) | |
c := b.Cursor() | |
i := numEntries | |
for k, v := c.Last(); k != nil; k, v = c.Prev() { | |
if i <= 0 { | |
break | |
} | |
timeValue, err := time.Parse(time.RFC3339, string(k)) | |
if err != nil { | |
return err | |
} | |
historyValue := history{ | |
data: string(v), | |
time: timeValue, | |
} | |
historyList = append(historyList, historyValue) | |
i -= 1 | |
} | |
return nil | |
}) | |
if err != nil { | |
return nil, err | |
} | |
return historyList, nil | |
} | |
func timeToBytes(t time.Time) []byte { | |
nanoTime := uint64(t.UnixNano()) | |
result := make([]byte, 8) | |
binary.BigEndian.PutUint64(result, nanoTime) | |
return result | |
} | |
func bytesToTime(timeBytes []byte) time.Time { | |
return time.Unix(0, int64(binary.BigEndian.Uint64(timeBytes))) | |
} | |
func TestTimeConvert(t *testing.T) { | |
timestamp1 := time.Date(2020, 1, 1, 0, 0, 0, 0, &time.Location{}) | |
d := timeToBytes(timestamp1) | |
revertD := bytesToTime(d) | |
fmt.Println(revertD.Format(time.RFC3339)) | |
} | |
func timeToString(t time.Time) string { | |
return t.Format(time.RFC3339) | |
} | |
func stringToTime(s string) (time.Time, error) { | |
return time.Parse(time.RFC3339, s) | |
} | |
func TestTimeConvert2(t *testing.T) { | |
timestamp1 := time.Date(2020, 1, 1, 0, 2, 0, 0, &time.Location{}) | |
s := timeToString(timestamp1) | |
tme, err := stringToTime(s) | |
assert.Nil(t, err) | |
fmt.Printf("%#v %s", tme, tme) | |
} | |
func TestAnother(t *testing.T) { | |
dbFile := "my.db" | |
os.Remove(dbFile) | |
store, err := NewStore(dbFile) | |
assert.Nil(t, err) | |
timestamp1 := time.Date(2020, 1, 1, 0, 0, 0, 0, &time.Location{}) | |
fmt.Println(timestamp1.Format(time.RFC3339), "......!") | |
err = store.Add( | |
"/tmp", | |
timestamp1, | |
[]byte("ls ."), | |
) | |
assert.Nil(t, err) | |
timestamp1a := time.Date(2020, 1, 1, 23, 55, 55, 55, &time.Location{}) | |
fmt.Println(timestamp1.Format(time.RFC3339), "......!") | |
err = store.Add( | |
"/tmp", | |
timestamp1a, | |
[]byte("source ./bla"), | |
) | |
assert.Nil(t, err) | |
timestamp2 := time.Date(2020, 1, 2, 1, 0, 2, 0, &time.Location{}) | |
err = store.Add( | |
"/tmp", | |
timestamp2, | |
[]byte("echo bla"), | |
) | |
assert.Nil(t, err) | |
timestamp3 := time.Date(2020, 1, 1, 1, 1, 2, 0, &time.Location{}) | |
err = store.Add( | |
"/home/user", | |
timestamp3, | |
[]byte("echo bla"), | |
) | |
assert.Nil(t, err) | |
timestamp4 := time.Date(2020, 1, 2, 1, 2, 2, 0, &time.Location{}) | |
err = store.Add( | |
"/home/user2", | |
timestamp4, | |
[]byte(`cat<<"EOF" > test | |
bla yadda | |
EOF | |
`), | |
) | |
assert.Nil(t, err) | |
store.Dump("/tmp") | |
store.Dump("/home/user2") | |
minTime := time.Date(2020, 1, 1, 0, 0, 0, 0, &time.Location{}) | |
maxTime := time.Date(2020, 1, 3, 0, 0, 2, 0, &time.Location{}) | |
fmt.Println("\nRange test..") | |
rangeCount := 0 | |
store.Range("/tmp", minTime, maxTime, func(t time.Time, value []byte) { | |
rangeCount += 1 | |
fmt.Printf("RANGE [%s]: %s\n", t.Format(time.RFC3339), value) | |
}) | |
assert.Equal(t, 3, rangeCount) | |
fmt.Println("\nPrefix test") | |
prefixCount := 0 | |
store.Today("/tmp", minTime, func(k string, v []byte) { | |
prefixCount += 1 | |
fmt.Printf("PREFIX key=%s, value=%s\n", k, v) | |
}) | |
assert.Equal(t, 2, prefixCount) | |
fmt.Println("Last...") | |
lastEntries, err := store.Last("/tmp", 1) | |
assert.Nil(t, err) | |
assert.Len(t, lastEntries, 1) | |
fmt.Println("Last...2") | |
lastEntries, err = store.Last("/tmp", 2) | |
assert.Nil(t, err) | |
assert.Len(t, lastEntries, 2) | |
fmt.Println("Last...4") | |
lastEntries, err = store.Last("/tmp", 4) | |
assert.Nil(t, err) | |
assert.Len(t, lastEntries, 3) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main_test | |
import ( | |
"bytes" | |
"fmt" | |
"log" | |
"os" | |
"testing" | |
"time" | |
bolt "go.etcd.io/bbolt" | |
) | |
type history struct { | |
id int64 | |
historyID int64 | |
data string | |
time time.Time | |
} | |
type histories []history | |
func init() { | |
fmt.Println("bla") | |
} | |
func TestSomething(t *testing.T) { | |
dbFile := "my.db" | |
os.Remove(dbFile) | |
db, err := bolt.Open(dbFile, 0600, nil) | |
if err != nil { | |
log.Fatal(err) | |
} | |
defer db.Close() | |
//startTime := time.Now() | |
startTime1 := time.Date(2020, 1, 1, 1, 0, 0, 0, &time.Location{}) | |
startTime2 := time.Date(2020, 1, 2, 1, 0, 0, 0, &time.Location{}) | |
fmt.Println(startTime1.Format(time.RFC3339)) | |
testCases := []struct { | |
time time.Time | |
workingDirectory string | |
command string | |
}{ | |
{ | |
time: startTime1.Add(10 * time.Second), | |
workingDirectory: "/tmp", | |
command: "ls", | |
}, | |
{ | |
time: startTime1.Add(100 * time.Second), | |
workingDirectory: "/home/user", | |
command: "echo hello world", | |
}, | |
{ | |
time: startTime1.Add(20 * time.Minute), | |
workingDirectory: "/tmp", | |
command: `cat <<"EOF" > some_file.txt | |
something | |
something else, | |
done | |
EOF | |
`, | |
}, | |
{ | |
time: startTime2.Add(100 * time.Minute), | |
workingDirectory: "/home/user", | |
command: "echo hello world2", | |
}, | |
{ | |
time: startTime2.Add(20 * time.Minute), | |
workingDirectory: "/home/user", | |
command: `cat <<"EOF" > some_file2.txt | |
something 22 | |
something else, | |
done | |
EOF | |
`, | |
}, | |
} | |
db.Update(func(tx *bolt.Tx) error { | |
mainBucket, err := tx.CreateBucket([]byte("main bucket")) | |
if err != nil { | |
return fmt.Errorf("create bucket %s", err) | |
} | |
for _, testCase := range testCases { | |
err = mainBucket.Put([]byte(testCase.time.Format(time.RFC3339)), []byte(testCase.command)) | |
if err != nil { | |
return err | |
} | |
fmt.Println("...", testCase.time.Format(time.RFC3339), testCase.command) | |
} | |
return nil | |
}) | |
db.View(func(tx *bolt.Tx) error { | |
mainBucket := tx.Bucket([]byte("main bucket")) | |
c := mainBucket.Cursor() | |
min := []byte("2020-01-02T00:00:00Z") | |
max := []byte("2020-01-03T00:00:00Z") | |
// Iterate over the 90's. | |
for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() { | |
fmt.Printf("[%s]: %s\n", k, v) | |
} | |
return nil | |
}) | |
fmt.Println("Prefix?") | |
db.View(func(tx *bolt.Tx) error { | |
mainBucket := tx.Bucket([]byte("main bucket")) | |
c := mainBucket.Cursor() | |
prefix := []byte("2020-01-02") | |
for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() { | |
fmt.Printf("key=%s, value=%s\n", k, v) | |
} | |
return nil | |
}) | |
// db.Update(func(tx *bolt.Tx) error { | |
// b, err := tx.CreateBucket([]byte("MyBucket")) | |
// if err != nil { | |
// return fmt.Errorf("create bucket: %s", err) | |
// } | |
// subB, err := b.CreateBucket([]byte("sub-bucket")) | |
// if err != nil { | |
// return fmt.Errorf("create bucket: %s", err) | |
// } | |
// err = subB.Put([]byte("yaya"), []byte("ding dong")) | |
// return err | |
// }) | |
// db.Update(func(tx *bolt.Tx) error { | |
// b := tx.Bucket([]byte("MyBucket")) | |
// err := b.Put([]byte("answer"), []byte("42")) | |
// return err | |
// }) | |
// db.View(func(tx *bolt.Tx) error { | |
// b := tx.Bucket([]byte("MyBucket")) | |
// v := b.Get([]byte("answer")) | |
// fmt.Printf("The answer is: %s\n", v) | |
// subB := b.Bucket([]byte("sub-bucket")) | |
// fmt.Println(string(subB.Get([]byte("yaya")))) | |
// return nil | |
// }) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment