From 5f7609e2d171d0164a23648f5e117d0326507f42 Mon Sep 17 00:00:00 2001 From: Branden J Brown Date: Mon, 19 Aug 2024 22:12:28 -0400 Subject: [PATCH] brain/*: remove forgetting by tuples It was complex to implement and not really the right thing. Now that we record traces of generated messages, we don't need it. Resolves #44. --- brain/braintest/braintest.go | 33 -- brain/kvbrain/forget.go | 57 --- brain/kvbrain/forget_test.go | 204 ----------- brain/learn.go | 18 - brain/learn_test.go | 50 --- brain/sqlbrain/forget.go | 33 -- brain/sqlbrain/forget.sql | 14 - brain/sqlbrain/forget_test.go | 628 ---------------------------------- 8 files changed, 1037 deletions(-) delete mode 100644 brain/sqlbrain/forget.sql diff --git a/brain/braintest/braintest.go b/brain/braintest/braintest.go index 9663809..688c2e9 100644 --- a/brain/braintest/braintest.go +++ b/brain/braintest/braintest.go @@ -18,7 +18,6 @@ import ( // If a brain cannot be created without error, new should call t.Fatal. func Test(ctx context.Context, t *testing.T, new func(context.Context) brain.Brain) { t.Run("speak", testSpeak(ctx, new(ctx))) - t.Run("forget", testForget(ctx, new(ctx))) t.Run("forgetMessage", testForgetMessage(ctx, new(ctx))) t.Run("forgetDuring", testForgetDuring(ctx, new(ctx))) t.Run("combinatoric", testCombinatoric(ctx, new(ctx))) @@ -183,38 +182,6 @@ func testSpeak(ctx context.Context, br brain.Brain) func(t *testing.T) { } } -// testForget tests that a brain forgets what it forgets. -func testForget(ctx context.Context, br brain.Brain) func(t *testing.T) { - return func(t *testing.T) { - learn(ctx, t, br) - if err := brain.Forget(ctx, br, "kessoku", messages[0].Tokens()); err != nil { - t.Errorf("couldn't forget: %v", err) - } - for range 100 { - s, trace, err := brain.Speak(ctx, br, "kessoku", "") - if err != nil { - t.Errorf("couldn't speak: %v", err) - } - if strings.Contains(s, "bocchi") { - t.Errorf("remembered that which must be forgotten: %q", s) - } - if trace[len(trace)-1] == messages[0].ID { - t.Errorf("id %q should have been forgotten but was used in %q", messages[0].ID, trace) - } - } - for range 10000 { - s, _, err := brain.Speak(ctx, br, "sickhack", "") - if err != nil { - t.Errorf("couldn't speak: %v", err) - } - if strings.Contains(s, "bocchi") { - return - } - } - t.Error("didn't see bocchi in many attempts; deleted from wrong tag?") - } -} - // testForgetMessage tests that a brain can forget messages by ID. func testForgetMessage(ctx context.Context, br brain.Brain) func(t *testing.T) { return func(t *testing.T) { diff --git a/brain/kvbrain/forget.go b/brain/kvbrain/forget.go index 2226416..1300036 100644 --- a/brain/kvbrain/forget.go +++ b/brain/kvbrain/forget.go @@ -3,16 +3,11 @@ package kvbrain import ( "bytes" "context" - "errors" "fmt" "slices" - "strings" "sync" "time" - "github.com/dgraph-io/badger/v4" - - "github.com/zephyrtronium/robot/brain" "github.com/zephyrtronium/robot/userhash" ) @@ -94,58 +89,6 @@ func (p *past) findUser(user userhash.Hash) [][]byte { return r } -// Forget removes a set of recorded tuples. The tuples provided are as for -// Learn. If a tuple has been recorded multiple times, only the first -// should be deleted. If a tuple has not been recorded, it should be -// ignored. -func (br *Brain) Forget(ctx context.Context, tag string, tuples []brain.Tuple) error { - // Sort tuples so that we always seek forward. - slices.SortFunc(tuples, func(a, b brain.Tuple) int { - p := slices.Compare(a.Prefix, b.Prefix) - if p == 0 { - p = strings.Compare(a.Suffix, b.Suffix) - } - return p - }) - err := br.knowledge.Update(func(txn *badger.Txn) error { - var errs error - opts := badger.DefaultIteratorOptions - it := txn.NewIterator(opts) - defer it.Close() - b := hashTag(nil, tag) - for _, t := range tuples { - b = append(appendPrefix(b[:tagHashLen], t.Prefix), '\xff') // terminate the prefix - it.Seek(b) - for it.ValidForPrefix(b) { - v := it.Item() - it.Next() - if v.IsDeletedOrExpired() { - continue - } - u, err := v.ValueCopy(nil) - if err != nil { - errs = errors.Join(errs, err) - continue - } - if string(u) != t.Suffix { - continue - } - if err := txn.Delete(v.KeyCopy(nil)); err != nil { - errs = errors.Join(errs, err) - continue - } - // Only delete a single instance of each tuple. - break - } - } - return errs - }) - if err != nil { - return fmt.Errorf("couldn't forget: %w", err) - } - return nil -} - // ForgetMessage forgets everything learned from a single given message. // If nothing has been learned from the message, it should be ignored. func (br *Brain) ForgetMessage(ctx context.Context, tag, id string) error { diff --git a/brain/kvbrain/forget_test.go b/brain/kvbrain/forget_test.go index 4c7acd5..25aef53 100644 --- a/brain/kvbrain/forget_test.go +++ b/brain/kvbrain/forget_test.go @@ -209,210 +209,6 @@ func BenchmarkPastFindUser(b *testing.B) { //go:noinline func use(x [][]byte) {} -func TestForget(t *testing.T) { - type message struct { - id string - user userhash.Hash - tag string - time time.Time - tups []brain.Tuple - } - cases := []struct { - name string - msgs []message - forget []brain.Tuple - want map[string]string - }{ - { - name: "none", - msgs: []message{ - { - id: "1", - user: userhash.Hash{2}, - tag: "kessoku", - time: time.Unix(0, 0), - tups: []brain.Tuple{ - { - Prefix: []string{"ryou", "bocchi"}, - Suffix: "kita", - }, - }, - }, - }, - forget: []brain.Tuple{ - { - Prefix: []string{"eliza", "kikuri"}, - Suffix: "shima", - }, - }, - want: map[string]string{ - mkey("kessoku", "ryou\xffbocchi\xff\xff", "1"): "kita", - }, - }, - { - name: "suffix", - msgs: []message{ - { - id: "1", - user: userhash.Hash{2}, - tag: "kessoku", - time: time.Unix(0, 0), - tups: []brain.Tuple{ - { - Prefix: []string{"ryou", "bocchi"}, - Suffix: "kita", - }, - }, - }, - }, - forget: []brain.Tuple{ - { - Prefix: []string{"eliza", "kikuri"}, - Suffix: "kita", - }, - }, - want: map[string]string{ - mkey("kessoku", "ryou\xffbocchi\xff\xff", "1"): "kita", - }, - }, - { - name: "prefix", - msgs: []message{ - { - id: "1", - user: userhash.Hash{2}, - tag: "kessoku", - time: time.Unix(0, 0), - tups: []brain.Tuple{ - { - Prefix: []string{"ryou", "bocchi"}, - Suffix: "kita", - }, - }, - }, - }, - forget: []brain.Tuple{ - { - Prefix: []string{"ryou", "bocchi"}, - Suffix: "shima", - }, - }, - want: map[string]string{ - mkey("kessoku", "ryou\xffbocchi\xff\xff", "1"): "kita", - }, - }, - { - name: "tag", - msgs: []message{ - { - id: "1", - user: userhash.Hash{2}, - tag: "sickhack", - time: time.Unix(0, 0), - tups: []brain.Tuple{ - { - Prefix: []string{"ryou", "bocchi"}, - Suffix: "kita", - }, - }, - }, - }, - forget: []brain.Tuple{ - { - Prefix: []string{"ryou", "bocchi"}, - Suffix: "kita", - }, - }, - want: map[string]string{ - mkey("sickhack", "ryou\xffbocchi\xff\xff", "1"): "kita", - }, - }, - { - name: "match", - msgs: []message{ - { - id: "1", - user: userhash.Hash{2}, - tag: "kessoku", - time: time.Unix(0, 0), - tups: []brain.Tuple{ - { - Prefix: []string{"ryou", "bocchi"}, - Suffix: "kita", - }, - }, - }, - }, - forget: []brain.Tuple{ - { - Prefix: []string{"ryou", "bocchi"}, - Suffix: "kita", - }, - }, - want: map[string]string{}, - }, - { - name: "single", - msgs: []message{ - { - id: "1", - user: userhash.Hash{2}, - tag: "kessoku", - time: time.Unix(0, 0), - tups: []brain.Tuple{ - { - Prefix: []string{"ryou", "bocchi"}, - Suffix: "kita", - }, - }, - }, - { - id: "2", - user: userhash.Hash{2}, - tag: "kessoku", - time: time.Unix(0, 0), - tups: []brain.Tuple{ - { - Prefix: []string{"ryou", "bocchi"}, - Suffix: "kita", - }, - }, - }, - }, - forget: []brain.Tuple{ - { - Prefix: []string{"ryou", "bocchi"}, - Suffix: "kita", - }, - }, - want: map[string]string{ - mkey("kessoku", "ryou\xffbocchi\xff\xff", "2"): "kita", - }, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - t.Parallel() - ctx := context.Background() - db, err := badger.Open(badger.DefaultOptions("").WithInMemory(true).WithLogger(nil)) - if err != nil { - t.Fatal(err) - } - br := New(db) - for _, msg := range c.msgs { - err := br.Learn(ctx, msg.tag, msg.id, msg.user, msg.time, msg.tups) - if err != nil { - t.Errorf("failed to learn: %v", err) - } - } - if err := br.Forget(ctx, "kessoku", c.forget); err != nil { - t.Errorf("failed to forget: %v", err) - } - dbcheck(t, db, c.want) - }) - } -} - func TestForgetMessage(t *testing.T) { type message struct { id string diff --git a/brain/learn.go b/brain/learn.go index 2d98f6a..fefb855 100644 --- a/brain/learn.go +++ b/brain/learn.go @@ -27,12 +27,6 @@ type Learner interface { // Each tuple's prefix has entropy reduction transformations applied. // Tuples in the argument may share storage for prefixes. Learn(ctx context.Context, tag, id string, user userhash.Hash, t time.Time, tuples []Tuple) error - // Forget removes a set of recorded tuples. - // The tuples provided are as for Learn. - // If a tuple has been recorded multiple times, only the first - // should be deleted. - // If a tuple has not been recorded, it should be ignored. - Forget(ctx context.Context, tag string, tuples []Tuple) error // ForgetMessage forgets everything learned from a single given message. // If nothing has been learned from the message, it should be ignored. ForgetMessage(ctx context.Context, tag, id string) error @@ -56,18 +50,6 @@ func Learn(ctx context.Context, l Learner, tag, id string, user userhash.Hash, t return l.Learn(ctx, tag, id, user, t, tt) } -// Forget removes tokens from a Learner. -func Forget(ctx context.Context, l Learner, tag string, toks []string) error { - if len(toks) == 0 { - return nil - } - tt := tuplesPool.Get() - defer func() { tuplesPool.Put(tt[:0]) }() - tt = slices.Grow(tt, len(toks)+1) - tt = tupleToks(tt, toks) - return l.Forget(ctx, tag, tt) -} - func tupleToks(tt []Tuple, toks []string) []Tuple { slices.Reverse(toks) pres := slices.Clone(toks) diff --git a/brain/learn_test.go b/brain/learn_test.go index 46b00fd..c839ff1 100644 --- a/brain/learn_test.go +++ b/brain/learn_test.go @@ -88,53 +88,3 @@ func TestLearn(t *testing.T) { }) } } - -func TestForget(t *testing.T) { - s := func(x ...string) []string { return x } - cases := []struct { - name string - msg []string - want []brain.Tuple - }{ - { - name: "single", - msg: s("word"), - want: []brain.Tuple{ - {Prefix: s("word"), Suffix: ""}, - {Prefix: nil, Suffix: "word"}, - }, - }, - { - name: "many-1", - msg: s("many", "words", "in", "this", "message"), - want: []brain.Tuple{ - {Prefix: s("message", "this", "in", "words", "many"), Suffix: ""}, - {Prefix: s("this", "in", "words", "many"), Suffix: "message"}, - {Prefix: s("in", "words", "many"), Suffix: "this"}, - {Prefix: s("words", "many"), Suffix: "in"}, - {Prefix: s("many"), Suffix: "words"}, - {Prefix: nil, Suffix: "many"}, - }, - }, - { - name: "entropy", - msg: s("A"), - want: []brain.Tuple{ - {Prefix: s("a"), Suffix: ""}, - {Prefix: nil, Suffix: "A"}, - }, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - var l testLearner - err := brain.Forget(context.Background(), &l, "", c.msg) - if err != nil { - t.Error(err) - } - if diff := cmp.Diff(c.want, l.forgot); diff != "" { - t.Errorf("forgot the wrong things from %q:\n%s", c.msg, diff) - } - }) - } -} diff --git a/brain/sqlbrain/forget.go b/brain/sqlbrain/forget.go index 353e922..2accd95 100644 --- a/brain/sqlbrain/forget.go +++ b/brain/sqlbrain/forget.go @@ -9,42 +9,9 @@ import ( "zombiezen.com/go/sqlite" "zombiezen.com/go/sqlite/sqlitex" - "github.com/zephyrtronium/robot/brain" "github.com/zephyrtronium/robot/userhash" ) -//go:embed forget.sql -var forgetSQL string - -// Forget removes a set of recorded tuples. -func (br *Brain) Forget(ctx context.Context, tag string, tuples []brain.Tuple) (err error) { - conn, err := br.db.Take(ctx) - defer br.db.Put(conn) - if err != nil { - return fmt.Errorf("couldn't get connection to forget: %w", err) - } - defer sqlitex.Transaction(conn)(&err) - p := make([]byte, 0, 256) - s := make([]byte, 0, 32) - for _, tt := range tuples { - p = prefix(p[:0], tt.Prefix) - s = append(s[:0], tt.Suffix...) - // Unlike learning and speaking, forgetting is generally outside the hot path. - // So, it's fine to have extra allocations and reflection here. - opts := sqlitex.ExecOptions{ - Named: map[string]any{ - ":tag": tag, - ":prefix": p, - ":suffix": s, - }, - } - if err := sqlitex.Execute(conn, forgetSQL, &opts); err != nil { - return fmt.Errorf("couldn't forget: %w", err) - } - } - return nil -} - // ForgetMessage forgets everything learned from a single given message. // If nothing has been learned from the message, nothing happens. func (br *Brain) ForgetMessage(ctx context.Context, tag, id string) (err error) { diff --git a/brain/sqlbrain/forget.sql b/brain/sqlbrain/forget.sql deleted file mode 100644 index 2d54d2d..0000000 --- a/brain/sqlbrain/forget.sql +++ /dev/null @@ -1,14 +0,0 @@ -UPDATE knowledge -SET deleted = 'FORGET' -WHERE tag = :tag - AND id = ( - SELECT id - FROM knowledge - WHERE tag = :tag - AND prefix = :prefix - AND suffix = :suffix - AND LIKELY(deleted IS NULL) - LIMIT 1 - ) - AND prefix = :prefix - -- We don't need to match suffix because every prefix of a given message is unique. diff --git a/brain/sqlbrain/forget_test.go b/brain/sqlbrain/forget_test.go index 1f33a1e..1b50f6e 100644 --- a/brain/sqlbrain/forget_test.go +++ b/brain/sqlbrain/forget_test.go @@ -11,634 +11,6 @@ import ( "github.com/zephyrtronium/robot/userhash" ) -func TestForget(t *testing.T) { - learn := []learn{ - { - tag: "結束", - user: userhash.Hash{1}, - id: "2", - t: 3, - tups: []brain.Tuple{ - {Prefix: strings.Fields("喜多 虹夏 リョウ ぼっち"), Suffix: ""}, - {Prefix: strings.Fields("虹夏 リョウ ぼっち"), Suffix: "喜多"}, - {Prefix: strings.Fields("リョウ ぼっち"), Suffix: "虹夏"}, - {Prefix: strings.Fields("ぼっち"), Suffix: "リョウ"}, - {Prefix: nil, Suffix: "ぼっち"}, - }, - }, - { - tag: "結束", - user: userhash.Hash{4}, - id: "5", - t: 6, - tups: []brain.Tuple{ - {Prefix: []string{"bocchi"}, Suffix: ""}, - {Prefix: nil, Suffix: "bocchi"}, - }, - }, - { - tag: "結束", - user: userhash.Hash{7}, - id: "8", - t: 9, - tups: []brain.Tuple{ - {Prefix: []string{"bocchi"}, Suffix: ""}, - {Prefix: nil, Suffix: "bocchi"}, - }, - }, - { - tag: "sickhack", - user: userhash.Hash{1}, - id: "2", - t: 3, - tups: []brain.Tuple{ - {Prefix: []string{"bocchi"}, Suffix: ""}, - {Prefix: nil, Suffix: "bocchi"}, - }, - }, - } - initKnow := []know{ - { - tag: "結束", - id: "2", - prefix: "喜多\x00虹夏\x00リョウ\x00ぼっち\x00", - suffix: "", - }, - { - tag: "結束", - id: "2", - prefix: "虹夏\x00リョウ\x00ぼっち\x00", - suffix: "喜多", - }, - { - tag: "結束", - id: "2", - prefix: "リョウ\x00ぼっち\x00", - suffix: "虹夏", - }, - { - tag: "結束", - id: "2", - prefix: "ぼっち\x00", - suffix: "リョウ", - }, - { - tag: "結束", - id: "2", - prefix: "", - suffix: "ぼっち", - }, - { - tag: "結束", - id: "5", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "結束", - id: "5", - prefix: "", - suffix: "bocchi", - }, - { - tag: "結束", - id: "8", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "結束", - id: "8", - prefix: "", - suffix: "bocchi", - }, - { - tag: "sickhack", - id: "2", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "sickhack", - id: "2", - prefix: "", - suffix: "bocchi", - }, - } - initMsgs := []msg{ - { - tag: "結束", - id: "2", - time: 3, - user: userhash.Hash{1}, - }, - { - tag: "結束", - id: "5", - time: 6, - user: userhash.Hash{4}, - }, - { - tag: "結束", - id: "8", - time: 9, - user: userhash.Hash{7}, - }, - { - tag: "sickhack", - id: "2", - time: 3, - user: userhash.Hash{1}, - }, - } - type forget struct { - tag string - tups []brain.Tuple - } - cases := []struct { - name string - forget []forget - know []know - msgs []msg - }{ - { - name: "empty", - forget: nil, - know: initKnow, - msgs: initMsgs, - }, - { - name: "none", - forget: []forget{ - { - tag: "結束", - tups: []brain.Tuple{ - {Prefix: strings.Fields("tuples such no"), Suffix: ""}, - {Prefix: strings.Fields("such no"), Suffix: "tuples"}, - {Prefix: strings.Fields("no"), Suffix: "such"}, - {Prefix: nil, Suffix: "no"}, - }, - }, - }, - know: initKnow, - msgs: initMsgs, - }, - { - name: "single", - forget: []forget{ - { - tag: "結束", - tups: []brain.Tuple{ - {Prefix: nil, Suffix: "ぼっち"}, - }, - }, - }, - know: []know{ - { - tag: "結束", - id: "2", - prefix: "喜多\x00虹夏\x00リョウ\x00ぼっち\x00", - suffix: "", - }, - { - tag: "結束", - id: "2", - prefix: "虹夏\x00リョウ\x00ぼっち\x00", - suffix: "喜多", - }, - { - tag: "結束", - id: "2", - prefix: "リョウ\x00ぼっち\x00", - suffix: "虹夏", - }, - { - tag: "結束", - id: "2", - prefix: "ぼっち\x00", - suffix: "リョウ", - }, - { - tag: "結束", - id: "2", - prefix: "", - suffix: "ぼっち", - deleted: ref("FORGET"), - }, - { - tag: "結束", - id: "5", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "結束", - id: "5", - prefix: "", - suffix: "bocchi", - }, - { - tag: "結束", - id: "8", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "結束", - id: "8", - prefix: "", - suffix: "bocchi", - }, - { - tag: "sickhack", - id: "2", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "sickhack", - id: "2", - prefix: "", - suffix: "bocchi", - }, - }, - msgs: initMsgs, - }, - { - name: "all", - forget: []forget{ - { - tag: "結束", - tups: []brain.Tuple{ - {Prefix: strings.Fields("喜多 虹夏 リョウ ぼっち"), Suffix: ""}, - {Prefix: strings.Fields("虹夏 リョウ ぼっち"), Suffix: "喜多"}, - {Prefix: strings.Fields("リョウ ぼっち"), Suffix: "虹夏"}, - {Prefix: strings.Fields("ぼっち"), Suffix: "リョウ"}, - {Prefix: nil, Suffix: "ぼっち"}, - }, - }, - }, - know: []know{ - { - tag: "結束", - id: "2", - prefix: "喜多\x00虹夏\x00リョウ\x00ぼっち\x00", - suffix: "", - deleted: ref("FORGET"), - }, - { - tag: "結束", - id: "2", - prefix: "虹夏\x00リョウ\x00ぼっち\x00", - suffix: "喜多", - deleted: ref("FORGET"), - }, - { - tag: "結束", - id: "2", - prefix: "リョウ\x00ぼっち\x00", - suffix: "虹夏", - deleted: ref("FORGET"), - }, - { - tag: "結束", - id: "2", - prefix: "ぼっち\x00", - suffix: "リョウ", - deleted: ref("FORGET"), - }, - { - tag: "結束", - id: "2", - prefix: "", - suffix: "ぼっち", - deleted: ref("FORGET"), - }, - { - tag: "結束", - id: "5", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "結束", - id: "5", - prefix: "", - suffix: "bocchi", - }, - { - tag: "結束", - id: "8", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "結束", - id: "8", - prefix: "", - suffix: "bocchi", - }, - { - tag: "sickhack", - id: "2", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "sickhack", - id: "2", - prefix: "", - suffix: "bocchi", - }, - }, - msgs: initMsgs, - }, - { - name: "once", - forget: []forget{ - { - tag: "結束", - tups: []brain.Tuple{ - {Prefix: nil, Suffix: "bocchi"}, - }, - }, - }, - know: []know{ - { - tag: "結束", - id: "2", - prefix: "喜多\x00虹夏\x00リョウ\x00ぼっち\x00", - suffix: "", - }, - { - tag: "結束", - id: "2", - prefix: "虹夏\x00リョウ\x00ぼっち\x00", - suffix: "喜多", - }, - { - tag: "結束", - id: "2", - prefix: "リョウ\x00ぼっち\x00", - suffix: "虹夏", - }, - { - tag: "結束", - id: "2", - prefix: "ぼっち\x00", - suffix: "リョウ", - }, - { - tag: "結束", - id: "2", - prefix: "", - suffix: "ぼっち", - }, - { - tag: "結束", - id: "5", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "結束", - id: "5", - prefix: "", - suffix: "bocchi", - deleted: ref("FORGET"), - }, - { - tag: "結束", - id: "8", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "結束", - id: "8", - prefix: "", - suffix: "bocchi", - }, - { - tag: "sickhack", - id: "2", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "sickhack", - id: "2", - prefix: "", - suffix: "bocchi", - }, - }, - msgs: initMsgs, - }, - { - name: "multi", - forget: []forget{ - { - tag: "結束", - tups: []brain.Tuple{ - {Prefix: nil, Suffix: "bocchi"}, - }, - }, - { - tag: "結束", - tups: []brain.Tuple{ - {Prefix: nil, Suffix: "bocchi"}, - }, - }, - }, - know: []know{ - { - tag: "結束", - id: "2", - prefix: "喜多\x00虹夏\x00リョウ\x00ぼっち\x00", - suffix: "", - }, - { - tag: "結束", - id: "2", - prefix: "虹夏\x00リョウ\x00ぼっち\x00", - suffix: "喜多", - }, - { - tag: "結束", - id: "2", - prefix: "リョウ\x00ぼっち\x00", - suffix: "虹夏", - }, - { - tag: "結束", - id: "2", - prefix: "ぼっち\x00", - suffix: "リョウ", - }, - { - tag: "結束", - id: "2", - prefix: "", - suffix: "ぼっち", - }, - { - tag: "結束", - id: "5", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "結束", - id: "5", - prefix: "", - suffix: "bocchi", - deleted: ref("FORGET"), - }, - { - tag: "結束", - id: "8", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "結束", - id: "8", - prefix: "", - suffix: "bocchi", - deleted: ref("FORGET"), - }, - { - tag: "sickhack", - id: "2", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "sickhack", - id: "2", - prefix: "", - suffix: "bocchi", - }, - }, - msgs: initMsgs, - }, - { - name: "tag", - forget: []forget{ - { - tag: "sickhack", - tups: []brain.Tuple{ - {Prefix: nil, Suffix: "bocchi"}, - }, - }, - }, - know: []know{ - { - tag: "結束", - id: "2", - prefix: "喜多\x00虹夏\x00リョウ\x00ぼっち\x00", - suffix: "", - }, - { - tag: "結束", - id: "2", - prefix: "虹夏\x00リョウ\x00ぼっち\x00", - suffix: "喜多", - }, - { - tag: "結束", - id: "2", - prefix: "リョウ\x00ぼっち\x00", - suffix: "虹夏", - }, - { - tag: "結束", - id: "2", - prefix: "ぼっち\x00", - suffix: "リョウ", - }, - { - tag: "結束", - id: "2", - prefix: "", - suffix: "ぼっち", - }, - { - tag: "結束", - id: "5", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "結束", - id: "5", - prefix: "", - suffix: "bocchi", - }, - { - tag: "結束", - id: "8", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "結束", - id: "8", - prefix: "", - suffix: "bocchi", - }, - { - tag: "sickhack", - id: "2", - prefix: "bocchi\x00", - suffix: "", - }, - { - tag: "sickhack", - id: "2", - prefix: "", - suffix: "bocchi", - deleted: ref("FORGET"), - }, - }, - msgs: initMsgs, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - t.Parallel() - ctx := context.Background() - db := testDB(ctx) - br, err := sqlbrain.Open(ctx, db) - if err != nil { - t.Fatalf("couldn't open brain: %v", err) - } - for _, m := range learn { - err := br.Learn(ctx, m.tag, m.id, m.user, time.Unix(0, m.t), m.tups) - if err != nil { - t.Errorf("failed to learn %v/%v: %v", m.tag, m.id, err) - } - } - conn, err := db.Take(ctx) - defer db.Put(conn) - if err != nil { - t.Fatalf("couldn't get conn to check db state: %v", err) - } - contents(t, conn, initKnow, initMsgs) - if t.Failed() { - t.Fatal("setup failed") - } - for _, v := range c.forget { - err := br.Forget(ctx, v.tag, v.tups) - if err != nil { - t.Errorf("couldn't forget %q in %v: %v", v.tups, v.tag, err) - } - } - contents(t, conn, c.know, c.msgs) - }) - } -} - func TestForgetMessage(t *testing.T) { learn := []learn{ {