Skip to content

Commit 78a29df

Browse files
authored
Merge pull request #192 from lorentey/set-API-checker
[test] Check baseline API expectations for set-like types
2 parents 674e5b6 + 4604c1b commit 78a29df

File tree

9 files changed

+173
-19
lines changed

9 files changed

+173
-19
lines changed

Sources/BitCollections/BitSet/BitSet+Extras.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,24 @@ extension BitSet {
143143
}
144144

145145
extension BitSet {
146+
/// Removes and returns the element at the specified position.
147+
///
148+
/// - Parameter i: The position of the element to remove. `index` must be
149+
/// a valid index of the collection that is not equal to the collection's
150+
/// end index.
151+
///
152+
/// - Returns: The removed element.
153+
///
154+
/// - Complexity: O(`1`) if the set is a unique value (with no live copies),
155+
/// and the removed value is less than the largest value currently in the
156+
/// set (named *max*). Otherwise the complexity is at worst O(*max*).
157+
@discardableResult
158+
public mutating func remove(at index: Index) -> Element {
159+
let removed = _remove(index._value)
160+
precondition(removed, "Invalid index")
161+
return Int(bitPattern: index._value)
162+
}
163+
146164
/// Returns the current set (already sorted).
147165
///
148166
/// - Complexity: O(1)

Sources/BitCollections/BitSet/BitSet+SetAlgebra basics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ extension BitSet {
9797
///
9898
/// - Complexity: O(`1`) if the set is a unique value (with no live copies),
9999
/// and the removed value is less than the largest value currently in the
100-
/// set (named *max*). Otherwise the complexity is O(*max*).
100+
/// set (named *max*). Otherwise the complexity is at worst O(*max*).
101101
@discardableResult
102102
public mutating func remove(_ member: Int) -> Int? {
103103
guard let m = UInt(exactly: member) else { return nil }

Sources/_CollectionsTestSupport/AssertionContexts/Combinatorics.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,30 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12+
/// Run the supplied closure with all values in `items` in a loop,
13+
/// recording the current value in the current test trace stack.
14+
public func withEvery<Element>(
15+
_ label: String,
16+
by generator: () -> Element?,
17+
file: StaticString = #file,
18+
line: UInt = #line,
19+
run body: (Element) throws -> Void
20+
) rethrows {
21+
let context = TestContext.current
22+
while let item = generator() {
23+
let entry = context.push("\(label): \(item)", file: file, line: line)
24+
var done = false
25+
defer {
26+
context.pop(entry)
27+
if !done {
28+
print(context.currentTrace(title: "Throwing trace"))
29+
}
30+
}
31+
try body(item)
32+
done = true
33+
}
34+
}
35+
1236
/// Run the supplied closure with all values in `items` in a loop,
1337
/// recording the current value in the current test trace stack.
1438
public func withEvery<S: Sequence>(

Sources/_CollectionsTestSupport/Utilities/DictionaryAPIChecker.swift

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12+
/// A test protocol for validating that dictionary-like types implement users'
13+
/// baseline expectations.
14+
///
15+
/// To ensure maximum utility, this protocol doesn't refine `Collection`,
16+
/// although it does share some of the same requirements.
1217
public protocol DictionaryAPIChecker<Key, Value> {
1318
associatedtype Key
1419
associatedtype Value
@@ -42,15 +47,6 @@ public protocol DictionaryAPIChecker<Key, Value> {
4247

4348
init()
4449

45-
init<S: Sequence>(
46-
uniqueKeysWithValues keysAndValues: S
47-
) where S.Element == (Key, Value)
48-
49-
init<Keys: Sequence, Values: Sequence>(
50-
uniqueKeys keys: Keys,
51-
values: Values
52-
) where Keys.Element == Key, Values.Element == Value
53-
5450
init<S: Sequence>(
5551
_ keysAndValues: S,
5652
uniquingKeysWith combine: (Value, Value) throws -> Value
@@ -90,19 +86,28 @@ public protocol DictionaryAPIChecker<Key, Value> {
9086
by keyForValue: (S.Element) throws -> Key
9187
) rethrows where Value == [S.Element]
9288
#endif
89+
}
9390

91+
extension Dictionary: DictionaryAPIChecker {}
92+
93+
/// Additional entry points provided by this package that aren't provided
94+
/// by `Dictionary` (yet?).
95+
public protocol DictionaryAPIExtras: DictionaryAPIChecker {
9496
// Extras (not in the Standard Library)
9597

96-
mutating func updateValue<R>(
97-
forKey key: Key,
98-
default defaultValue: @autoclosure () -> Value,
99-
with body: (inout Value) throws -> R
100-
) rethrows -> R
98+
init<S: Sequence>(
99+
uniqueKeysWithValues keysAndValues: S
100+
) where S.Element == (Key, Value)
101101

102102
init<S: Sequence>(
103103
uniqueKeysWithValues keysAndValues: S
104104
) where S.Element == Element
105105

106+
init<Keys: Sequence, Values: Sequence>(
107+
uniqueKeys keys: Keys,
108+
values: Values
109+
) where Keys.Element == Key, Values.Element == Value
110+
106111
init<S: Sequence>(
107112
_ keysAndValues: S,
108113
uniquingKeysWith combine: (Value, Value) throws -> Value
@@ -118,7 +123,13 @@ public protocol DictionaryAPIChecker<Key, Value> {
118123
uniquingKeysWith combine: (Value, Value) throws -> Value
119124
) rethrows -> Self where S.Element == Element
120125

121-
#if false
126+
mutating func updateValue<R>(
127+
forKey key: Key,
128+
default defaultValue: @autoclosure () -> Value,
129+
with body: (inout Value) throws -> R
130+
) rethrows -> R
131+
132+
#if false
122133
// Potential additions implemented by PersistentDictionary:
123134

124135
func contains(_ key: Key) -> Bool
@@ -128,5 +139,5 @@ public protocol DictionaryAPIChecker<Key, Value> {
128139
with body: (inout Value?) throws -> R
129140
) rethrows -> R
130141

131-
#endif
142+
#endif
132143
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Collections open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
/// A test protocol for validating that set-like types implement users'
13+
/// baseline expectations.
14+
///
15+
/// To ensure maximum utility, this protocol refines neither `Collection` nor
16+
/// `SetAlgebra` although it does share some of the same requirements.
17+
public protocol SetAPIChecker {
18+
associatedtype Element
19+
associatedtype Index
20+
21+
var isEmpty: Bool { get }
22+
var count: Int { get }
23+
24+
init()
25+
26+
mutating func remove(at index: Index) -> Element
27+
28+
func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> Self
29+
30+
func isSubset<S: Sequence>(of other: S) -> Bool
31+
where S.Element == Element
32+
33+
func isSuperset<S: Sequence>(of other: S) -> Bool
34+
where S.Element == Element
35+
36+
func isStrictSubset<S: Sequence>(of other: S) -> Bool
37+
where S.Element == Element
38+
39+
func isStrictSuperset<S: Sequence>(of other: S) -> Bool
40+
where S.Element == Element
41+
42+
func isDisjoint<S: Sequence>(with other: S) -> Bool
43+
where S.Element == Element
44+
45+
46+
func intersection<S: Sequence>(_ other: S) -> Self
47+
where S.Element == Element
48+
49+
func union<S: Sequence>(_ other: __owned S) -> Self
50+
where S.Element == Element
51+
52+
__consuming func subtracting<S: Sequence>(_ other: S) -> Self
53+
where S.Element == Element
54+
55+
func symmetricDifference<S: Sequence>(_ other: __owned S) -> Self
56+
where S.Element == Element
57+
58+
mutating func formIntersection<S: Sequence>(_ other: S)
59+
where S.Element == Element
60+
61+
mutating func formUnion<S: Sequence>(_ other: __owned S)
62+
where S.Element == Element
63+
64+
mutating func subtract<S: Sequence>(_ other: S)
65+
where S.Element == Element
66+
67+
mutating func formSymmetricDifference<S: Sequence>(_ other: __owned S)
68+
where S.Element == Element
69+
}
70+
71+
extension Set: SetAPIChecker {}

Tests/BitCollectionsTests/BitSetTests.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import XCTest
1313
import _CollectionsTestSupport
1414
import BitCollections
1515

16+
extension BitSet: SetAPIChecker {}
17+
1618
final class BitSetTest: CollectionTestCase {
1719
func test_empty_initializer() {
1820
let set = BitSet()
@@ -288,6 +290,32 @@ final class BitSetTest: CollectionTestCase {
288290
}
289291
}
290292

293+
func test_remove_at() {
294+
let count = 100
295+
withEvery("seed", in: 0 ..< 10) { seed in
296+
var rng = RepeatableRandomNumberGenerator(seed: seed)
297+
var actual = BitSet(0 ..< count)
298+
var expected = Set<Int>(0 ..< count)
299+
var c = count
300+
301+
func nextOffset() -> Int? {
302+
guard let next = (0 ..< c).randomElement(using: &rng)
303+
else { return nil }
304+
c -= 1
305+
return next
306+
}
307+
308+
withEvery("offset", by: nextOffset) { offset in
309+
let i = actual.index(actual.startIndex, offsetBy: offset)
310+
let old = actual.remove(at: i)
311+
312+
let old2 = expected.remove(old)
313+
expectEqual(old, old2)
314+
}
315+
expectEqual(Array(actual), expected.sorted())
316+
}
317+
}
318+
291319
func test_member_subscript_getter() {
292320
withInterestingSets("input", maximum: 1000) { input in
293321
let bitset = BitSet(input)

Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary Tests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import XCTest
1414

1515
import _CollectionsTestSupport
1616

17-
extension OrderedDictionary: DictionaryAPIChecker {}
17+
extension OrderedDictionary: DictionaryAPIExtras {}
1818

1919
class OrderedDictionaryTests: CollectionTestCase {
2020
func test_empty() {

Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import XCTest
1313
@_spi(Testing) import OrderedCollections
1414
import _CollectionsTestSupport
1515

16+
extension OrderedSet: SetAPIChecker {}
17+
1618
class OrderedSetTests: CollectionTestCase {
1719
func test_init_uncheckedUniqueElements_concrete() {
1820
withEvery("count", in: 0 ..< 20) { count in

Tests/PersistentCollectionsTests/PersistentDictionary Tests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import _CollectionsTestSupport
1313
@testable import PersistentCollections
1414

15-
extension PersistentDictionary: DictionaryAPIChecker {}
15+
extension PersistentDictionary: DictionaryAPIExtras {}
1616

1717
class PersistentDictionaryTests: CollectionTestCase {
1818
func test_empty() {

0 commit comments

Comments
 (0)