diff --git a/database/controller.go b/database/controller.go index d2f279c..438072f 100644 --- a/database/controller.go +++ b/database/controller.go @@ -54,8 +54,8 @@ func (c *Controller) Get(key string) (record.Record, error) { // process hooks for _, hook := range c.hooks { - if hook.q.MatchesKey(key) { - err := hook.hook.PreGet(key) + if hook.h.UsesPreGet() && hook.q.MatchesKey(key) { + err := hook.h.PreGet(key) if err != nil { return nil, err } @@ -76,8 +76,8 @@ func (c *Controller) Get(key string) (record.Record, error) { // process hooks for _, hook := range c.hooks { - if hook.q.Matches(r) { - r, err = hook.hook.PostGet(r) + if hook.h.UsesPostGet() && hook.q.Matches(r) { + r, err = hook.h.PostGet(r) if err != nil { return nil, err } @@ -106,13 +106,13 @@ func (c *Controller) Put(r record.Record) (err error) { // process hooks for _, hook := range c.hooks { - if hook.q.Matches(r) { - r, err = hook.hook.PrePut(r) - if err != nil { - return err + if hook.h.UsesPrePut() && hook.q.Matches(r) { + r, err = hook.h.PrePut(r) + if err != nil { + return err + } } } - } if r.Meta() == nil { r.SetMeta(&record.Meta{}) @@ -127,10 +127,13 @@ func (c *Controller) Put(r record.Record) (err error) { return err } - // process hooks - for _, hook := range c.hooks { - if hook.q.Matches(r) { - hook.hook.PostPut(r) + // process subscriptions + for _, sub := range c.subscriptions { + if sub.q.Matches(r) { + select { + case sub.Feed <- r: + default: + } } } diff --git a/database/database_test.go b/database/database_test.go index d38f575..4cc94c3 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -30,6 +30,19 @@ func testDatabase(t *testing.T, storageType string) { t.Fatal(err) } + // hook + hook, err := RegisterHook(q.New(dbName).MustBeValid(), &HookBase{}) + if err != nil { + t.Fatal(err) + } + + // sub + sub, err := Subscribe(q.New(dbName).MustBeValid()) + if err != nil { + t.Fatal(err) + } + + // interface db := NewInterface(nil) A := NewExample(makeKey(dbName, "A"), "Herbert", 411) @@ -92,6 +105,15 @@ func testDatabase(t *testing.T, storageType string) { t.Fatal("expected two records") } + err = hook.Cancel() + if err != nil { + t.Fatal(err) + } + err = sub.Cancel() + if err != nil { + t.Fatal(err) + } + } func TestDatabaseSystem(t *testing.T) { @@ -99,7 +121,7 @@ func TestDatabaseSystem(t *testing.T) { // panic after 10 seconds, to check for locks go func() { time.Sleep(10 * time.Second) - fmt.Println("===== TAKING TOO LONG FOR SHUTDOWN - PRINTING STACK TRACES =====") + fmt.Println("===== TAKING TOO LONG - PRINTING STACK TRACES =====") pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) os.Exit(1) }() diff --git a/database/hook.go b/database/hook.go index a26aa1d..1edac94 100644 --- a/database/hook.go +++ b/database/hook.go @@ -15,39 +15,37 @@ type Hook interface { UsesPrePut() bool PrePut(r record.Record) (record.Record, error) - - UsesPostPut() bool - PostPut(r record.Record) } // RegisteredHook is a registered database hook. type RegisteredHook struct { - q *query.Query - hook Hook + q *query.Query + h Hook } // RegisterHook registeres a hook for records matching the given query in the database. -func RegisterHook(q *query.Query, hook Hook) error { +func RegisterHook(q *query.Query, hook Hook) (*RegisteredHook, error) { _, err := q.Check() if err != nil { - return err + return nil, err } c, err := getController(q.DatabaseName()) if err != nil { - return err + return nil, err } c.readLock.Lock() - defer c.readLock.Lock() + defer c.readLock.Unlock() c.writeLock.Lock() defer c.writeLock.Unlock() - c.hooks = append(c.hooks, &RegisteredHook{ - q: q, - hook: hook, - }) - return nil + rh := &RegisteredHook{ + q: q, + h: hook, + } + c.hooks = append(c.hooks, rh) + return rh, nil } // Cancel unhooks the hook. @@ -58,7 +56,7 @@ func (h *RegisteredHook) Cancel() error { } c.readLock.Lock() - defer c.readLock.Lock() + defer c.readLock.Unlock() c.writeLock.Lock() defer c.writeLock.Unlock() diff --git a/database/hookbase.go b/database/hookbase.go index 15fc8fe..fd42748 100644 --- a/database/hookbase.go +++ b/database/hookbase.go @@ -4,10 +4,25 @@ import ( "github.com/Safing/portbase/database/record" ) -// HookBase implements the Hook interface. +// HookBase implements the Hook interface and provides dummy functions to reduce boilerplate. type HookBase struct { } +// UsesPreGet implements the Hook interface and returns false. +func (b *HookBase) UsesPreGet() bool { + return false +} + +// UsesPostGet implements the Hook interface and returns false. +func (b *HookBase) UsesPostGet() bool { + return false +} + +// UsesPrePut implements the Hook interface and returns false. +func (b *HookBase) UsesPrePut() bool { + return false +} + // PreGet implements the Hook interface. func (b *HookBase) PreGet(dbKey string) error { return nil @@ -22,8 +37,3 @@ func (b *HookBase) PostGet(r record.Record) (record.Record, error) { func (b *HookBase) PrePut(r record.Record) (record.Record, error) { return r, nil } - -// PostPut implements the Hook interface. -func (b *HookBase) PostPut(r record.Record) { - return -} diff --git a/database/query/condition-no.go b/database/query/condition-no.go deleted file mode 100644 index 8709474..0000000 --- a/database/query/condition-no.go +++ /dev/null @@ -1,20 +0,0 @@ -package query - -import ( - "github.com/Safing/portbase/database/accessor" -) - -type noCond struct { -} - -func (c *noCond) complies(acc accessor.Accessor) bool { - return true -} - -func (c *noCond) check() (err error) { - return nil -} - -func (c *noCond) string() string { - return "" -} diff --git a/database/query/query.go b/database/query/query.go index 6741cc7..d9ecb7f 100644 --- a/database/query/query.go +++ b/database/query/query.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/Safing/portbase/database/accessor" "github.com/Safing/portbase/database/record" ) @@ -101,14 +102,10 @@ func (q *Query) MatchesKey(dbKey string) bool { return true } -// Matches checks whether the query matches the supplied data object. -func (q *Query) Matches(r record.Record) bool { - if !strings.HasPrefix(r.DatabaseKey(), q.dbKeyPrefix) { - return false - } - +// MatchesRecord checks whether the query matches the supplied database record (value only). +func (q *Query) MatchesRecord(r record.Record) bool { if q.where == nil { - return false + return true } acc := r.GetAccessor(r) @@ -118,14 +115,33 @@ func (q *Query) Matches(r record.Record) bool { return q.where.complies(acc) } +// MatchesAccessor checks whether the query matches the supplied accessor (value only). +func (q *Query) MatchesAccessor(acc accessor.Accessor) bool { + if q.where == nil { + return true + } + return q.where.complies(acc) +} + +// Matches checks whether the query matches the supplied database record. +func (q *Query) Matches(r record.Record) bool { + if q.MatchesKey(r.DatabaseKey()) { + return true + } + return q.MatchesRecord(r) +} + // Print returns the string representation of the query. func (q *Query) Print() string { - where := q.where.string() - if where != "" { - if strings.HasPrefix(where, "(") { - where = where[1 : len(where)-1] + var where string + if q.where != nil { + where = q.where.string() + if where != "" { + if strings.HasPrefix(where, "(") { + where = where[1 : len(where)-1] + } + where = fmt.Sprintf(" where %s", where) } - where = fmt.Sprintf(" where %s", where) } var orderBy string diff --git a/database/query/query_test.go b/database/query/query_test.go index 6ac49f8..4645a61 100644 --- a/database/query/query_test.go +++ b/database/query/query_test.go @@ -3,7 +3,7 @@ package query import ( "testing" - "github.com/Safing/portbase/database/accessor" + "github.com/Safing/portbase/database/record" ) var ( @@ -46,12 +46,12 @@ var ( }` ) -func testQuery(t *testing.T, acc accessor.Accessor, shouldMatch bool, condition Condition) { +func testQuery(t *testing.T, r record.Record, shouldMatch bool, condition Condition) { q := New("test:").Where(condition).MustBeValid() - // fmt.Printf("%s\n", q.String()) + // fmt.Printf("%s\n", q.Print()) - matched := q.Matches(acc) + matched := q.Matches(r) switch { case !matched && shouldMatch: t.Errorf("should match: %s", q.Print()) @@ -65,36 +65,39 @@ func TestQuery(t *testing.T) { // if !gjson.Valid(testJSON) { // t.Fatal("test json is invalid") // } - f := accessor.NewJSONAccessor(&testJSON) + r, err := record.NewWrapper("", nil, append([]byte("J"), []byte(testJSON)...)) + if err != nil { + t.Fatal(err) + } - testQuery(t, f, true, Where("age", Equals, 100)) - testQuery(t, f, true, Where("age", GreaterThan, uint8(99))) - testQuery(t, f, true, Where("age", GreaterThanOrEqual, 99)) - testQuery(t, f, true, Where("age", GreaterThanOrEqual, 100)) - testQuery(t, f, true, Where("age", LessThan, 101)) - testQuery(t, f, true, Where("age", LessThanOrEqual, "101")) - testQuery(t, f, true, Where("age", LessThanOrEqual, 100)) + testQuery(t, r, true, Where("age", Equals, 100)) + testQuery(t, r, true, Where("age", GreaterThan, uint8(99))) + testQuery(t, r, true, Where("age", GreaterThanOrEqual, 99)) + testQuery(t, r, true, Where("age", GreaterThanOrEqual, 100)) + testQuery(t, r, true, Where("age", LessThan, 101)) + testQuery(t, r, true, Where("age", LessThanOrEqual, "101")) + testQuery(t, r, true, Where("age", LessThanOrEqual, 100)) - testQuery(t, f, true, Where("temperature", FloatEquals, 120.413)) - testQuery(t, f, true, Where("temperature", FloatGreaterThan, 120)) - testQuery(t, f, true, Where("temperature", FloatGreaterThanOrEqual, 120)) - testQuery(t, f, true, Where("temperature", FloatGreaterThanOrEqual, 120.413)) - testQuery(t, f, true, Where("temperature", FloatLessThan, 121)) - testQuery(t, f, true, Where("temperature", FloatLessThanOrEqual, "121")) - testQuery(t, f, true, Where("temperature", FloatLessThanOrEqual, "120.413")) + testQuery(t, r, true, Where("temperature", FloatEquals, 120.413)) + testQuery(t, r, true, Where("temperature", FloatGreaterThan, 120)) + testQuery(t, r, true, Where("temperature", FloatGreaterThanOrEqual, 120)) + testQuery(t, r, true, Where("temperature", FloatGreaterThanOrEqual, 120.413)) + testQuery(t, r, true, Where("temperature", FloatLessThan, 121)) + testQuery(t, r, true, Where("temperature", FloatLessThanOrEqual, "121")) + testQuery(t, r, true, Where("temperature", FloatLessThanOrEqual, "120.413")) - testQuery(t, f, true, Where("lastly.yay", SameAs, "final")) - testQuery(t, f, true, Where("lastly.yay", Contains, "ina")) - testQuery(t, f, true, Where("lastly.yay", StartsWith, "fin")) - testQuery(t, f, true, Where("lastly.yay", EndsWith, "nal")) - testQuery(t, f, true, Where("lastly.yay", In, "draft,final")) - testQuery(t, f, true, Where("lastly.yay", In, "final,draft")) + testQuery(t, r, true, Where("lastly.yay", SameAs, "final")) + testQuery(t, r, true, Where("lastly.yay", Contains, "ina")) + testQuery(t, r, true, Where("lastly.yay", StartsWith, "fin")) + testQuery(t, r, true, Where("lastly.yay", EndsWith, "nal")) + testQuery(t, r, true, Where("lastly.yay", In, "draft,final")) + testQuery(t, r, true, Where("lastly.yay", In, "final,draft")) - testQuery(t, f, true, Where("happy", Is, true)) - testQuery(t, f, true, Where("happy", Is, "true")) - testQuery(t, f, true, Where("happy", Is, "t")) - testQuery(t, f, true, Not(Where("happy", Is, "0"))) - testQuery(t, f, true, And( + testQuery(t, r, true, Where("happy", Is, true)) + testQuery(t, r, true, Where("happy", Is, "true")) + testQuery(t, r, true, Where("happy", Is, "t")) + testQuery(t, r, true, Not(Where("happy", Is, "0"))) + testQuery(t, r, true, And( Where("happy", Is, "1"), Not(Or( Where("happy", Is, false), @@ -102,8 +105,8 @@ func TestQuery(t *testing.T) { )), )) - testQuery(t, f, true, Where("happy", Exists, nil)) + testQuery(t, r, true, Where("happy", Exists, nil)) - testQuery(t, f, true, Where("created", Matches, "^2014-[0-9]{2}-[0-9]{2}T")) + testQuery(t, r, true, Where("created", Matches, "^2014-[0-9]{2}-[0-9]{2}T")) } diff --git a/database/storage/badger/badger.go b/database/storage/badger/badger.go index 96f63c5..0ff6abe 100644 --- a/database/storage/badger/badger.go +++ b/database/storage/badger/badger.go @@ -138,13 +138,12 @@ func (b *Badger) queryExecutor(queryIter *iterator.Iterator, q *query.Query, loc continue } - acc := r.GetAccessor(r) - if acc != nil && q.Matches(acc) { + if q.MatchesRecord(r) { copiedData, err := item.ValueCopy(nil) if err != nil { return err } - new, err := record.NewRawWrapper(b.name, string(item.Key()), copiedData) + new, err := record.NewRawWrapper(b.name, r.DatabaseKey(), copiedData) if err != nil { return err } diff --git a/database/storage/badger/badger_test.go b/database/storage/badger/badger_test.go index e858769..f89ee90 100644 --- a/database/storage/badger/badger_test.go +++ b/database/storage/badger/badger_test.go @@ -7,6 +7,7 @@ import ( "sync" "testing" + "github.com/Safing/portbase/database/query" "github.com/Safing/portbase/database/record" ) @@ -42,6 +43,7 @@ func TestBadger(t *testing.T) { } defer os.RemoveAll(testDir) // clean up + // start db, err := NewBadger("test", testDir) if err != nil { t.Fatal(err) @@ -67,11 +69,13 @@ func TestBadger(t *testing.T) { a.Meta().Update() a.SetKey("test:A") + // put record err = db.Put(a) if err != nil { t.Fatal(err) } + // get and compare r1, err := db.Get("A") if err != nil { t.Fatal(err) @@ -87,26 +91,46 @@ func TestBadger(t *testing.T) { t.Fatalf("mismatch, got %v", a1) } + // test query + q := query.New("").MustBeValid() + it, err := db.Query(q, true, true) + if err != nil { + t.Fatal(err) + } + cnt := 0 + for _ = range it.Next { + cnt++ + } + if it.Error != nil { + t.Fatal(err) + } + if cnt != 1 { + t.Fatalf("unexpected query result count: %d", cnt) + } + + // delete err = db.Delete("A") if err != nil { t.Fatal(err) } + // check if its gone _, err = db.Get("A") if err == nil { t.Fatal("should fail") } + // maintenance err = db.Maintain() if err != nil { t.Fatal(err) } - err = db.MaintainThorough() if err != nil { t.Fatal(err) } + // shutdown err = db.Shutdown() if err != nil { t.Fatal(err) diff --git a/database/subscription.go b/database/subscription.go index 592e2c7..d95ac94 100644 --- a/database/subscription.go +++ b/database/subscription.go @@ -25,7 +25,7 @@ func Subscribe(q *query.Query) (*Subscription, error) { } c.readLock.Lock() - defer c.readLock.Lock() + defer c.readLock.Unlock() c.writeLock.Lock() defer c.writeLock.Unlock() @@ -45,7 +45,7 @@ func (s *Subscription) Cancel() error { } c.readLock.Lock() - defer c.readLock.Lock() + defer c.readLock.Unlock() c.writeLock.Lock() defer c.writeLock.Unlock() diff --git a/database/subscriptions.go b/database/subscriptions.go deleted file mode 100644 index 460713e..0000000 --- a/database/subscriptions.go +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. - -package database - -// import ( -// "fmt" -// "strings" -// "sync" -// -// "github.com/Safing/portbase/database/record" -// "github.com/Safing/portbase/modules" -// "github.com/Safing/portbase/taskmanager" -// -// "github.com/tevino/abool" -// ) -// -// var subscriptionModule *modules.Module -// var subscriptions []*Subscription -// var subLock sync.Mutex -// -// var databaseUpdate chan Model -// var databaseCreate chan Model -// var databaseDelete chan string -// -// var workIsWaiting chan *struct{} -// var workIsWaitingFlag *abool.AtomicBool -// var forceProcessing chan *struct{} -// -// type Subscription struct { -// typeAndLocation map[string]bool -// exactObject map[string]bool -// children map[string]uint8 -// Created chan record.Record -// Updated chan record.Record -// Deleted chan string -// } -// -// func NewSubscription() *Subscription { -// subLock.Lock() -// defer subLock.Unlock() -// sub := &Subscription{ -// typeAndLocation: make(map[string]bool), -// exactObject: make(map[string]bool), -// children: make(map[string]uint8), -// Created: make(chan record.Record, 128), -// Updated: make(chan record.Record, 128), -// Deleted: make(chan string, 128), -// } -// subscriptions = append(subscriptions, sub) -// return sub -// } -// -// func (sub *Subscription) Subscribe(subKey string) { -// subLock.Lock() -// defer subLock.Unlock() -// -// namespaces := strings.Split(subKey, "/")[1:] -// lastSpace := "" -// if len(namespaces) != 0 { -// lastSpace = namespaces[len(namespaces)-1] -// } -// -// switch { -// case lastSpace == "": -// // save key without leading "/" -// // save with depth 255 to get all -// sub.children[strings.Trim(subKey, "/")] = 0xFF -// case strings.HasPrefix(lastSpace, "*"): -// // save key without leading or trailing "/" or "*" -// // save full wanted depth - this makes comparison easier -// sub.children[strings.Trim(subKey, "/*")] = uint8(len(lastSpace) + len(namespaces) - 1) -// case strings.Contains(lastSpace, ":"): -// sub.exactObject[subKey] = true -// default: -// sub.typeAndLocation[subKey] = true -// } -// } -// -// func (sub *Subscription) Unsubscribe(subKey string) { -// subLock.Lock() -// defer subLock.Unlock() -// -// namespaces := strings.Split(subKey, "/")[1:] -// lastSpace := "" -// if len(namespaces) != 0 { -// lastSpace = namespaces[len(namespaces)-1] -// } -// -// switch { -// case lastSpace == "": -// delete(sub.children, strings.Trim(subKey, "/")) -// case strings.HasPrefix(lastSpace, "*"): -// delete(sub.children, strings.Trim(subKey, "/*")) -// case strings.Contains(lastSpace, ":"): -// delete(sub.exactObject, subKey) -// default: -// delete(sub.typeAndLocation, subKey) -// } -// } -// -// func (sub *Subscription) Destroy() { -// subLock.Lock() -// defer subLock.Unlock() -// -// for k, v := range subscriptions { -// if v.Created == sub.Created { -// defer func() { -// subscriptions = append(subscriptions[:k], subscriptions[k+1:]...) -// }() -// close(sub.Created) -// close(sub.Updated) -// close(sub.Deleted) -// return -// } -// } -// } -// -// func (sub *Subscription) Subscriptions() *[]string { -// subStrings := make([]string, 0) -// for subString := range sub.exactObject { -// subStrings = append(subStrings, subString) -// } -// for subString := range sub.typeAndLocation { -// subStrings = append(subStrings, subString) -// } -// for subString, depth := range sub.children { -// if depth == 0xFF { -// subStrings = append(subStrings, fmt.Sprintf("/%s/", subString)) -// } else { -// subStrings = append(subStrings, fmt.Sprintf("/%s/%s", subString, strings.Repeat("*", int(depth)-len(strings.Split(subString, "/"))))) -// } -// } -// return &subStrings -// } -// -// func (sub *Subscription) String() string { -// return fmt.Sprintf("", strings.Join(*sub.Subscriptions(), " ")) -// } -// -// func (sub *Subscription) send(key string, rec record.Record, created bool) { -// if rec == nil { -// sub.Deleted <- key -// } else if created { -// sub.Created <- rec -// } else { -// sub.Updated <- rec -// } -// } -// -// func process(key string, rec record.Record, created bool) { -// subLock.Lock() -// defer subLock.Unlock() -// -// stringRep := key.String() -// // "/Comedy/MontyPython/Actor:JohnCleese" -// typeAndLocation := key.Path().String() -// // "/Comedy/MontyPython/Actor" -// namespaces := key.Namespaces() -// // ["Comedy", "MontyPython", "Actor:JohnCleese"] -// depth := uint8(len(namespaces)) -// // 3 -// -// subscriptionLoop: -// for _, sub := range subscriptions { -// if _, ok := sub.exactObject[stringRep]; ok { -// sub.send(key, rec, created) -// continue subscriptionLoop -// } -// if _, ok := sub.typeAndLocation[typeAndLocation]; ok { -// sub.send(key, rec, created) -// continue subscriptionLoop -// } -// for i := 0; i < len(namespaces); i++ { -// if subscribedDepth, ok := sub.children[strings.Join(namespaces[:i], "/")]; ok { -// if subscribedDepth >= depth { -// sub.send(key, rec, created) -// continue subscriptionLoop -// } -// } -// } -// } -// -// } -// -// func init() { -// subscriptionModule = modules.Register("Database:Subscriptions", 128) -// subscriptions = make([]*Subscription, 0) -// subLock = sync.Mutex{} -// -// databaseUpdate = make(chan Model, 32) -// databaseCreate = make(chan Model, 32) -// databaseDelete = make(chan string, 32) -// -// workIsWaiting = make(chan *struct{}, 0) -// workIsWaitingFlag = abool.NewBool(false) -// forceProcessing = make(chan *struct{}, 0) -// -// go run() -// } -// -// func run() { -// for { -// select { -// case <-subscriptionModule.Stop: -// subscriptionModule.StopComplete() -// return -// case <-workIsWaiting: -// work() -// } -// } -// } -// -// func work() { -// defer workIsWaitingFlag.UnSet() -// -// // wait -// select { -// case <-taskmanager.StartMediumPriorityMicroTask(): -// defer taskmanager.EndMicroTask() -// case <-forceProcessing: -// } -// -// // work -// for { -// select { -// case rec := <-databaseCreate: -// process(rec.GetKey(), rec, true) -// case rec := <-databaseUpdate: -// process(rec.GetKey(), rec, false) -// case key := <-databaseDelete: -// process(key, nil, false) -// default: -// return -// } -// } -// } -// -// func handleCreateSubscriptions(rec record.Record) { -// select { -// case databaseCreate <- rec: -// default: -// forceProcessing <- nil -// databaseCreate <- rec -// } -// if workIsWaitingFlag.SetToIf(false, true) { -// workIsWaiting <- nil -// } -// } -// -// func handleUpdateSubscriptions(rec record.Record) { -// select { -// case databaseUpdate <- rec: -// default: -// forceProcessing <- nil -// databaseUpdate <- rec -// } -// if workIsWaitingFlag.SetToIf(false, true) { -// workIsWaiting <- nil -// } -// } -// -// func handleDeleteSubscriptions(key string) { -// select { -// case databaseDelete <- key: -// default: -// forceProcessing <- nil -// databaseDelete <- key -// } -// if workIsWaitingFlag.SetToIf(false, true) { -// workIsWaiting <- nil -// } -// } diff --git a/database/subscriptions_test.go b/database/subscriptions_test.go deleted file mode 100644 index 5ab06d3..0000000 --- a/database/subscriptions_test.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. - -package database - -// import ( -// "strconv" -// "strings" -// "sync" -// "testing" -// ) -// -// var subTestWg sync.WaitGroup -// -// func waitForSubs(t *testing.T, sub *Subscription, highest int) { -// defer subTestWg.Done() -// expecting := 1 -// var subbedModel Model -// forLoop: -// for { -// select { -// case subbedModel = <-sub.Created: -// case subbedModel = <-sub.Updated: -// } -// t.Logf("got model from subscription: %s", subbedModel.GetKey().String()) -// if !strings.HasPrefix(subbedModel.GetKey().Name(), "sub") { -// // not a model that we use for testing, other tests might be interfering -// continue forLoop -// } -// number, err := strconv.Atoi(strings.TrimPrefix(subbedModel.GetKey().Name(), "sub")) -// if err != nil || number != expecting { -// t.Errorf("test subscription: got unexpected model %s, expected sub%d", subbedModel.GetKey().String(), expecting) -// continue forLoop -// } -// if number == highest { -// return -// } -// expecting++ -// } -// } -// -// func TestSubscriptions(t *testing.T) { -// -// // create subscription -// sub := NewSubscription() -// -// // FIRST TEST -// -// subTestWg.Add(1) -// go waitForSubs(t, sub, 3) -// sub.Subscribe("/Tests/") -// t.Log(sub.String()) -// -// (&(TestingModel{})).CreateInNamespace("", "sub1") -// (&(TestingModel{})).CreateInNamespace("A", "sub2") -// (&(TestingModel{})).CreateInNamespace("A/B/C/D/E", "sub3") -// -// subTestWg.Wait() -// -// // SECOND TEST -// -// subTestWg.Add(1) -// go waitForSubs(t, sub, 3) -// sub.Unsubscribe("/Tests/") -// sub.Subscribe("/Tests/A/****") -// t.Log(sub.String()) -// -// (&(TestingModel{})).CreateInNamespace("", "subX") -// (&(TestingModel{})).CreateInNamespace("A", "sub1") -// (&(TestingModel{})).CreateInNamespace("A/B/C/D", "sub2") -// (&(TestingModel{})).CreateInNamespace("A/B/C/D/E", "subX") -// (&(TestingModel{})).CreateInNamespace("A", "sub3") -// -// subTestWg.Wait() -// -// // THIRD TEST -// -// subTestWg.Add(1) -// go waitForSubs(t, sub, 3) -// sub.Unsubscribe("/Tests/A/****") -// sub.Subscribe("/Tests/TestingModel:sub1") -// sub.Subscribe("/Tests/TestingModel:sub1/TestingModel") -// t.Log(sub.String()) -// -// (&(TestingModel{})).CreateInNamespace("", "sub1") -// (&(TestingModel{})).CreateInNamespace("", "subX") -// (&(TestingModel{})).CreateInNamespace("TestingModel:sub1", "sub2") -// (&(TestingModel{})).CreateInNamespace("TestingModel:sub1/A", "subX") -// (&(TestingModel{})).CreateInNamespace("TestingModel:sub1", "sub3") -// -// subTestWg.Wait() -// -// // FINAL STUFF -// -// model := &TestingModel{} -// model.CreateInNamespace("Invalid", "subX") -// model.Save() -// -// sub.Destroy() -// -// // time.Sleep(1 * time.Second) -// // pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) -// -// }