├── .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 | [![Build Status](https://secure.travis-ci.org/mrb/hob.png?branch=master)](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 | --------------------------------------------------------------------------------