// Copyright 2017 Pilosa Corp. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package rbf_test import ( "fmt" "math" "math/rand" "testing" "time" "github.com/pilosa/pilosa/v2/rbf" txkey "github.com/pilosa/pilosa/v2/short_txkey" ) func TestTx_CommitRollback(t *testing.T) { t.Run("NoReopen", func(t *testing.T) { db := MustOpenDB(t) defer MustCloseDB(t, db) // Create bitmap in transaction but rollback. tx, err := db.Begin(true) if err != nil { t.Fatal(err) } else if err := tx.CreateBitmap("x"); err != nil { t.Fatal(err) } tx.Rollback() // Create bitmap in transaction again but commit. if tx, err := db.Begin(true); err != nil { t.Fatal(err) } else if err := tx.CreateBitmap("x"); err != nil { t.Fatal(err) } else if err := tx.Commit(); err != nil { t.Fatal(err) } // Create bitmap again but it should fail as it already exists. if tx, err := db.Begin(true); err != nil { t.Fatal(err) } else if err := tx.CreateBitmap("x"); err == nil || err != rbf.ErrBitmapExists { tx.Rollback() t.Fatal(err) } else if err := tx.Commit(); err != nil { t.Fatal(err) } }) t.Run("Reopen", func(t *testing.T) { db := MustOpenDB(t) defer func() { MustCloseDB(t, db) }() // Create bitmap in transaction but rollback. tx, err := db.Begin(true) if err != nil { t.Fatal(err) } else if err := tx.CreateBitmap("x"); err != nil { t.Fatal(err) } tx.Rollback() db = MustReopenDB(t, db) // Create bitmap in transaction again but commit. if tx, err := db.Begin(true); err != nil { t.Fatal(err) } else if err := tx.CreateBitmap("x"); err != nil { t.Fatal(err) } else if err := tx.Commit(); err != nil { t.Fatal(err) } db = MustReopenDB(t, db) // Create bitmap again but it should fail as it already exists. if tx, err := db.Begin(true); err != nil { t.Fatal(err) } else if err := tx.CreateBitmap("x"); err == nil || err != rbf.ErrBitmapExists { tx.Rollback() t.Fatal(err) } else if err := tx.Commit(); err != nil { t.Fatal(err) } }) t.Run("SingleWriter", func(t *testing.T) { //t.Skip("NEED TO FIX IN RACE") //TODO (twg) db := MustOpenDB(t) defer MustCloseDB(t, db) // Start write transaction. ch0 := make(chan struct{}) tx0 := MustBegin(t, db, true) go func() { <-ch0 tx0.Rollback() }() // Start separate write transaction in different goroutine. ch1 := make(chan struct{}) go func() { tx1 := MustBegin(t, db, true) close(ch1) _ = tx1.Commit() }() // Ensure second tx doesn't start. select { case <-ch1: t.Fatal("second tx started while first tx active") case <-time.After(10 * time.Millisecond): } // Finish first transaction. close(ch0) select { case <-ch1: case <-time.After(10 * time.Millisecond): t.Fatal("second tx should have started after first tx closed") } }) } func TestTx_Add(t *testing.T) { db := MustOpenDB(t) defer MustCloseDB(t, db) tx := MustBegin(t, db, true) defer tx.Rollback() if err := tx.CreateBitmap("x"); err != nil { t.Fatal(err) } if _, err := tx.Add("x", 1); err != nil { t.Fatal(err) } else if _, err := tx.Add("x", 10); err != nil { t.Fatal(err) } else if _, err := tx.Add("x", 3); err != nil { t.Fatal(err) } for _, v := range []uint64{1, 3, 10} { if ok, err := tx.Contains("x", v); err != nil { t.Fatal(err) } else if !ok { t.Fatalf("Tx.Contains(%d): expected true", v) } } if ok, err := tx.Contains("x", 2); err != nil { t.Fatal(err) } else if ok { t.Fatal("Tx.Contains(): expected false") } } func TestTx_DeleteBitmap(t *testing.T) { db := MustOpenDB(t) defer MustCloseDB(t, db) tx := MustBegin(t, db, true) defer tx.Rollback() // Create bitmap & add value. if err := tx.CreateBitmap("x"); err != nil { t.Fatal(err) } else if _, err := tx.Add("x", 1); err != nil { t.Fatal(err) } // Recreate bitmap & ensure value does not exist. if err := tx.DeleteBitmap("x"); err != nil { t.Fatal(err) } else if err := tx.CreateBitmap("x"); err != nil { t.Fatal(err) } else if ok, err := tx.Contains("x", 1); err != nil { t.Fatal(err) } else if ok { t.Fatal("expected no value in recreated bitmap") } } func TestTx_RenameBitmap(t *testing.T) { db := MustOpenDB(t) defer MustCloseDB(t, db) tx := MustBegin(t, db, true) defer tx.Rollback() // Create bitmap & add value. if err := tx.CreateBitmap("x"); err != nil { t.Fatal(err) } else if _, err := tx.Add("x", 1); err != nil { t.Fatal(err) } // Rename bitmap & ensure value still exists. if err := tx.RenameBitmap("x", "y"); err != nil { t.Fatal(err) } else if ok, err := tx.Contains("y", 1); err != nil { t.Fatal(err) } else if !ok { t.Fatal("expected value in renamed bitmap") } } func TestTx_Add_Quick(t *testing.T) { if testing.Short() { t.Skip("-short enabled, skipping") } else if is32Bit() { t.Skip("32-bit build, skipping quick check tests") } QuickCheck(t, func(t *testing.T, rand *rand.Rand) { t.Parallel() db := MustOpenDB(t) defer MustCloseDB(t, db) tx := MustBegin(t, db, true) defer tx.Rollback() values := GenerateValues(rand, 10000) if err := tx.CreateBitmap("x"); err != nil { t.Fatal(err) } // Insert values in random order. for _, i := range rand.Perm(len(values)) { v := values[i] if _, err := tx.Add("x", v); err != nil { t.Fatalf("Add(%d) i=%d err=%q", v, i, err) } } // Verify all bits are written. for i, v := range values { if ok, err := tx.Contains("x", v); !ok || err != nil { t.Fatalf("Contains(%d)=(%v,%v) i=%d hi=%d lo=%d", v, ok, err, i, highbits(v), lowbits(v)) } } }) } func TestTx_AddRemove_Quick(t *testing.T) { if testing.Short() { t.Skip("-short enabled, skipping") } else if is32Bit() { t.Skip("32-bit build, skipping quick check tests") } QuickCheck(t, func(t *testing.T, rand *rand.Rand) { t.Parallel() db := MustOpenDB(t) defer MustCloseDB(t, db) tx := MustBegin(t, db, true) defer tx.Rollback() values := GenerateValues(rand, 10000) if err := tx.CreateBitmap("x"); err != nil { t.Fatal(err) } // Insert values in random order. for _, i := range rand.Perm(len(values)) { if _, err := tx.Add("x", values[i]); err != nil { t.Fatalf("Add(%d) i=%d err=%q", values[i], i, err) } } // Remove half the values in random order. for _, i := range rand.Perm(len(values)) { if _, err := tx.Remove("x", values[i]); err != nil { t.Fatalf("Remove(%d) i=%d err=%q", values[i], i, err) } } // Verify all bits are removed. for i, v := range values { if ok, err := tx.Contains("x", v); ok || err != nil { t.Fatalf("Contains(%d)=(%v,%v) i=%d hi=%d lo=%d", v, ok, err, i, highbits(v), lowbits(v)) } } // Re-add those values back in. for _, i := range rand.Perm(len(values)) { if _, err := tx.Add("x", values[i]); err != nil { t.Fatalf("Re-Add(%d) i=%d err=%q", values[i], i, err) } } // Verify all bits are written. for i, v := range values { if ok, err := tx.Contains("x", v); !ok || err != nil { t.Fatalf("Contains(%d)=(%v,%v) i=%d hi=%d lo=%d", v, ok, err, i, highbits(v), lowbits(v)) } } }) } func TestTx_Multiple_CreateBitmap(t *testing.T) { rand := rand.New(rand.NewSource(0)) db := MustOpenDB(t) defer MustCloseDB(t, db) tx := MustBegin(t, db, true) defer tx.Rollback() values := GenerateValues(rand, 2) if err := tx.CreateBitmap("x/1"); err != nil { t.Fatal(err) } // Insert values in random order. for _, i := range rand.Perm(len(values)) { if _, err := tx.Add("x/1", values[i]); err != nil { t.Fatalf("Add(%d) i=%d err=%q", values[i], i, err) } } if err := tx.Commit(); err != nil { t.Fatalf("Commit 1 err=%q", err) } tx1 := MustBegin(t, db, true) defer tx1.Rollback() if err := tx1.CreateBitmap("x/2"); err != nil { t.Fatal(err) } // Insert values in random order. for _, i := range rand.Perm(len(values)) { if _, err := tx1.Add("x/2", values[i]); err != nil { t.Fatalf("Add(%d) i=%d err=%q", values[i], i, err) } } if err := tx1.Commit(); err != nil { t.Fatalf("Commit 2 err=%q", err) } } func TestTx_CursorCrashArray(t *testing.T) { db := MustOpenDB(t) defer MustCloseDB(t, db) tx := MustBegin(t, db, true) defer tx.Rollback() if err := tx.CreateBitmap("x"); err != nil { t.Fatal(err) } c, err := tx.Cursor("x") if err != nil { t.Fatal(err) } //setArray(t, 0, 2379, &c) //setArray(t, 1, 2337, &c) setArray(t, 32, 1216, c) setArray(t, 33, 1195, c) setArray(t, 48, 1186, c) setArray(t, 49, 1223, c) setArray(t, 50, 1223, c) } func TestTx_CursorCrashBitmap(t *testing.T) { if testing.Short() { t.Skip("-short enabled, skipping") } db := MustOpenDB(t) defer MustCloseDB(t, db) tx := MustBegin(t, db, true) defer tx.Rollback() if err := tx.CreateBitmap("x"); err != nil { t.Fatal(err) } c, err := tx.Cursor("x") if err != nil { t.Fatal(err) } setArray(t, 0, 22510, c) setArray(t, 1, 23584, c) } func setArray(tb testing.TB, key, num int, c *rbf.Cursor) { for i := uint64(0); i < uint64(num); i++ { v := i | (uint64(key) << 16) if _, err := c.Add(v); err != nil { tb.Fatal(err) } } } func BenchmarkTx_Add(b *testing.B) { for _, n := range []int{1, 10, 1000} { b.Run(fmt.Sprint(n), func(b *testing.B) { rand := rand.New(rand.NewSource(0)) values := make([]uint64, n) for i := range values { values[i] = uint64(rand.Intn(rbf.ShardWidth)) } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { func() { db := MustOpenDB(b) defer MustCloseDB(b, db) for _, v := range values { func() { tx := MustBegin(b, db, true) defer tx.Rollback() if _, err := tx.Add("x", v); err != nil { b.Fatal(err) } else if err := tx.Commit(); err != nil { b.Fatal(err) } }() } }() } }) } } func BenchmarkTx_Contains(b *testing.B) { for _, n := range []int{10000, 100000, 1000000} { b.Run(fmt.Sprint(n), func(b *testing.B) { rand := rand.New(rand.NewSource(0)) values := make([]uint64, n) for i := range values { values[i] = uint64(rand.Intn(rbf.ShardWidth)) } db := MustOpenDB(b) defer MustCloseDB(b, db) tx := MustBegin(b, db, true) defer tx.Rollback() b.ResetTimer() t := time.Now() b.ReportAllocs() for i := 0; i < b.N; i++ { for _, v := range values { if _, err := tx.Contains("x", v); err != nil { b.Fatalf("Contains(%d) i=%d err=%q", v, i, err) } } } b.ReportMetric(float64(time.Since(t).Nanoseconds())/float64(n*b.N), "ns/op") }) } } func TestTx_Dump(t *testing.T) { db := MustOpenDB(t) defer MustCloseDB(t, db) tx := MustBegin(t, db, true) defer tx.Rollback() index, field, view, shard := "i", "f", "v", uint64(15) nm := rbfName(index, field, view, shard) if err := tx.CreateBitmap(nm); err != nil { t.Fatal(err) } else if _, err := tx.Add(nm, 0x00000001, 0x00000002, 0x00010003, 0x00030004); err != nil { t.Fatal(err) } // test that we don't crash, and get *something* back s := tx.DumpString(true, math.MaxUint64) if s == "" { panic("should have had 3 containers!") } } func TestTx_CreateBitmap(t *testing.T) { t.Run("Bulk", func(t *testing.T) { db := MustOpenDB(t) defer MustCloseDB(t, db) tx, err := db.Begin(true) if err != nil { t.Fatal(err) } defer tx.Rollback() if err := tx.CreateBitmap(fmt.Sprintf("%4000x", 0)); err != nil { t.Fatal(err) } else if err := tx.CreateBitmap(fmt.Sprintf("%4000x", 1)); err != nil { t.Fatal(err) } else if err := tx.CreateBitmap(fmt.Sprintf("%4000x", 2)); err != nil { t.Fatal(err) } if err := tx.Commit(); err != nil { t.Fatal(err) } }) } func rbfName(index, field, view string, shard uint64) string { return string(txkey.Prefix(index, field, view, shard)) }