From 104cdab35fdb96c915a5bf9cd97dc8146bcaae6a Mon Sep 17 00:00:00 2001 From: Saeid Aghapour Date: Tue, 18 Jul 2023 02:14:46 +0330 Subject: [PATCH 1/2] implements data structure, Set(#138) --- structure/set.go | 82 +++++++++++++++++++++++ structure/set_test.go | 151 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 structure/set.go create mode 100644 structure/set_test.go diff --git a/structure/set.go b/structure/set.go new file mode 100644 index 00000000..00a01d29 --- /dev/null +++ b/structure/set.go @@ -0,0 +1,82 @@ +package structure + +import ( + "github.com/ByteStorage/FlyDB/config" + "github.com/ByteStorage/FlyDB/engine" +) + +type SetStructure struct { + db *engine.DB +} + +func NewSetStructure(options config.Options) (*SetStructure, error) { + db, err := engine.NewDB(options) + if err != nil { + return nil, err + } + return &SetStructure{db: db}, nil +} + +// SAdd adds a member to the set stored at key. +// +// If the set did not exist, a new set will be created +// and the member will be added to it. +func (s *SetStructure) SAdd(key, member string) error { + + return nil +} + +// SAdds adds multiple members to a set +func (s *SetStructure) SAdds(key string, members ...string) error { + return nil +} + +// SRem removes a member from a set +func (s *SetStructure) SRem(key, member string) error { + return nil +} + +// SRems removes multiple members from a set +func (s *SetStructure) SRems(key string, members ...string) error { + return nil +} + +// SCard gets the cardinality (size) of a set +func (s *SetStructure) SCard(key string) (int, error) { + return 0, nil +} + +// SMembers gets all members of a set +func (s *SetStructure) SMembers(key string) ([]string, error) { + return nil, nil +} + +// SIsMember checks if a member exists in a set +func (s *SetStructure) SIsMember(key, member string) (bool, error) { + return false, nil +} + +// SUnion gets the union of multiple sets +func (s *SetStructure) SUnion(keys ...string) ([]string, error) { + return nil, nil +} + +// SInter gets the intersection of multiple sets +func (s *SetStructure) SInter(keys ...string) ([]string, error) { + return nil, nil +} + +// SDiff gets the difference between two sets +func (s *SetStructure) SDiff(key1, key2 string) ([]string, error) { + return nil, nil +} + +// SUnionStore stores the union of multiple sets in a destination set +func (s *SetStructure) SUnionStore(destination, key1, key2 string) error { + return nil +} + +// SInterStore stores the intersection of multiple sets in a destination set +func (s *SetStructure) SInterStore(destination, key1, key2 string) error { + return nil +} diff --git a/structure/set_test.go b/structure/set_test.go new file mode 100644 index 00000000..407f829f --- /dev/null +++ b/structure/set_test.go @@ -0,0 +1,151 @@ +package structure + +import ( + "github.com/ByteStorage/FlyDB/config" + "github.com/ByteStorage/FlyDB/engine" + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func initTestSetDb() (*SetStructure, *config.Options) { + opts := config.DefaultOptions + dir, _ := os.MkdirTemp("", "TestSetStructure") + opts.DirPath = dir + str, _ := NewSetStructure(opts) + return str, &opts +} +func TestSAdd(t *testing.T) { + s, _ := initTestSetDb() + + err := s.SAdd("destination", "key1") + if err != nil { + t.Errorf("Expected nil error, got: %v", err) + } + +} +func TestSAdds(t *testing.T) { + s, _ := initTestSetDb() + + err := s.SAdds("destination", "key1", "key2") + if err != nil { + t.Errorf("Expected nil error, got: %v", err) + } + +} +func TestSRems(t *testing.T) { + s, _ := initTestSetDb() + + err := s.SRems("destination", "key1", "key2") + if err != nil { + t.Errorf("Expected nil error, got: %v", err) + } + +} +func TestSRem(t *testing.T) { + s, _ := initTestSetDb() + + err := s.SRem("destination", "key1") + if err != nil { + t.Errorf("Expected nil error, got: %v", err) + } + +} +func TestSCard(t *testing.T) { + s, _ := initTestSetDb() + + got, err := s.SCard("destination") + if err != nil { + t.Errorf("Expected nil error, got: %v", err) + } + assert.NotNil(t, got) + +} +func TestSMembers(t *testing.T) { + s, _ := initTestSetDb() + + mem, err := s.SMembers("destination") + if err != nil { + t.Errorf("Expected nil error, got: %v", err) + } + assert.IsType(t, []string{}, mem) + +} +func TestSIsMember(t *testing.T) { + s, _ := initTestSetDb() + + got, err := s.SIsMember("destination", "key1") + if err != nil { + t.Errorf("Expected nil error, got: %v", err) + } + assert.False(t, got) + +} + +func TestNewSetStructure(t *testing.T) { + // expect error + opts := config.DefaultOptions + opts.DirPath = "" // the cause of error + setDB, err := NewSetStructure(opts) + assert.NotNil(t, err) + assert.Nil(t, setDB) + // expect no error + opts = config.DefaultOptions + dir, _ := os.MkdirTemp("", "TestSetStructure") + opts.DirPath = dir + setDB, _ = NewSetStructure(opts) + assert.NotNil(t, setDB) + assert.IsType(t, &engine.DB{}, setDB.db) +} + +func TestSUnion(t *testing.T) { + s, _ := initTestSetDb() + keys := []string{"key1", "key2"} + + _, err := s.SUnion(keys...) + if err != nil { + t.Errorf("Expected nil error, got: %v", err) + } + +} + +func TestSInter(t *testing.T) { + s, _ := initTestSetDb() + keys := []string{"key1", "key2"} + + _, err := s.SInter(keys...) + if err != nil { + t.Errorf("Expected nil error, got: %v", err) + } + +} + +func TestSDiff(t *testing.T) { + s, _ := initTestSetDb() + + _, err := s.SDiff("key1", "key2") + if err != nil { + t.Errorf("Expected nil error, got: %v", err) + } + +} + +func TestSUnionStore(t *testing.T) { + s, _ := initTestSetDb() + + err := s.SUnionStore("destination", "key1", "key2") + if err != nil { + t.Errorf("Expected nil error, got: %v", err) + } + +} + +func TestSInterStore(t *testing.T) { + s, _ := initTestSetDb() + + err := s.SInterStore("destination", "key1", "key2") + if err != nil { + t.Errorf("Expected nil error, got: %v", err) + } + +} From af5753143f2335c31bfd0a36f9314d70c6bb46df Mon Sep 17 00:00:00 2001 From: Saeid Aghapour Date: Tue, 18 Jul 2023 10:41:07 +0330 Subject: [PATCH 2/2] implements data structure, Set(#138) --- structure/set.go | 357 +++++++++++++- structure/set_test.go | 1056 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 1318 insertions(+), 95 deletions(-) diff --git a/structure/set.go b/structure/set.go index 00a01d29..f3b71ed2 100644 --- a/structure/set.go +++ b/structure/set.go @@ -1,14 +1,25 @@ package structure import ( + "errors" "github.com/ByteStorage/FlyDB/config" "github.com/ByteStorage/FlyDB/engine" + _const "github.com/ByteStorage/FlyDB/lib/const" + "github.com/ByteStorage/FlyDB/lib/encoding" ) type SetStructure struct { db *engine.DB } +var ( + ErrMemberNotFound = errors.New("ErrMemberNotFound: member not found") + ErrSetNotInitialized = errors.New("wrong operation: set not initialized") +) + +// FSets serves as a set data structure where every key in the map is an element of the set. +type FSets map[string]struct{} + func NewSetStructure(options config.Options) (*SetStructure, error) { db, err := engine.NewDB(options) if err != nil { @@ -22,61 +33,365 @@ func NewSetStructure(options config.Options) (*SetStructure, error) { // If the set did not exist, a new set will be created // and the member will be added to it. func (s *SetStructure) SAdd(key, member string) error { - - return nil + return s.SAdds(key, member) } -// SAdds adds multiple members to a set +/* +SAdds attempts to add multiple members to a set identified by the 'key' parameter. The members to add are variadic and come as 'members...' in the function signature. This means the function accepts an arbitrary number of strings to be added to the set. + +Parameters: + - 's': Pointer to an instance of SetStructure. This is the receiver of the SAdds function. + - 'key': String that is used as the identifier for the set. + - 'members': Variadic parameter that represents the members to be added. Since it's a variadic parameter, it can be any number of strings. + +Return: + - It returns an error if it encounters one during execution. Errors might occur if the key is found to be empty or if problems arise when retrieving the set from the database or saving it back. + +Internal logic: + 1. The function first checks if the provided key is an empty string. If so, it returns an error indicating that the key is empty. + 2. Converts the key into bytes format for internal use. + 3. It tries to get the set associated with the key from the database. If it encounters an error during retrieval, it returns that error. + 4. If it can successfully retrieve the set, it attempts to add the members to the set. + 5. After adding the members, it tries to save this updated set back into the database. If this operation throws an error, it returns that error. + +All the methods like 'getZSetFromDB', 'setZSetToDB', and 'add' handle the lower-level logic associated with database interaction and set manipulation. +*/ func (s *SetStructure) SAdds(key string, members ...string) error { - return nil + fs, err := s.checkAndGetSet(key, true) + if err != nil { + return err + } + fs.add(members...) + return s.setSetToDB(stringToBytesWithKey(key), fs) } // SRem removes a member from a set func (s *SetStructure) SRem(key, member string) error { - return nil + return s.SRems(key, member) } // SRems removes multiple members from a set func (s *SetStructure) SRems(key string, members ...string) error { - return nil + fs, err := s.checkAndGetSet(key, false) + if err != nil { + return err + } + if err = fs.remove(members...); err != nil { + return err + } + return s.setSetToDB(stringToBytesWithKey(key), fs) } // SCard gets the cardinality (size) of a set func (s *SetStructure) SCard(key string) (int, error) { - return 0, nil + fs, err := s.checkAndGetSet(key, false) + if err != nil { + return -1, err + } + return len(*fs), nil } -// SMembers gets all members of a set +// SMembers gets all members of a set identified by the provided key. +// If the set does not exist or the key belongs to a different data type, +// the function will return an error. +// It returns a slice of strings which are the members of the set. func (s *SetStructure) SMembers(key string) ([]string, error) { - return nil, nil + fs, err := s.checkAndGetSet(key, false) + if err != nil { + return nil, err + } + // type of fs is map[string]struct{} + // we need to take the keys and make them a slice + var members []string + for k := range *fs { + members = append(members, k) + } + + return members, nil } // SIsMember checks if a member exists in a set func (s *SetStructure) SIsMember(key, member string) (bool, error) { - return false, nil + fs, err := s.checkAndGetSet(key, false) + if err != nil { + return false, err + } + + return fs.exists(member), nil } // SUnion gets the union of multiple sets func (s *SetStructure) SUnion(keys ...string) ([]string, error) { - return nil, nil + // if there are no keys provided, then there's no union + if len(keys) == 0 { + return nil, nil + } + mem := make(map[string]struct{}) + var members []string + for _, key := range keys { + fs, err := s.checkAndGetSet(key, false) + if err != nil { + return nil, err + } + for k := range *fs { + if _, ok := mem[k]; !ok { + mem[k] = struct{}{} + members = append(members, k) + } + } + } + return members, nil } -// SInter gets the intersection of multiple sets +// SInter returns the intersection of multiple sets. +// The parameter 'keys' is a variadic parameter, meaning +// it can accept any number of arguments. These arguments are the keys +// of the sets that are to be intersected. func (s *SetStructure) SInter(keys ...string) ([]string, error) { - return nil, nil + // if there are no keys, then there's no intersection + if len(keys) == 0 { + return nil, nil + } + // All elements of 'first' are stored in 'mem' map because it's checked + // against each of the subsequent sets. + first, err := s.checkAndGetSet(keys[0], false) + if err != nil { + return nil, err + } + + mem := make(map[string]struct{}) + for k := range *first { + mem[k] = struct{}{} + } + + var members []string + // For each other key, we get its set and its members. + for _, key := range keys[1:] { + fs, err := s.checkAndGetSet(key, false) + if err != nil { + return nil, err + } + // We check if each member of the current set is in 'mem'. + // If yes, we add it to 'members' array, and remove it from 'mem' map. + for k := range *fs { + if _, ok := mem[k]; ok { + delete(mem, k) + members = append(members, k) + } + } + } + + return members, nil } -// SDiff gets the difference between two sets -func (s *SetStructure) SDiff(key1, key2 string) ([]string, error) { - return nil, nil +// SDiff computes the difference between the first and subsequent sets. +// It returns keys unique to the first set and an error if applicable. +func (s *SetStructure) SDiff(keys ...string) ([]string, error) { + // If there are no keys, then there's no difference + if len(keys) == 0 { + return nil, nil + } + + first, err := s.checkAndGetSet(keys[0], false) + if err != nil { + return nil, err + } + + // Initialize a set for members of first set + mem := make(map[string]struct{}) + for k := range *first { + mem[k] = struct{}{} + } + + var diffMembers []string + // If member is in the first set and also in another set, remove it. + for _, key := range keys[1:] { + fs, err := s.checkAndGetSet(key, false) + if err != nil { + return nil, err + } + for k := range *fs { + // Only delete the existing members in the first set. Do not add members from other sets + delete(mem, k) + } + } + // Remaining members in mem are unique to first set. + for k := range mem { + diffMembers = append(diffMembers, k) + } + + return diffMembers, nil } -// SUnionStore stores the union of multiple sets in a destination set -func (s *SetStructure) SUnionStore(destination, key1, key2 string) error { - return nil +// SUnionStore calculates and stores the union of multiple sets +// in a destination set. +// +// 'destination' is the name of the set where the result will be stored. +// +// 'keys' is a variadic parameter that represents the names of all the sets +// whose union is to be calculated. +// +// The function returns an error if there's any issue computing the union or +// storing the result in the destination set. +// +// Usage example: +// +// set := NewSetStructure(opts) // initialize t +// err := set.SUnionStore("result", "set1", "set2", "set3") +// if err != nil { +// log.Fatal(err) +// } +func (s *SetStructure) SUnionStore(destination string, keys ...string) error { + union, err := s.SUnion(keys...) + if err != nil { + return err + } + return s.SAdds(destination, union...) +} + +/* +SInterStore stores the intersection of multiple sets in a destination set. + +Function parameters: + - destination (string): The key for the set where the intersection result will be stored. + - keys (string[]): An array of keys for the sets to be intersected. + +This function first uses the SInter function to find the intersection of the provided sets (keys). +In case an error occurs during this process (the SInter function returns an error), it will immediately return that error. +Otherwise, it proceeds to use the SAdds function to store the results of the intersection into the destination set. + +The function will return the error from the SAdds function if it occurs or nil if the operation is successful. + +Errors can occur when there's an issue with accessing the data structures involved in the operations. For instance, trying to perform set operations on non-existing keys or keys that point to non-set data types. + +Returns: + - error: An error object that describes an error that occurred during the computation (if any). +*/ +func (s *SetStructure) SInterStore(destination string, keys ...string) error { + inter, err := s.SInter(keys...) + if err != nil { + return err + } + return s.SAdds(destination, inter...) } +func (s *SetStructure) checkAndGetSet(key string, createIfNotExist bool) (*FSets, error) { + // Check if value is empty + if err := checkKey(key); err != nil { + return nil, err + } + keyBytes := stringToBytesWithKey(key) + // Get the list + set, err := s.getSetFromDB(keyBytes, createIfNotExist) + if err != nil { + return nil, err + } -// SInterStore stores the intersection of multiple sets in a destination set -func (s *SetStructure) SInterStore(destination, key1, key2 string) error { + return set, nil +} + +// internal/private functions + +// add adds new elements to the set data structure (FSets). +// It allows one or more string parameters. If an element already exists, it won't be added again. +func (s *FSets) add(member ...string) { + for _, m := range member { + // Check if the member to be added already exists in the FSets or not. + // existence of the element in the FSets needs to be checked, not the value. + if _, exists := (*s)[m]; !exists { + // If the elements do not already exist, add it to the set. + // The value here is an empty struct, because we are only interested in the keys. + (*s)[m] = struct{}{} + } + } +} + +// remove is a method on the FSet struct that takes in a variable number +// of string parameters. It takes these strings to be "members", and generates errors +// if any of these strings are not currently present in the FSet. +// +// Parameters: +// +// member: a variable list of strings that are supposed to be "members" of the FSet. +// +// Returns: +// +// If all of the strings in the members parameter are present in the FSet, +// it then proceeds to remove each of these members from the FSet, +// and returns nil, signifying success. +// +// If any string from the members parameter does not exist in the FSet, +// the function immediately returns an ErrInvalidArgs error, +// which signifies that an invalid argument has been provided to the function. +func (s *FSets) remove(member ...string) error { + if s == nil { + return ErrSetNotInitialized + } + for _, m := range member { + // check to see if all members exist + if _, exists := (*s)[m]; !exists { + return ErrMemberNotFound + } + } + // iterate again + for _, m := range member { + // remove elements from FSets + delete(*s, m) + } return nil } + +// GetSetFromDB retrieves a set from database given a key. If createIfNotExist is true, +// a new set will be created if the key is not found. It returns the file sets and any write error encountered. +func (s *SetStructure) getSetFromDB(key []byte, createIfNotExist bool) (*FSets, error) { + if s.db == nil { + return nil, ErrSetNotInitialized + } + dbData, err := s.db.Get(key) + var zSetValue FSets + // If key is not found, return nil for both; otherwise return the error. + if err != nil { + if errors.Is(err, _const.ErrKeyNotFound) && createIfNotExist { + return &FSets{}, nil + } + return nil, err + } else { + err = encoding.NewMessagePackDecoder(dbData).Decode(&zSetValue) + if err != nil { + return nil, err + } + return &zSetValue, err + } + // return a pointer to the deserialized ZSetNodes, nil for the error +} + +// setSetToDB +func (s *SetStructure) setSetToDB(key []byte, zSetValue *FSets) error { + val := encoding.NewMessagePackEncoder() + err := val.Encode(zSetValue) + if err != nil { + return err + } + return s.db.Put(key, val.Bytes()) +} + +func (s *SetStructure) exists(key string, member ...string) bool { + if err := checkKey(key); err != nil { + return false + } + keyBytes := stringToBytesWithKey(key) + + zSet, err := s.getSetFromDB(keyBytes, false) + + if err != nil { + return false + } + return zSet.exists(member...) +} +func (s *FSets) exists(member ...string) bool { + for _, s2 := range member { + if _, ok := (*s)[s2]; !ok { + return false + } + } + return true +} diff --git a/structure/set_test.go b/structure/set_test.go index 407f829f..5e4337a2 100644 --- a/structure/set_test.go +++ b/structure/set_test.go @@ -1,10 +1,14 @@ package structure import ( + "errors" "github.com/ByteStorage/FlyDB/config" "github.com/ByteStorage/FlyDB/engine" + _const "github.com/ByteStorage/FlyDB/lib/const" "github.com/stretchr/testify/assert" "os" + "reflect" + "sort" "testing" ) @@ -15,71 +19,853 @@ func initTestSetDb() (*SetStructure, *config.Options) { str, _ := NewSetStructure(opts) return str, &opts } + func TestSAdd(t *testing.T) { - s, _ := initTestSetDb() + // Creating a list of cases for testing. + tests := []struct { + name string + setup func(*SetStructure) + key string + membersAdd []string + membersWant []string + wantErr error + }{ + { + name: "test when key empty", + setup: func(s *SetStructure) {}, + key: "", + membersAdd: []string{"key1", "key2"}, + membersWant: []string{}, + wantErr: _const.ErrKeyIsEmpty, + }, + { + name: "test add two members", + setup: func(s *SetStructure) {}, + key: "destination", + membersAdd: []string{"key1", "key2"}, + membersWant: []string{"key1", "key2"}, + wantErr: nil, + }, + { + name: "test add three members one duplicate", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "key2") + }, + key: "destination", + membersAdd: []string{"key1", "key2"}, + membersWant: []string{"key1", "key2"}, + wantErr: nil, + }, + { + name: "test add db not init", + setup: func(s *SetStructure) { + s.db = nil + }, + key: "destination", + membersAdd: []string{"key1", "key2"}, + membersWant: []string{"key1", "key2"}, + wantErr: ErrSetNotInitialized, + }, + } - err := s.SAdd("destination", "key1") - if err != nil { - t.Errorf("Expected nil error, got: %v", err) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + // Call the method with test case parameters. + for _, s2 := range tt.membersAdd { + err := s.SAdd(tt.key, s2) + if !errors.Is(err, tt.wantErr) { + t.Errorf("SRems() error = %v, wantErr %v", err, tt.wantErr) + } + } + if tt.wantErr == nil { + // positive verify + assert.True(t, s.exists(tt.key, tt.membersWant...)) + } + }) } } -func TestSAdds(t *testing.T) { - s, _ := initTestSetDb() - err := s.SAdds("destination", "key1", "key2") - if err != nil { - t.Errorf("Expected nil error, got: %v", err) +func TestSAdds(t *testing.T) { + // Creating a list of cases for testing. + tests := []struct { + name string + setup func(*SetStructure) + key string + membersAdd []string + membersWant []string + wantErr error + }{ + { + name: "test when key empty", + setup: func(s *SetStructure) {}, + key: "", + membersAdd: []string{"key1", "key2"}, + membersWant: []string{}, + wantErr: _const.ErrKeyIsEmpty, + }, + { + name: "test add two members", + setup: func(s *SetStructure) {}, + key: "destination", + membersAdd: []string{"key1", "key2"}, + membersWant: []string{"key1", "key2"}, + wantErr: nil, + }, + { + name: "test add three members one duplicate", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "key2") + }, + key: "destination", + membersAdd: []string{"key1", "key2"}, + membersWant: []string{"key1", "key2"}, + wantErr: nil, + }, } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + // Call the method with test case parameters. + err := s.SAdds(tt.key, tt.membersAdd...) + if !errors.Is(err, tt.wantErr) { + t.Errorf("SRems() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr == nil { + // positive verify + assert.True(t, s.exists(tt.key, tt.membersWant...)) + } + }) + } } -func TestSRems(t *testing.T) { - s, _ := initTestSetDb() - err := s.SRems("destination", "key1", "key2") - if err != nil { - t.Errorf("Expected nil error, got: %v", err) +func TestSRems(t *testing.T) { + // Creating a list of cases for testing. + tests := []struct { + name string + setup func(*SetStructure) + key string + membersRem []string + membersWant []string + wantErr error + }{ + { + name: "test empty key ", + setup: func(s *SetStructure) {}, + key: "", + membersRem: []string{"key1", "key2"}, + membersWant: []string{}, + wantErr: _const.ErrKeyIsEmpty, + }, + { + name: "test when key not found", + setup: func(s *SetStructure) {}, + key: "destination", + membersRem: []string{"key1", "key2"}, + membersWant: []string{}, + wantErr: _const.ErrKeyNotFound, + }, + { + name: "test when remove one key", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "key1", "key2") + }, + key: "destination", + membersRem: []string{"key1"}, + membersWant: []string{"key2"}, + wantErr: nil, + }, + { + name: "test remove all keys", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "key1", "key2") + }, + key: "destination", + membersRem: []string{"key1", "key1"}, + membersWant: []string{}, + wantErr: nil, + }, + { + name: "test remove FSets that don't exist", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "key1", "key2") + }, + key: "destination", + membersRem: []string{"key10", "key11"}, + membersWant: []string{}, + wantErr: ErrMemberNotFound, + }, } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + // Call the method with test case parameters. + err := s.SRems(tt.key, tt.membersRem...) + if !errors.Is(err, tt.wantErr) { + t.Errorf("SRems() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr == nil { + // positive verify + assert.True(t, s.exists(tt.key, tt.membersWant...)) + // verify removal + assert.False(t, s.exists(tt.key, tt.membersRem...)) + } + }) + } } -func TestSRem(t *testing.T) { - s, _ := initTestSetDb() - err := s.SRem("destination", "key1") - if err != nil { - t.Errorf("Expected nil error, got: %v", err) +func TestSRem(t *testing.T) { + // Creating a list of cases for testing. + tests := []struct { + name string + setup func(*SetStructure) + key string + membersRem []string + membersWant []string + wantErr error + }{ + { + name: "test when key not found", + setup: func(s *SetStructure) {}, + key: "destination", + membersRem: []string{"key1", "key2"}, + membersWant: []string{}, + wantErr: _const.ErrKeyNotFound, + }, + { + name: "test when remove one key", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "key1", "key2") + }, + key: "destination", + membersRem: []string{"key1"}, + membersWant: []string{"key2"}, + wantErr: nil, + }, + { + name: "test remove all keys", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "key3", "key4") + }, + key: "destination", + membersRem: []string{"key3", "key4"}, + membersWant: []string{}, + wantErr: nil, + }, } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + for _, s2 := range tt.membersRem { + // Call the method with test case parameters. + err := s.SRem(tt.key, s2) + if !errors.Is(err, tt.wantErr) { + t.Errorf("SRems() error = %v, wantErr %v", err, tt.wantErr) + } + } + if tt.wantErr == nil { + // positive verify + assert.True(t, s.exists(tt.key, tt.membersWant...)) + // verify removal + assert.False(t, s.exists(tt.key, tt.membersRem...)) + } + }) + } } -func TestSCard(t *testing.T) { - s, _ := initTestSetDb() - got, err := s.SCard("destination") - if err != nil { - t.Errorf("Expected nil error, got: %v", err) +func TestSCard(t *testing.T) { + // Creating a list of cases for testing. + tests := []struct { + name string + setup func(*SetStructure) + key string + want int + wantErr error + }{ + { + name: "test when key empty", + setup: func(s *SetStructure) {}, + key: "", + want: -1, + wantErr: _const.ErrKeyIsEmpty, + }, + { + name: "test two members", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "mem1", "mem2") + }, + key: "destination", + want: 2, + wantErr: nil, + }, + { + name: "test three members one duplicate", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "mem1", "mem2", "mem1") + }, + key: "destination", + want: 2, + wantErr: nil, + }, } - assert.NotNil(t, got) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + // Call the method with test case parameters. + got, err := s.SCard(tt.key) + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.want, got) + }) + } } + func TestSMembers(t *testing.T) { - s, _ := initTestSetDb() + // Creating a list of cases for testing. + tests := []struct { + name string + setup func(*SetStructure) + key string + want []string + wantErr error + }{ + { + name: "test when key empty", + setup: func(s *SetStructure) {}, + key: "", + want: nil, + wantErr: _const.ErrKeyIsEmpty, + }, + { + name: "test two members", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "mem1", "mem2") + }, + key: "destination", + want: []string{"mem1", "mem2"}, + wantErr: nil, + }, + { + name: "test three members one duplicate", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "mem1", "mem2", "mem1") + }, + key: "destination", + want: []string{"mem1", "mem2"}, + wantErr: nil, + }, + } - mem, err := s.SMembers("destination") - if err != nil { - t.Errorf("Expected nil error, got: %v", err) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + // Call the method with test case parameters. + got, err := s.SMembers(tt.key) + sort.Strings(got) + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.want, got) + }) } - assert.IsType(t, []string{}, mem) } + func TestSIsMember(t *testing.T) { - s, _ := initTestSetDb() + // Creating a list of cases for testing. + tests := []struct { + name string + setup func(*SetStructure) + key string + member string + want bool + wantErr error + }{ + { + name: "test when key empty", + setup: func(s *SetStructure) {}, + key: "", + want: false, + wantErr: _const.ErrKeyIsEmpty, + }, + { + name: "test two members, is member", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "mem1", "mem2") + }, + key: "destination", + member: "mem2", + want: true, + wantErr: nil, + }, + { + name: "test three members, not member", + setup: func(s *SetStructure) { + _ = s.SAdds("destination", "mem1", "mem2", "mem1") + }, + key: "destination", + member: "mem3", + want: false, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + // Call the method with test case parameters. + got, err := s.SIsMember(tt.key, tt.member) + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestSUnion(t *testing.T) { + // Creating a list of cases for testing. + tests := []struct { + name string + setup func(*SetStructure) + keys []string + want []string + wantErr error + }{ + { + name: "test when key empty", + setup: func(s *SetStructure) {}, + keys: []string{""}, + want: nil, + wantErr: _const.ErrKeyIsEmpty, + }, + { + name: "test when no keys", + setup: func(s *SetStructure) {}, + keys: []string{}, + want: nil, + wantErr: nil, + }, + { + name: "test two members", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem1", "mem2") + _ = s.SAdds("key2", "mem1", "mem2") + }, + keys: []string{"key1", "key2"}, + want: []string{"mem1", "mem2"}, + wantErr: nil, + }, + { + name: "test three members ", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem2", "mem1") + _ = s.SAdds("key2", "mem3") + }, + keys: []string{"key1", "key2"}, + want: []string{"mem1", "mem2", "mem3"}, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + got, err := s.SUnion(tt.keys...) + assert.Equal(t, tt.wantErr, err) + sort.Strings(got) + assert.True(t, reflect.DeepEqual(tt.want, got)) + + }) + } +} + +func TestSInter(t *testing.T) { + // Creating a list of cases for testing. + tests := []struct { + name string + setup func(*SetStructure) + keys []string + want []string + wantErr error + }{ + { + name: "test when key empty", + setup: func(s *SetStructure) {}, + keys: []string{""}, + want: nil, + wantErr: _const.ErrKeyIsEmpty, + }, + { + name: "test when no keys", + setup: func(s *SetStructure) {}, + keys: []string{}, + want: nil, + wantErr: nil, + }, + { + name: "test when keys not found", + setup: func(s *SetStructure) {}, + keys: []string{"notfound", "notfound2"}, + want: nil, + wantErr: _const.ErrKeyNotFound, + }, + { + name: "test when first keys found but second not found", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem1") + }, + keys: []string{"key1", "notfound"}, + want: nil, + wantErr: _const.ErrKeyNotFound, + }, + { + name: "test two members", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem1", "mem2") + _ = s.SAdds("key2", "mem1", "mem2") + }, + keys: []string{"key1", "key2"}, + want: []string{"mem1", "mem2"}, + wantErr: nil, + }, + { + name: "test three members with no intersect", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem2", "mem1") + _ = s.SAdds("key2", "mem3") + }, + keys: []string{"key1", "key2"}, + want: nil, + wantErr: nil, + }, + { + name: "test three keys with one intersect", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem2", "mem1", "mem3", "mem4") + _ = s.SAdds("key2", "mem3") + _ = s.SAdds("key3", "mem5", "mem3", "mem6") + }, + keys: []string{"key1", "key2", "key3"}, + want: []string{"mem3"}, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + got, err := s.SInter(tt.keys...) + assert.Equal(t, tt.wantErr, err) + sort.Strings(got) + assert.True(t, reflect.DeepEqual(tt.want, got)) + + }) + } +} - got, err := s.SIsMember("destination", "key1") - if err != nil { - t.Errorf("Expected nil error, got: %v", err) +func TestSDiff(t *testing.T) { + // Creating a list of cases for testing. + tests := []struct { + name string + setup func(*SetStructure) + keys []string + want []string + wantErr error + }{ + { + name: "test when key empty", + setup: func(s *SetStructure) {}, + keys: []string{""}, + want: nil, + wantErr: _const.ErrKeyIsEmpty, + }, + { + name: "test when no keys", + setup: func(s *SetStructure) {}, + keys: nil, + want: nil, + wantErr: nil, + }, + { + name: "test when keys not found", + setup: func(s *SetStructure) {}, + keys: []string{"notfound", "notfound2"}, + want: nil, + wantErr: _const.ErrKeyNotFound, + }, + { + name: "test when first keys found but second not found", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem1") + }, + keys: []string{"key1", "notfound"}, + want: nil, + wantErr: _const.ErrKeyNotFound, + }, + { + name: "test two members no diff", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem1", "mem2") + _ = s.SAdds("key2", "mem1", "mem2") + }, + keys: []string{"key1", "key2"}, + want: nil, + wantErr: nil, + }, + { + name: "test three members with two diffs", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem2", "mem1") + _ = s.SAdds("key2", "mem3") + }, + keys: []string{"key1", "key2"}, + want: []string{"mem2", "mem1"}, + wantErr: nil, + }, + { + name: "test three keys with five three", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem2", "mem1", "mem3", "mem4") + _ = s.SAdds("key2", "mem3") + _ = s.SAdds("key3", "mem5", "mem3", "mem6") + }, + keys: []string{"key1", "key2", "key3"}, + want: []string{"mem1", "mem2", "mem4"}, + wantErr: nil, + }, + { + name: "test three keys with no diffs", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem2", "mem1", "mem3", "mem4") + _ = s.SAdds("key2", "mem3") + _ = s.SAdds("key3", "mem5", "mem3", "mem6") + }, + keys: []string{"key2", "key1", "key3"}, // the first key here will be `key2` + want: nil, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + got, err := s.SDiff(tt.keys...) + assert.Equal(t, tt.wantErr, err) + sort.Strings(got) + sort.Strings(tt.want) + assert.EqualValues(t, tt.want, got) + + }) + } +} + +func TestSInterStore(t *testing.T) { + // Creating a list of cases for testing. + tests := []struct { + name string + setup func(*SetStructure) + keys []string + want []string + wantErr error + }{ + { + name: "test when key empty", + setup: func(s *SetStructure) {}, + keys: []string{""}, + want: nil, + wantErr: _const.ErrKeyIsEmpty, + }, + { + name: "test when no keys", + setup: func(s *SetStructure) {}, + keys: nil, + want: nil, + wantErr: nil, + }, + { + name: "test when keys not found", + setup: func(s *SetStructure) {}, + keys: []string{"notfound", "notfound2"}, + want: nil, + wantErr: _const.ErrKeyNotFound, + }, + { + name: "test when first keys found but second not found", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem1") + }, + keys: []string{"key1", "notfound"}, + want: nil, + wantErr: _const.ErrKeyNotFound, + }, + { + name: "test two members no two", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem1", "mem2") + _ = s.SAdds("key2", "mem1", "mem2") + }, + keys: []string{"key1", "key2"}, + want: []string{"mem1", "mem2"}, + wantErr: nil, + }, + { + name: "test three members with no intersect", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem2", "mem1") + _ = s.SAdds("key2", "mem3") + }, + keys: []string{"key1", "key2"}, + want: nil, + wantErr: nil, + }, + { + name: "test three keys with one intersect", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem2", "mem1", "mem3", "mem4") + _ = s.SAdds("key2", "mem3") + _ = s.SAdds("key3", "mem5", "mem3", "mem6") + }, + keys: []string{"key1", "key2", "key3"}, + want: []string{"mem3"}, + wantErr: nil, + }, + { + name: "test three keys with two intersect", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem2", "mem5", "mem3", "mem4") + _ = s.SAdds("key2", "mem3", "mem5") + _ = s.SAdds("key3", "mem5", "mem3", "mem6") + }, + keys: []string{"key2", "key1", "key3"}, // the first key here will be `key2` + want: []string{"mem3", "mem5"}, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + destKey := "dest" + err := s.SInterStore(destKey, tt.keys...) + assert.Equal(t, tt.wantErr, err) + got, _ := s.SUnion(destKey) // get values stored in `destKey` + sort.Strings(tt.want) + sort.Strings(got) + assert.EqualValues(t, tt.want, got) + + }) } - assert.False(t, got) +} +func TestSUnionStore(t *testing.T) { + // Creating a list of cases for testing. + tests := []struct { + name string + setup func(*SetStructure) + keys []string + want []string + wantErr error + }{ + { + name: "test when key empty", + setup: func(s *SetStructure) {}, + keys: []string{""}, + want: nil, + wantErr: _const.ErrKeyIsEmpty, + }, + { + name: "test when no keys", + setup: func(s *SetStructure) {}, + keys: nil, + want: nil, + wantErr: nil, + }, + { + name: "test when keys not found", + setup: func(s *SetStructure) {}, + keys: []string{"notfound", "notfound2"}, + want: nil, + wantErr: _const.ErrKeyNotFound, + }, + { + name: "test when first keys found but second not found", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem1") + }, + keys: []string{"key1", "notfound"}, + want: nil, + wantErr: _const.ErrKeyNotFound, + }, + { + name: "test two members no diff", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem1", "mem2") + _ = s.SAdds("key2", "mem1", "mem2") + }, + keys: []string{"key1", "key2"}, + want: []string{"mem1", "mem2"}, + wantErr: nil, + }, + { + name: "test three members with two diffs", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem2", "mem1") + _ = s.SAdds("key2", "mem3") + }, + keys: []string{"key1", "key2"}, + want: []string{"mem2", "mem1", "mem3"}, + wantErr: nil, + }, + { + name: "test three keys with five three", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem2") + _ = s.SAdds("key2", "mem3") + _ = s.SAdds("key3", "mem5", "mem3", "mem6") + }, + keys: []string{"key1", "key2", "key3"}, + want: []string{"mem2", "mem3", "mem5", "mem6"}, + wantErr: nil, + }, + { + name: "test three keys with no diffs", + setup: func(s *SetStructure) { + _ = s.SAdds("key1", "mem2", "mem1", "mem3", "mem4") + _ = s.SAdds("key2", "mem3") + _ = s.SAdds("key3", "mem5", "mem3", "mem6") + }, + keys: []string{"key2", "key1", "key3"}, // the first key here will be `key2` + want: []string{"mem1", "mem2", "mem3", "mem4", "mem5", "mem6"}, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + destKey := "dest" + err := s.SUnionStore(destKey, tt.keys...) + assert.Equal(t, tt.wantErr, err) + got, _ := s.SUnion(destKey) // get values stored in `destKey` + sort.Strings(tt.want) + sort.Strings(got) + assert.EqualValues(t, tt.want, got) + + }) + } } func TestNewSetStructure(t *testing.T) { @@ -98,54 +884,176 @@ func TestNewSetStructure(t *testing.T) { assert.IsType(t, &engine.DB{}, setDB.db) } -func TestSUnion(t *testing.T) { - s, _ := initTestSetDb() - keys := []string{"key1", "key2"} - - _, err := s.SUnion(keys...) - if err != nil { - t.Errorf("Expected nil error, got: %v", err) +func TestSetStructure_exists(t *testing.T) { + tests := []struct { + name string + setup func(*SetStructure) + key string + members []string + found bool + }{ + { + name: "test when empty Set", + setup: func(s *SetStructure) {}, + key: "testKey", + members: []string{"key1", "key2"}, + found: false, + }, + { + name: "test Set with no members found", + setup: func(s *SetStructure) { + _ = s.SAdds("testKey", "non1", "non2") + }, + key: "testKey", + members: []string{"key1", "key2"}, + found: false, + }, + { + name: "test Set with partial members found", + setup: func(s *SetStructure) { + _ = s.SAdds("testKey", "key1", "non2") + }, + key: "testKey", + members: []string{"key1", "key2"}, + found: false, + }, + { + name: "test Set with all members found", + setup: func(s *SetStructure) { + _ = s.SAdds("testKey", "key1", "non2", "key2") + }, + key: "testKey", + members: []string{"key1", "key2"}, + found: true, + }, + { + name: "test Set key empty", + setup: func(s *SetStructure) { + _ = s.SAdds("testKey", "key1", "non2", "key2") + }, + key: "", + members: []string{"key1", "key2"}, + found: false, + }, } - -} - -func TestSInter(t *testing.T) { - s, _ := initTestSetDb() - keys := []string{"key1", "key2"} - - _, err := s.SInter(keys...) - if err != nil { - t.Errorf("Expected nil error, got: %v", err) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, _ := initTestSetDb() + tt.setup(s) + if tt.found { + assert.True(t, s.exists(tt.key, tt.members...)) + } else { + assert.False(t, s.exists(tt.key, tt.members...)) + } + }) } - } -func TestSDiff(t *testing.T) { - s, _ := initTestSetDb() - - _, err := s.SDiff("key1", "key2") - if err != nil { - t.Errorf("Expected nil error, got: %v", err) +func TestFSetsExists(t *testing.T) { + tests := []struct { + name string + setup func(sets *FSets) + key string + members []string + found bool + }{ + { + name: "test when empty Set", + setup: func(s *FSets) {}, + key: "testKey", + members: []string{"key1", "key2"}, + found: false, + }, + { + name: "test Set with no members found", + setup: func(fs *FSets) { + fs.add("non1", "non2") + }, + key: "testKey", + members: []string{"key1", "key2"}, + found: false, + }, + { + name: "test Set with partial members found", + setup: func(fs *FSets) { + fs.add("key1", "non2") + }, + key: "testKey", + members: []string{"key1", "key2"}, + found: false, + }, + { + name: "test Set with all members found", + setup: func(fs *FSets) { + fs.add("key1", "non2", "key2") + }, + key: "testKey", + members: []string{"key1", "key2"}, + found: true, + }, } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testFSets := &FSets{} + tt.setup(testFSets) -} - -func TestSUnionStore(t *testing.T) { - s, _ := initTestSetDb() - - err := s.SUnionStore("destination", "key1", "key2") - if err != nil { - t.Errorf("Expected nil error, got: %v", err) + if tt.found { + assert.True(t, testFSets.exists(tt.members...)) + } else { + assert.False(t, testFSets.exists(tt.members...)) + } + }) } - } -func TestSInterStore(t *testing.T) { - s, _ := initTestSetDb() - - err := s.SInterStore("destination", "key1", "key2") - if err != nil { - t.Errorf("Expected nil error, got: %v", err) +func TestFSets_remove(t *testing.T) { + tests := []struct { + name string + setup *FSets + members []string + want []string + wantErrors error + }{ + { + name: "test when empty Set", + setup: &FSets{}, + members: []string{"key1", "key2"}, + want: []string{}, + wantErrors: ErrMemberNotFound, + }, + { + name: "test Set with wrong members ", + setup: &FSets{"non1": struct{}{}, "non2": struct{}{}}, + want: []string{"non1", "non2"}, + members: []string{"key1", "key2"}, + wantErrors: ErrMemberNotFound, + }, + { + name: "test Set with partial members found", // this should not delete the found member neither + setup: &FSets{"key1": struct{}{}, "non2": struct{}{}}, + want: []string{"key1", "non2"}, + members: []string{"key1", "key2"}, + wantErrors: ErrMemberNotFound, + }, + { + name: "test Set with all members found", + setup: &FSets{"key1": struct{}{}, "non2": struct{}{}, "key2": struct{}{}}, + want: []string{"non2"}, + members: []string{"key1", "key2"}, + }, + { + name: "test Set not initialized", + want: []string{}, + members: []string{"key1", "key2"}, + wantErrors: ErrSetNotInitialized, + }, } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.setup.remove(tt.members...) + assert.Equal(t, tt.wantErrors, err) + assert.True(t, tt.setup.exists(tt.want...)) + }) + } }