diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e76bd6c..204b0c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,17 +4,16 @@ jobs: test: strategy: matrix: - go-version: [1.20.1, 1.21.1] + go-version: ['1.21', '1.22', '1.23', '1.24'] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - stable: false - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Test run: | go test -v -race ./... diff --git a/set.go b/set.go index 04c478b..9bde7a0 100644 --- a/set.go +++ b/set.go @@ -35,171 +35,6 @@ SOFTWARE. // that can enforce mutual exclusion through other means. package mapset -// Set is the primary interface provided by the mapset package. It -// represents an unordered set of data and a large number of -// operations that can be applied to that set. -type Set[T comparable] interface { - // Add adds an element to the set. Returns whether - // the item was added. - Add(val T) bool - - // Append multiple elements to the set. Returns - // the number of elements added. - Append(val ...T) int - - // Cardinality returns the number of elements in the set. - Cardinality() int - - // Clear removes all elements from the set, leaving - // the empty set. - Clear() - - // Clone returns a clone of the set using the same - // implementation, duplicating all keys. - Clone() Set[T] - - // Contains returns whether the given items - // are all in the set. - Contains(val ...T) bool - - // ContainsOne returns whether the given item - // is in the set. - // - // Contains may cause the argument to escape to the heap. - // See: https://github.com/deckarep/golang-set/issues/118 - ContainsOne(val T) bool - - // ContainsAny returns whether at least one of the - // given items are in the set. - ContainsAny(val ...T) bool - - // ContainsAnyElement returns whether at least one of the - // given element are in the set. - ContainsAnyElement(other Set[T]) bool - - // Difference returns the difference between this set - // and other. The returned set will contain - // all elements of this set that are not also - // elements of other. - // - // Note that the argument to Difference - // must be of the same type as the receiver - // of the method. Otherwise, Difference will - // panic. - Difference(other Set[T]) Set[T] - - // Equal determines if two sets are equal to each - // other. If they have the same cardinality - // and contain the same elements, they are - // considered equal. The order in which - // the elements were added is irrelevant. - // - // Note that the argument to Equal must be - // of the same type as the receiver of the - // method. Otherwise, Equal will panic. - Equal(other Set[T]) bool - - // Intersect returns a new set containing only the elements - // that exist only in both sets. - // - // Note that the argument to Intersect - // must be of the same type as the receiver - // of the method. Otherwise, Intersect will - // panic. - Intersect(other Set[T]) Set[T] - - // IsEmpty determines if there are elements in the set. - IsEmpty() bool - - // IsProperSubset determines if every element in this set is in - // the other set but the two sets are not equal. - // - // Note that the argument to IsProperSubset - // must be of the same type as the receiver - // of the method. Otherwise, IsProperSubset - // will panic. - IsProperSubset(other Set[T]) bool - - // IsProperSuperset determines if every element in the other set - // is in this set but the two sets are not - // equal. - // - // Note that the argument to IsSuperset - // must be of the same type as the receiver - // of the method. Otherwise, IsSuperset will - // panic. - IsProperSuperset(other Set[T]) bool - - // IsSubset determines if every element in this set is in - // the other set. - // - // Note that the argument to IsSubset - // must be of the same type as the receiver - // of the method. Otherwise, IsSubset will - // panic. - IsSubset(other Set[T]) bool - - // IsSuperset determines if every element in the other set - // is in this set. - // - // Note that the argument to IsSuperset - // must be of the same type as the receiver - // of the method. Otherwise, IsSuperset will - // panic. - IsSuperset(other Set[T]) bool - - // Each iterates over elements and executes the passed func against each element. - // If passed func returns true, stop iteration at the time. - Each(func(T) bool) - - // Iter returns a channel of elements that you can - // range over. - Iter() <-chan T - - // Iterator returns an Iterator object that you can - // use to range over the set. - Iterator() *Iterator[T] - - // Remove removes a single element from the set. - Remove(i T) - - // RemoveAll removes multiple elements from the set. - RemoveAll(i ...T) - - // String provides a convenient string representation - // of the current state of the set. - String() string - - // SymmetricDifference returns a new set with all elements which are - // in either this set or the other set but not in both. - // - // Note that the argument to SymmetricDifference - // must be of the same type as the receiver - // of the method. Otherwise, SymmetricDifference - // will panic. - SymmetricDifference(other Set[T]) Set[T] - - // Union returns a new set with all elements in both sets. - // - // Note that the argument to Union must be of the - // same type as the receiver of the method. - // Otherwise, Union will panic. - Union(other Set[T]) Set[T] - - // Pop removes and returns an arbitrary item from the set. - Pop() (T, bool) - - // ToSlice returns the members of the set as a slice. - ToSlice() []T - - // MarshalJSON will marshal the set into a JSON-based representation. - MarshalJSON() ([]byte, error) - - // UnmarshalJSON will unmarshal a JSON-based byte slice into a full Set datastructure. - // For this to work, set subtypes must implemented the Marshal/Unmarshal interface. - UnmarshalJSON(b []byte) error -} - // NewSet creates and returns a new set with the given elements. // Operations on the resulting set are thread-safe. func NewSet[T comparable](vals ...T) Set[T] { diff --git a/set_go122.go b/set_go122.go new file mode 100644 index 0000000..d01558a --- /dev/null +++ b/set_go122.go @@ -0,0 +1,224 @@ +//go:build !go1.23 +// +build !go1.23 + +package mapset + +// Set is the primary interface provided by the mapset package. It +// represents an unordered set of data and a large number of +// operations that can be applied to that set. +type Set[T comparable] interface { + // Add adds an element to the set. Returns whether + // the item was added. + Add(val T) bool + + // Append multiple elements to the set. Returns + // the number of elements added. + Append(val ...T) int + + // Cardinality returns the number of elements in the set. + Cardinality() int + + // Clear removes all elements from the set, leaving + // the empty set. + Clear() + + // Clone returns a clone of the set using the same + // implementation, duplicating all keys. + Clone() Set[T] + + // Contains returns whether the given items + // are all in the set. + Contains(val ...T) bool + + // ContainsOne returns whether the given item + // is in the set. + // + // Contains may cause the argument to escape to the heap. + // See: https://github.com/deckarep/golang-set/issues/118 + ContainsOne(val T) bool + + // ContainsAny returns whether at least one of the + // given items are in the set. + ContainsAny(val ...T) bool + + // ContainsAnyElement returns whether at least one of the + // given element are in the set. + ContainsAnyElement(other Set[T]) bool + + // Difference returns the difference between this set + // and other. The returned set will contain + // all elements of this set that are not also + // elements of other. + // + // Note that the argument to Difference + // must be of the same type as the receiver + // of the method. Otherwise, Difference will + // panic. + Difference(other Set[T]) Set[T] + + // Equal determines if two sets are equal to each + // other. If they have the same cardinality + // and contain the same elements, they are + // considered equal. The order in which + // the elements were added is irrelevant. + // + // Note that the argument to Equal must be + // of the same type as the receiver of the + // method. Otherwise, Equal will panic. + Equal(other Set[T]) bool + + // Intersect returns a new set containing only the elements + // that exist only in both sets. + // + // Note that the argument to Intersect + // must be of the same type as the receiver + // of the method. Otherwise, Intersect will + // panic. + Intersect(other Set[T]) Set[T] + + // IsEmpty determines if there are elements in the set. + IsEmpty() bool + + // IsProperSubset determines if every element in this set is in + // the other set but the two sets are not equal. + // + // Note that the argument to IsProperSubset + // must be of the same type as the receiver + // of the method. Otherwise, IsProperSubset + // will panic. + IsProperSubset(other Set[T]) bool + + // IsProperSuperset determines if every element in the other set + // is in this set but the two sets are not + // equal. + // + // Note that the argument to IsSuperset + // must be of the same type as the receiver + // of the method. Otherwise, IsSuperset will + // panic. + IsProperSuperset(other Set[T]) bool + + // IsSubset determines if every element in this set is in + // the other set. + // + // Note that the argument to IsSubset + // must be of the same type as the receiver + // of the method. Otherwise, IsSubset will + // panic. + IsSubset(other Set[T]) bool + + // IsSuperset determines if every element in the other set + // is in this set. + // + // Note that the argument to IsSuperset + // must be of the same type as the receiver + // of the method. Otherwise, IsSuperset will + // panic. + IsSuperset(other Set[T]) bool + + // Each iterates over elements and executes the passed func against each element. + // If passed func returns true, stop iteration at the time. + Each(func(T) bool) + + // Elements returns a go1.23+ iterator function which acts as described here: https://go.dev/ref/spec#For_range and + // here: https://pkg.go.dev/iter#hdr-Naming_Conventions + // + // Upgrade to go1.23 to use this returned function in a range loop. Calling the returned method under versions prior + // to go1.23 will panic. + Elements() func(T) bool + + // Iter returns a channel of elements that you can + // range over. + Iter() <-chan T + + // Iterator returns an Iterator object that you can + // use to range over the set. + Iterator() *Iterator[T] + + // Remove removes a single element from the set. + Remove(i T) + + // RemoveAll removes multiple elements from the set. + RemoveAll(i ...T) + + // String provides a convenient string representation + // of the current state of the set. + String() string + + // SymmetricDifference returns a new set with all elements which are + // in either this set or the other set but not in both. + // + // Note that the argument to SymmetricDifference + // must be of the same type as the receiver + // of the method. Otherwise, SymmetricDifference + // will panic. + SymmetricDifference(other Set[T]) Set[T] + + // Union returns a new set with all elements in both sets. + // + // Note that the argument to Union must be of the + // same type as the receiver of the method. + // Otherwise, Union will panic. + Union(other Set[T]) Set[T] + + // Pop removes and returns an arbitrary item from the set. + Pop() (T, bool) + + // ToSlice returns the members of the set as a slice. + ToSlice() []T + + // MarshalJSON will marshal the set into a JSON-based representation. + MarshalJSON() ([]byte, error) + + // UnmarshalJSON will unmarshal a JSON-based byte slice into a full Set datastructure. + // For this to work, set subtypes must implemented the Marshal/Unmarshal interface. + UnmarshalJSON(b []byte) error +} + +func (t *threadSafeSet[T]) Elements() func(T) bool { + return func(T) bool { + panic("Upgrade to go1.23 to use this function in a range loop") + } +} + +func (s *threadUnsafeSet[T]) Elements() func(T) bool { + return func(T) bool { + panic("Upgrade to go1.23 to use this function in a range loop") + } +} + +// NewSetFromSeq creates a new set from a go1.23+ iter.Seq +// This function will panic if called under go1.22 or earlier. +func NewSetFromSeq[T comparable](iter func(yield func(T) bool)) Set[T] { + panic("Upgrade to go1.23 to use this function") +} + +// NewSetFromSeq2Keys creates a new set from a go1.23+ iter.Seq2's Keys +// This function will panic if called under go1.22 or earlier. +func NewSetFromSeq2Keys[K comparable, V any](iter func(yield func(K, V) bool)) Set[K] { + panic("Upgrade to go1.23 to use this function") +} + +// NewSetFromSeq2Values creates a new set from a go1.23+ iter.Seq2's Values +// This function will panic if called under go1.22 or earlier. +func NewSetFromSeq2Values[K any, V comparable](iter func(yield func(K, V) bool)) Set[V] { + panic("Upgrade to go1.23 to use this function") +} + +// NewThreadUnsafeSetFromSeq creates a new set from a go1.23+ iter,Seq +// This function will panic if called under go1.22 or earlier. +func NewThreadUnsafeSetFromSeq[T comparable](iter func(func(T) bool)) Set[T] { + panic("Upgrade to go1.23 to use this function") +} + +// NewSetFromSeq2Keys creates a new set from a go1.23+ iter.Seq2 Keys +// This function will panic if called under go1.22 or earlier. +func NewThreadUnsafeSetFromSeq2Keys[K comparable, V any](iter func(yield func(K, V) bool)) Set[K] { + panic("Upgrade to go1.23 to use this function") +} + +// NewSetFromSeq2Values creates a new set from a go1.23+ iter.Seq2's Values +// This function will panic if called under go1.22 or earlier. +func NewThreadUnsafeSetFromSeq2Values[K any, V comparable](iter func(yield func(K, V) bool)) Set[V] { + panic("Upgrade to go1.23 to use this function") +} diff --git a/set_go122_test.go b/set_go122_test.go new file mode 100644 index 0000000..1e4457f --- /dev/null +++ b/set_go122_test.go @@ -0,0 +1,147 @@ +//go:build !go1.23 +// +build !go1.23 + +package mapset + +import "testing" + +func TestThreadUnsafeSetElements(t *testing.T) { + s := NewThreadUnsafeSet(1, 2, 3) + + var gotPanic bool + func() { + defer func() { + if r := recover(); r != nil { + gotPanic = true + } + }() + + s.Elements()(1) + }() + + if !gotPanic { + t.Errorf("Elements should panic if called under go1.22") + } +} + +func TestNewThreadUnsafeSetFromSeq(t *testing.T) { + var gotPanic bool + func() { + defer func() { + if r := recover(); r != nil { + gotPanic = true + } + }() + + NewThreadUnsafeSetFromSeq[int](func(func(int) bool) {}) + }() + + if !gotPanic { + t.Errorf("Elements should panic if called under go1.22") + } +} + +func TestNewThreadUnsafeSetFromSeq2Keys(t *testing.T) { + var gotPanic bool + func() { + defer func() { + if r := recover(); r != nil { + gotPanic = true + } + }() + + NewThreadUnsafeSetFromSeq2Keys[int, string](func(func(int, string) bool) {}) + }() + + if !gotPanic { + t.Errorf("Elements should panic if called under go1.22") + } +} + +func TestNewThreadUnsafeSetFromSeq2Values(t *testing.T) { + var gotPanic bool + func() { + defer func() { + if r := recover(); r != nil { + gotPanic = true + } + }() + + NewThreadUnsafeSetFromSeq2Values[int, string](func(func(int, string) bool) {}) + }() + + if !gotPanic { + t.Errorf("Elements should panic if called under go1.22") + } +} + +func TestThreadsafeSetElements(t *testing.T) { + s := NewSet(1, 2, 3) + + var gotPanic bool + func() { + defer func() { + if r := recover(); r != nil { + gotPanic = true + } + }() + + s.Elements()(1) + }() + + if !gotPanic { + t.Errorf("Elements should panic if called under go1.22") + } +} + +func TestNewSetFromSeq(t *testing.T) { + var gotPanic bool + func() { + defer func() { + if r := recover(); r != nil { + gotPanic = true + } + }() + + NewSetFromSeq[int](func(func(int) bool) {}) + + }() + + if !gotPanic { + t.Errorf("Elements should panic if called under go1.22") + } +} + +func TestNewSetFromSeq2Keys(t *testing.T) { + var gotPanic bool + func() { + defer func() { + if r := recover(); r != nil { + gotPanic = true + } + }() + + NewSetFromSeq2Keys[int, string](func(func(int, string) bool) {}) + }() + + if !gotPanic { + t.Errorf("Elements should panic if called under go1.22") + } +} + +func TestNewSetFromSeq2Values(t *testing.T) { + var gotPanic bool + func() { + defer func() { + if r := recover(); r != nil { + gotPanic = true + } + }() + + NewSetFromSeq2Values[int, string](func(func(int, string) bool) {}) + }() + + if !gotPanic { + t.Errorf("Elements should panic if called under go1.22") + } +} diff --git a/set_go123.go b/set_go123.go new file mode 100644 index 0000000..a880356 --- /dev/null +++ b/set_go123.go @@ -0,0 +1,252 @@ +//go:build go1.23 +// +build go1.23 + +package mapset + +import "iter" + +// Set is the primary interface provided by the mapset package. It +// represents an unordered set of data and a large number of +// operations that can be applied to that set. +type Set[T comparable] interface { + // Add adds an element to the set. Returns whether + // the item was added. + Add(val T) bool + + // Append multiple elements to the set. Returns + // the number of elements added. + Append(val ...T) int + + // Cardinality returns the number of elements in the set. + Cardinality() int + + // Clear removes all elements from the set, leaving + // the empty set. + Clear() + + // Clone returns a clone of the set using the same + // implementation, duplicating all keys. + Clone() Set[T] + + // Contains returns whether the given items + // are all in the set. + Contains(val ...T) bool + + // ContainsOne returns whether the given item + // is in the set. + // + // Contains may cause the argument to escape to the heap. + // See: https://github.com/deckarep/golang-set/issues/118 + ContainsOne(val T) bool + + // ContainsAny returns whether at least one of the + // given items are in the set. + ContainsAny(val ...T) bool + + // ContainsAnyElement returns whether at least one of the + // given element are in the set. + ContainsAnyElement(other Set[T]) bool + + // Difference returns the difference between this set + // and other. The returned set will contain + // all elements of this set that are not also + // elements of other. + // + // Note that the argument to Difference + // must be of the same type as the receiver + // of the method. Otherwise, Difference will + // panic. + Difference(other Set[T]) Set[T] + + // Equal determines if two sets are equal to each + // other. If they have the same cardinality + // and contain the same elements, they are + // considered equal. The order in which + // the elements were added is irrelevant. + // + // Note that the argument to Equal must be + // of the same type as the receiver of the + // method. Otherwise, Equal will panic. + Equal(other Set[T]) bool + + // Intersect returns a new set containing only the elements + // that exist only in both sets. + // + // Note that the argument to Intersect + // must be of the same type as the receiver + // of the method. Otherwise, Intersect will + // panic. + Intersect(other Set[T]) Set[T] + + // IsEmpty determines if there are elements in the set. + IsEmpty() bool + + // IsProperSubset determines if every element in this set is in + // the other set but the two sets are not equal. + // + // Note that the argument to IsProperSubset + // must be of the same type as the receiver + // of the method. Otherwise, IsProperSubset + // will panic. + IsProperSubset(other Set[T]) bool + + // IsProperSuperset determines if every element in the other set + // is in this set but the two sets are not + // equal. + // + // Note that the argument to IsSuperset + // must be of the same type as the receiver + // of the method. Otherwise, IsSuperset will + // panic. + IsProperSuperset(other Set[T]) bool + + // IsSubset determines if every element in this set is in + // the other set. + // + // Note that the argument to IsSubset + // must be of the same type as the receiver + // of the method. Otherwise, IsSubset will + // panic. + IsSubset(other Set[T]) bool + + // IsSuperset determines if every element in the other set + // is in this set. + // + // Note that the argument to IsSuperset + // must be of the same type as the receiver + // of the method. Otherwise, IsSuperset will + // panic. + IsSuperset(other Set[T]) bool + + // Each iterates over elements and executes the passed func against each element. + // If passed func returns true, stop iteration at the time. + // + // Deprecated: Use Elements instead. + Each(func(T) bool) + + // Elements returns a go1.23+ iterator function which acts as described here: https://go.dev/ref/spec#For_range and + // here: https://pkg.go.dev/iter#hdr-Naming_Conventions. Modifications to the set during iteration result in + // undefined behavior. + Elements() iter.Seq[T] + + // Iter returns a channel of elements that you can + // range over. + // + // Deprecated: Use Elements instead. + Iter() <-chan T + + // Iterator returns an Iterator object that you can + // use to range over the set. + // + // Deprecated: Use Elements instead. + Iterator() *Iterator[T] + + // Remove removes a single element from the set. + Remove(i T) + + // RemoveAll removes multiple elements from the set. + RemoveAll(i ...T) + + // String provides a convenient string representation + // of the current state of the set. + String() string + + // SymmetricDifference returns a new set with all elements which are + // in either this set or the other set but not in both. + // + // Note that the argument to SymmetricDifference + // must be of the same type as the receiver + // of the method. Otherwise, SymmetricDifference + // will panic. + SymmetricDifference(other Set[T]) Set[T] + + // Union returns a new set with all elements in both sets. + // + // Note that the argument to Union must be of the + // same type as the receiver of the method. + // Otherwise, Union will panic. + Union(other Set[T]) Set[T] + + // Pop removes and returns an arbitrary item from the set. + Pop() (T, bool) + + // ToSlice returns the members of the set as a slice. + ToSlice() []T + + // MarshalJSON will marshal the set into a JSON-based representation. + MarshalJSON() ([]byte, error) + + // UnmarshalJSON will unmarshal a JSON-based byte slice into a full Set datastructure. + // For this to work, set subtypes must implemented the Marshal/Unmarshal interface. + UnmarshalJSON(b []byte) error +} + +func (t *threadSafeSet[T]) Elements() iter.Seq[T] { + return func(yield func(T) bool) { + t.Each(func(v T) bool { + return !yield(v) + }) + } +} + +func (s *threadUnsafeSet[T]) Elements() iter.Seq[T] { + return func(yield func(T) bool) { + s.Each(func(t T) bool { + return !yield(t) + }) + } +} + +// NewSetFromSeq creates a new set from a go1.23+ iter.Seq +func NewSetFromSeq[T comparable](iter iter.Seq[T]) Set[T] { + s := NewSet[T]() + for k := range iter { + s.Add(k) + } + return s +} + +// NewSetFromSeq2Keys creates a new set from a go1.23+ iter.Seq2's Keys +func NewSetFromSeq2Keys[K comparable, V any](iter iter.Seq2[K, V]) Set[K] { + s := NewSet[K]() + for k := range iter { + s.Add(k) + } + return s +} + +// NewSetFromSeq2Values creates a new set from a go1.23+ iter.Seq2's Values +func NewSetFromSeq2Values[K any, V comparable](iter iter.Seq2[K, V]) Set[V] { + s := NewSet[V]() + for _, v := range iter { + s.Add(v) + } + return s +} + +// NewSetFromSeq creates a new set from a go1.23+ iterator. +func NewThreadUnsafeSetFromSeq[T comparable](iter iter.Seq[T]) Set[T] { + s := NewThreadUnsafeSet[T]() + for k := range iter { + s.Add(k) + } + return s +} + +// NewThreadUnsafeSetFromSeq2Keys creates a new set from a go1.23+ iter.Seq2's Keys +func NewThreadUnsafeSetFromSeq2Keys[K comparable, V any](iter iter.Seq2[K, V]) Set[K] { + s := NewThreadUnsafeSet[K]() + for k := range iter { + s.Add(k) + } + return s +} + +// NewThreadUnsafeSetFromSeq2Values creates a new set from a go1.23+ iter.Seq2's Values +func NewThreadUnsafeSetFromSeq2Values[K any, V comparable](iter iter.Seq2[K, V]) Set[V] { + s := NewThreadUnsafeSet[V]() + for _, v := range iter { + s.Add(v) + } + return s +} diff --git a/set_go123_test.go b/set_go123_test.go new file mode 100644 index 0000000..c2d8863 --- /dev/null +++ b/set_go123_test.go @@ -0,0 +1,115 @@ +//go:build go1.23 +// +build go1.23 + +package mapset + +import ( + "slices" + "testing" +) + +func TestThreadUnsageSetElements(t *testing.T) { + s := NewThreadUnsafeSet(1, 2, 3) + got := NewThreadUnsafeSet[int]() + + var gotPanic bool + func() { + defer func() { + if r := recover(); r != nil { + gotPanic = true + } + }() + + for i := range s.Elements() { + got.Add(i) + } + }() + + if gotPanic { + t.Errorf("Elements should NOT panic if called under go1.23+") + } + + if !s.Equal(got) { + t.Errorf("Expected no difference, got: %v", s.Difference(got)) + } +} + +func TestNewThreadUnsafeSetFromSeq(t *testing.T) { + names := []string{"Alice", "Bob", "Vera", "Bob"} + got := NewThreadUnsafeSetFromSeq(slices.Values(names)) + expect := NewThreadUnsafeSet(names...) + if !got.Equal(expect) { + t.Errorf("Expected %v, got %v", expect, got) + } +} + +func TestNewThreadUnsafeSetFromSeq2Keys(t *testing.T) { + names := []string{"Alice", "Bob", "Vera", "Bob"} + got := NewThreadUnsafeSetFromSeq2Keys(slices.All(names)) + expect := NewThreadUnsafeSet(0, 1, 2, 3) + if !got.Equal(expect) { + t.Errorf("Expected %v, got %v", expect, got) + } +} + +func TestNewThreadUnsafeSetFromSeqValues(t *testing.T) { + names := []string{"Alice", "Bob", "Vera", "Bob"} + got := NewThreadUnsafeSetFromSeq2Values(slices.All(names)) + expect := NewThreadUnsafeSet(names...) + if !got.Equal(expect) { + t.Errorf("Expected %v, got %v", expect, got) + } +} + +func TestThreadsafeSetElements(t *testing.T) { + s := NewSet(1, 2, 3) + got := NewSet[int]() + + var gotPanic bool + func() { + defer func() { + if r := recover(); r != nil { + gotPanic = true + } + }() + + for i := range s.Elements() { + got.Add(i) + } + }() + + if gotPanic { + t.Errorf("Elements should NOT panic if called under go1.23+") + } + + if !s.Equal(got) { + t.Errorf("Expected no difference, got: %v", s.Difference(got)) + } +} + +func TestNewSetFromSeq(t *testing.T) { + names := []string{"Alice", "Bob", "Vera", "Bob"} + got := NewSetFromSeq(slices.Values(names)) + expect := NewSet(names...) + if !got.Equal(expect) { + t.Errorf("Expected %v, got %v", expect, got) + } +} + +func TestNewSetFromSeq2Keys(t *testing.T) { + names := []string{"Alice", "Bob", "Vera", "Bob"} + got := NewSetFromSeq2Keys(slices.All(names)) + expect := NewSet(0, 1, 2, 3) + if !got.Equal(expect) { + t.Errorf("Expected %v, got %v", expect, got) + } +} + +func TestNewSetFromSeqValues(t *testing.T) { + names := []string{"Alice", "Bob", "Vera", "Bob"} + got := NewSetFromSeq2Values(slices.All(names)) + expect := NewSet(names...) + if !got.Equal(expect) { + t.Errorf("Expected %v, got %v", expect, got) + } +}