├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── hob.go
├── hob_test.go
├── lww_e_set.go
├── lww_e_set_test.go
├── set.go
├── set_test.go
├── two_phase_set.go
└── two_phase_set_test.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | script: go get github.com/bmizerany/assert && go get github.com/mrb/hob && go test
3 | notifications:
4 | email: false
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2012 Michael R. Bernstein
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://travis-ci.org/mrb/hob)
2 |
3 | ## Hob: CRDT For Go
4 |
5 | Go implementations of data structures from A comprehensive study of Convergent and Commutative Replicated Data Types
6 |
7 | #### This is pre-release, experimental software
8 |
9 | ### Examples
10 |
11 | #### Two-Phase-Set
12 |
13 | ```go
14 | two_phase_set, _ := hob.NewTwoPhaseSet()
15 | two_phase_set.Add("I'm in the add set")
16 | two_phase_set.Add("I'm also in the add set")
17 | two_phase_set.Remove("I'm in the add set") // and in the remove set
18 | json, _ := two_phase_set.JSON()
19 | ```
20 |
21 | Produces:
22 |
23 | ```json
24 | {
25 | "type":"2p-set",
26 | "a": ["I'm in the add set","I'm also in the add set"],
27 | "r": ["I'm in the add set"]
28 | }
29 | ```
30 |
31 | #### LWW-element-Set
32 |
33 | ```go
34 | lwwset, _ := hob.NewLWWSet("a")
35 | lwwset.Add("Dude!")
36 | lwwset.Remove("Dude!")
37 | lwwset.Add("Other key")
38 | json, _ := lwwset.JSON()
39 | ```
40 |
41 | Produces:
42 |
43 | ```json
44 | {
45 | "type":"lww-e-set",
46 | "bias":"a",
47 | "e":[
48 | ["Dude!","2012-07-16T00:42:05.146259Z","2012-07-16T00:42:05.146263Z"],
49 | ["Other key","2012-07-16T00:42:05.146267Z",""]
50 | ]
51 | }
52 | ```
53 |
54 |
55 | ### Prior Art
56 |
57 | A few Open Source implementations of these data structures exist. Hob conforms to the same JSON format as:
58 |
59 | * Reid Draper *Knockbox* (Clojure Implementation - Github Repo)
60 | * Kyle Kingsbury *Meangirls* (Ruby Implementation - Github Repo)
61 |
62 | ### TODO
63 |
64 | A lot! This library lacks a lot at the moment, including JSON decoding. I'm trying to figure out the best idiomatic Go way to handle parsing multiple data types with the same code.
65 |
66 | ### Tests
67 |
68 | `go test`
69 |
70 | ### Credits
71 |
72 | hob is (c) Michael R. Bernstein, 2012
73 |
74 | ### License
75 |
76 | hob is distributed under the MIT License, see `LICENSE` file for details.
77 |
--------------------------------------------------------------------------------
/hob.go:
--------------------------------------------------------------------------------
1 | package hob
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "time"
7 | )
8 |
9 | var (
10 | ErrJSONDecode = errors.New("error decoding JSON")
11 | )
12 |
13 | func ParseJson(jsondata []byte) (st interface{}, err error) {
14 | err = json.Unmarshal(jsondata, &st)
15 |
16 | if data_type, ok := st.(map[string]interface{})["type"]; ok {
17 | if data_type == "lww-e-set" {
18 | return st, nil
19 | } else {
20 | return nil, ErrJSONDecode
21 | }
22 | }
23 |
24 | return st, nil
25 | }
26 |
27 | func Timestamp() (now string) {
28 | now = time.Now().UTC().Format(time.RFC3339Nano)
29 | return now
30 | }
31 |
--------------------------------------------------------------------------------
/hob_test.go:
--------------------------------------------------------------------------------
1 | package hob
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/bmizerany/assert"
7 | "github.com/mrb/hob"
8 | )
9 |
10 | func TestTimestamp(t *testing.T) {
11 | assert.T(t, len(hob.Timestamp()) > 23)
12 | }
13 |
--------------------------------------------------------------------------------
/lww_e_set.go:
--------------------------------------------------------------------------------
1 | package hob
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "time"
7 | )
8 |
9 | var (
10 | ErrInvalidBias = errors.New("invalid bias, must be a or r")
11 | )
12 |
13 | type Pair struct {
14 | Add string `json:"add"`
15 | Remove string `json:"remove"`
16 | }
17 |
18 | type LWWESet struct {
19 | Type string `json:"type"`
20 | Bias string `json:"bias"`
21 | Data map[string]*Pair `json:"-"`
22 | JSONData [][3]string `json:"e"`
23 | }
24 |
25 | func NewLWWESet(bias string) (lwwset *LWWESet, err error) {
26 | if bias != "a" && bias != "r" {
27 | err = ErrInvalidBias
28 | }
29 |
30 | data := make(map[string]*Pair)
31 |
32 | lwwset = &LWWESet{
33 | Type: "lww-e-set",
34 | Bias: bias,
35 | Data: data,
36 | }
37 | return
38 | }
39 |
40 | func (lwwset *LWWESet) Add(value string) (err error) {
41 | data := lwwset.Data
42 |
43 | if pair, ok := lwwset.Data[value]; ok {
44 | pair.Add = Timestamp()
45 | return
46 | }
47 |
48 | data[value] = &Pair{
49 | Add: Timestamp(),
50 | }
51 | return
52 | }
53 |
54 | func (lwwset *LWWESet) Remove(value string) (err error) {
55 | data := lwwset.Data
56 |
57 | if pair, ok := lwwset.Data[value]; ok {
58 | pair.Remove = Timestamp()
59 | return
60 | }
61 |
62 | data[value] = &Pair{
63 | Remove: Timestamp(),
64 | }
65 | return
66 | }
67 |
68 | func (lwwset *LWWESet) Test(value string) (is_member bool, err error) {
69 | if pair, ok := lwwset.Data[value]; ok {
70 | if remove := pair.Remove; remove != "" {
71 | remove_time, err := time.Parse(time.RFC3339, pair.Remove)
72 | if err != nil {
73 | return false, err
74 | }
75 |
76 | add_time, err := time.Parse(time.RFC3339, pair.Add)
77 | if err != nil {
78 | return false, err
79 | }
80 |
81 | bias := lwwset.Bias
82 |
83 | if bias == "a" {
84 | if add_time.Before(remove_time) == false {
85 | is_member = true
86 | } else {
87 | is_member = false
88 | }
89 | } else {
90 | if add_time.After(remove_time) == true {
91 | is_member = true
92 | } else {
93 | is_member = false
94 | }
95 | }
96 |
97 | return is_member, err
98 | }
99 |
100 | is_member = true
101 | } else {
102 | is_member = false
103 | }
104 | return is_member, nil
105 | }
106 |
107 | func (lwwset *LWWESet) ToSet() (keys []string, err error) {
108 | return
109 | }
110 |
111 | func (lwwset *LWWESet) Clone() (clone *LWWESet, err error) {
112 | clone = &LWWESet{
113 | Type: lwwset.Type,
114 | Bias: lwwset.Bias,
115 | Data: lwwset.Data,
116 | }
117 | return
118 | }
119 |
120 | func (lwwset *LWWESet) Merge(olwwset *LWWESet) (merged_set *LWWESet, err error) {
121 | merged_set, err = lwwset.Clone()
122 | if err != nil {
123 | return nil, err
124 | }
125 |
126 | for k, v := range olwwset.Data {
127 | if pair, ok := lwwset.Data[k]; ok {
128 | merged, err := pair.merge(v)
129 | if err != nil {
130 | return nil, err
131 | }
132 | merged_set.Data[k] = merged
133 | } else {
134 | merged_set.Data[k] = v
135 | }
136 | }
137 |
138 | return
139 | }
140 |
141 | func (lwwset *LWWESet) JSON() (json_bytes []byte, err error) {
142 | for k, v := range lwwset.Data {
143 | inner := [3]string{k, v.Add, v.Remove}
144 | lwwset.JSONData = append(lwwset.JSONData, inner)
145 | }
146 |
147 | json_bytes, err = json.Marshal(lwwset)
148 | return
149 | }
150 |
151 | func newPair(value string) (p *Pair) {
152 | p = &Pair{
153 | Add: "",
154 | Remove: "",
155 | }
156 | return
157 | }
158 |
159 | func (p *Pair) merge(op *Pair) (merged *Pair, err error) {
160 | merged = &Pair{
161 | Add: "",
162 | Remove: "",
163 | }
164 |
165 | add, err := time.Parse(time.RFC3339, p.Add)
166 | if err != nil {
167 | return nil, err
168 | }
169 |
170 | oadd, err := time.Parse(time.RFC3339, op.Add)
171 | if err != nil {
172 | return nil, err
173 | }
174 |
175 | if add.After(oadd) == true {
176 | merged.Add = p.Add
177 | } else {
178 | merged.Add = op.Add
179 | }
180 |
181 | var remove, oremove time.Time
182 |
183 | if p.Remove != "" {
184 | remove, _ = time.Parse(time.RFC3339, p.Remove)
185 | } else {
186 | if op.Remove == "" {
187 | return
188 | } else {
189 | merged.Remove = op.Remove
190 | return
191 | }
192 | }
193 |
194 | if op.Remove != "" {
195 | oremove, _ = time.Parse(time.RFC3339, op.Remove)
196 | } else {
197 | if p.Remove == "" {
198 | return
199 | } else {
200 | merged.Remove = p.Remove
201 | return
202 | }
203 | }
204 |
205 | if remove.After(oremove) != true {
206 | merged.Remove = p.Remove
207 | } else {
208 | merged.Remove = op.Remove
209 | }
210 |
211 | return
212 | }
213 |
--------------------------------------------------------------------------------
/lww_e_set_test.go:
--------------------------------------------------------------------------------
1 | package hob
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/bmizerany/assert"
7 | "github.com/mrb/hob"
8 | )
9 |
10 | func TestNewLWWESet(t *testing.T) {
11 | _, err := hob.NewLWWESet("nah")
12 | assert.T(t, err == hob.ErrInvalidBias)
13 | }
14 |
15 | func setupLWWESet(t *testing.T) (lwwset *hob.LWWESet) {
16 | lwwset, err := hob.NewLWWESet("a")
17 | assert.T(t, lwwset != nil)
18 | assert.T(t, err == nil)
19 | return
20 | }
21 |
22 | func setupLWWESetWithData(t *testing.T) (lwwset *hob.LWWESet) {
23 | lwwset = setupLWWESet(t)
24 |
25 | err := lwwset.Add("Key1")
26 | assert.T(t, err == nil)
27 |
28 | err = lwwset.Remove("Key1")
29 | assert.T(t, err == nil)
30 |
31 | err = lwwset.Add("Key2")
32 | assert.T(t, err == nil)
33 |
34 | return
35 | }
36 |
37 | /*
38 | - Bias: "a" / "r"
39 | */
40 | func TestLWWESetBias(t *testing.T) {
41 | lwwset := setupLWWESetWithData(t)
42 | assert.T(t, lwwset.Bias == "a")
43 |
44 | rlwwset := setupLWWESetWithData(t)
45 | rlwwset.Bias = "r"
46 | assert.T(t, rlwwset.Bias == "r")
47 |
48 | // e added, not removed - true / true
49 | is_member, err := lwwset.Test("Key2")
50 | assert.T(t, err == nil)
51 | assert.T(t, is_member == true)
52 |
53 | is_member, err = rlwwset.Test("Key2")
54 | assert.T(t, err == nil)
55 | assert.T(t, is_member == true)
56 |
57 | // e added, removed, removed > added - false / false
58 | is_member, err = lwwset.Test("Key1")
59 | assert.T(t, err == nil)
60 | assert.T(t, is_member == false)
61 |
62 | is_member, err = rlwwset.Test("Key1")
63 | assert.T(t, err == nil)
64 | assert.T(t, is_member == false)
65 |
66 | // e added, removed, added -- added = removed - true / false
67 | err = lwwset.Add("Key1")
68 | assert.T(t, err == nil)
69 | lwwset.Data["Key1"].Add = lwwset.Data["Key1"].Remove
70 | assert.T(t, lwwset.Data["Key1"].Add == lwwset.Data["Key1"].Remove)
71 |
72 | is_member, err = lwwset.Test("Key1")
73 | assert.T(t, err == nil)
74 | assert.T(t, is_member == true)
75 |
76 | err = rlwwset.Add("Key1")
77 | assert.T(t, err == nil)
78 | rlwwset.Data["Key1"].Add = rlwwset.Data["Key1"].Remove
79 | assert.T(t, rlwwset.Data["Key1"].Add == rlwwset.Data["Key1"].Remove)
80 |
81 | is_member, err = rlwwset.Test("Key1")
82 | assert.T(t, err == nil)
83 | assert.T(t, is_member == false)
84 |
85 | // e added, removed, added again - true / true
86 | err = lwwset.Add("Key1")
87 | assert.T(t, err == nil)
88 |
89 | is_member, err = lwwset.Test("Key1")
90 | assert.T(t, err == nil)
91 | assert.T(t, is_member == true)
92 |
93 | err = rlwwset.Add("Key1")
94 | assert.T(t, err == nil)
95 |
96 | is_member, err = rlwwset.Test("Key1")
97 | assert.T(t, err == nil)
98 | assert.T(t, is_member == true)
99 | }
100 |
101 | func TestLWWJson(t *testing.T) {
102 | lwwset := setupLWWESetWithData(t)
103 |
104 | json, err := lwwset.JSON()
105 |
106 | assert.T(t, err == nil)
107 | assert.T(t, json != nil)
108 |
109 | //data, err := hob.ParseJson(json)
110 | //assert.T(t, err == nil)
111 | //assert.T(t, data != nil)
112 | //assert.T(t, data.(LWWESet).Type == "lww-e-set")
113 | //assert.T(t, data.(*hob.LWWESet).JSONData != nil)
114 | //assert.T(t, len(data.(*hob.LWWESet).JSONData) == 2)
115 | }
116 |
117 | func TestLWWMerge(t *testing.T) {
118 | lwwset := setupLWWESetWithData(t)
119 | olwwset := setupLWWESetWithData(t)
120 |
121 | err := olwwset.Remove("Key2")
122 | assert.T(t, err == nil)
123 |
124 | merged, err := lwwset.Merge(olwwset)
125 | assert.T(t, err == nil)
126 |
127 | assert.T(t, merged.Data["Key2"] != nil)
128 | assert.T(t, merged.Data["Key2"].Add != "")
129 | assert.T(t, merged.Data["Key2"].Remove != "")
130 | assert.T(t, merged.Data["Key2"].Remove == olwwset.Data["Key2"].Remove)
131 | assert.T(t, merged.Data["Key2"].Add == olwwset.Data["Key2"].Add)
132 | }
133 |
--------------------------------------------------------------------------------
/set.go:
--------------------------------------------------------------------------------
1 | package hob
2 |
3 | import ()
4 |
5 | type SetData map[string]bool
6 |
7 | type Set struct {
8 | setData SetData
9 | Set []string
10 | }
11 |
12 | func NewSet() (set *Set) {
13 | return &Set{
14 | setData: newSetData(),
15 | Set: newSetSlice(),
16 | }
17 | }
18 |
19 | func newSetData() (setData SetData) {
20 | setData = make(SetData)
21 | return
22 | }
23 |
24 | func newSetSlice() (setSlice []string) {
25 | setSlice = make([]string, 0)
26 | return
27 | }
28 |
29 | func (set *Set) Add(value string) (ok bool) {
30 | set.setData[value] = true
31 | ok = true
32 | return
33 | }
34 |
35 | func (set *Set) Remove(value string) (ok bool) {
36 | delete(set.setData, value)
37 | ok = true
38 | return
39 | }
40 |
41 | func (set *Set) Test(value string) (ok bool) {
42 | ok = set.setData[value]
43 | return
44 | }
45 |
46 | func (set *Set) Clone() (clone *Set) {
47 | clone = &Set{
48 | setData: set.setData,
49 | Set: set.Set,
50 | }
51 | return
52 | }
53 |
54 | func (set *Set) Union(oset *Set) (union *Set) {
55 | union = set.Clone()
56 | for value, _ := range oset.setData {
57 | union.setData[value] = true
58 | }
59 | return
60 | }
61 |
62 | func (set *Set) Intersection(oset *Set) (intersection *Set) {
63 | intersection = NewSet()
64 |
65 | var shorterSet, longerSet *Set
66 |
67 | if len(set.setData) > len(oset.setData) {
68 | shorterSet = oset
69 | longerSet = set
70 | } else {
71 | shorterSet = set
72 | longerSet = oset
73 | }
74 |
75 | for value, _ := range shorterSet.setData {
76 | if ok := longerSet.setData[value]; ok {
77 | intersection.setData[value] = true
78 | }
79 | }
80 |
81 | return
82 | }
83 |
84 | func (set *Set) Slice() []string {
85 | set.Set = newSetSlice()
86 |
87 | for value, ok := range set.setData {
88 | if ok {
89 | set.Set = append(set.Set, value)
90 | }
91 | }
92 |
93 | return set.Set
94 | }
95 |
--------------------------------------------------------------------------------
/set_test.go:
--------------------------------------------------------------------------------
1 | package hob
2 |
3 | import (
4 | "log"
5 | "testing"
6 |
7 | "github.com/bmizerany/assert"
8 | "github.com/mrb/hob"
9 | )
10 |
11 | func TestSet(t *testing.T) {
12 | set := hob.NewSet()
13 |
14 | set.Add("Key1")
15 |
16 | key1 := set.Test("Key1")
17 | key2 := set.Test("Key2")
18 |
19 | assert.T(t, key1 == true)
20 | assert.T(t, key2 == false)
21 |
22 | set.Add("Key2")
23 | key2 = set.Test("Key2")
24 | assert.T(t, key2 == true)
25 |
26 | set.Remove("Key2")
27 | key2 = set.Test("Key2")
28 | assert.T(t, key2 == false)
29 |
30 | set.Add("Key3")
31 | set.Add("Key4")
32 | set.Add("Key5")
33 | set.Add("Key6")
34 | set.Add("Key7")
35 |
36 | log.Print(set.Slice())
37 |
38 | set2 := hob.NewSet()
39 | set2.Add("Key1")
40 | set2.Add("Okey1")
41 | set2.Add("Okey23")
42 |
43 | union := set.Union(set2)
44 | log.Print(union.Slice())
45 |
46 | intersection := set.Intersection(set2)
47 | log.Print(intersection.Slice())
48 | }
49 |
--------------------------------------------------------------------------------
/two_phase_set.go:
--------------------------------------------------------------------------------
1 | package hob
2 |
3 | import (
4 | "encoding/json"
5 | )
6 |
7 | type TwoPhaseSet struct {
8 | Type string `json:"type"`
9 | A map[string]bool `json:"-"`
10 | R map[string]bool `json:"-"`
11 | JSONA []string `json:"a"`
12 | JSONR []string `json:"r"`
13 | }
14 |
15 | func NewTwoPhaseSet() (twoPhaseSet *TwoPhaseSet, err error) {
16 | add := make(map[string]bool)
17 | remove := make(map[string]bool)
18 |
19 | twoPhaseSet = &TwoPhaseSet{
20 | Type: "2p-set",
21 | A: add,
22 | R: remove,
23 | }
24 | return
25 | }
26 |
27 | func (twoPhaseSet *TwoPhaseSet) Add(value string) (err error) {
28 | twoPhaseSet.A[value] = true
29 | return
30 | }
31 |
32 | func (twoPhaseSet *TwoPhaseSet) Remove(value string) (err error) {
33 | twoPhaseSet.R[value] = true
34 | return
35 | }
36 |
37 | func (twoPhaseSet *TwoPhaseSet) Test(value string) (is_member bool, err error) {
38 | if _, ok := twoPhaseSet.R[value]; ok {
39 | is_member = false
40 | return
41 | }
42 |
43 | if _, ok := twoPhaseSet.A[value]; ok {
44 | is_member = true
45 | return
46 | }
47 | return
48 | }
49 |
50 | func (twoPhaseSet *TwoPhaseSet) JSON() (json_bytes []byte, err error) {
51 | for k, _ := range twoPhaseSet.A {
52 | twoPhaseSet.JSONA = append(twoPhaseSet.JSONA, k)
53 | }
54 |
55 | for k, _ := range twoPhaseSet.R {
56 | twoPhaseSet.JSONR = append(twoPhaseSet.JSONR, k)
57 | }
58 |
59 | json_bytes, err = json.Marshal(twoPhaseSet)
60 | return
61 | }
62 |
63 | func (twoPhaseSet *TwoPhaseSet) Clone() (clone *TwoPhaseSet, err error) {
64 | clone = &TwoPhaseSet{
65 | Type: twoPhaseSet.Type,
66 | A: twoPhaseSet.A,
67 | R: twoPhaseSet.R,
68 | }
69 | return
70 | }
71 |
72 | func (twoPhaseSet *TwoPhaseSet) Merge(oTwoPhaseSet *TwoPhaseSet) (merged_set *TwoPhaseSet, err error) {
73 | clone, err := twoPhaseSet.Clone()
74 | if err != nil {
75 | return nil, err
76 | }
77 |
78 | merged_set = clone
79 |
80 | return
81 | }
82 |
--------------------------------------------------------------------------------
/two_phase_set_test.go:
--------------------------------------------------------------------------------
1 | package hob
2 |
3 | import (
4 | "log"
5 | "testing"
6 |
7 | "github.com/bmizerany/assert"
8 | "github.com/mrb/hob"
9 | )
10 |
11 | func setupTwoPhaseSetWithData(t *testing.T) (twoPhaseSet *hob.TwoPhaseSet) {
12 | twoPhaseSet, err := hob.NewTwoPhaseSet()
13 | assert.T(t, err == nil)
14 | assert.T(t, twoPhaseSet != nil)
15 |
16 | twoPhaseSet.Add("Key1")
17 | twoPhaseSet.Add("Key2")
18 | twoPhaseSet.Remove("Key1")
19 | return
20 | }
21 |
22 | func TestNewTwoPhaseSet(t *testing.T) {
23 | twoPhaseSet := setupTwoPhaseSetWithData(t)
24 | assert.T(t, twoPhaseSet != nil)
25 |
26 | jsonb, err := twoPhaseSet.JSON()
27 | assert.T(t, err == nil)
28 | assert.T(t, jsonb != nil)
29 |
30 | jsons := string(jsonb)
31 | log.Print(jsons)
32 | }
33 |
34 | func TestTwoPhaseSetTest(t *testing.T) {
35 | twoPhaseSet := setupTwoPhaseSetWithData(t)
36 | assert.T(t, twoPhaseSet != nil)
37 |
38 | is_member, err := twoPhaseSet.Test("Key1")
39 | assert.T(t, err == nil)
40 | assert.T(t, is_member == false)
41 |
42 | is_member, err = twoPhaseSet.Test("Key2")
43 | assert.T(t, err == nil)
44 | assert.T(t, is_member == true)
45 |
46 | }
47 |
--------------------------------------------------------------------------------