Skip to content

Commit

Permalink
Merge pull request #192 from lorentey/set-API-checker
Browse files Browse the repository at this point in the history
[test] Check baseline API expectations for set-like types
  • Loading branch information
lorentey committed Oct 9, 2022
2 parents 674e5b6 + 4604c1b commit 78a29df
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 19 deletions.
18 changes: 18 additions & 0 deletions Sources/BitCollections/BitSet/BitSet+Extras.swift
Expand Up @@ -143,6 +143,24 @@ extension BitSet {
}

extension BitSet {
/// Removes and returns the element at the specified position.
///
/// - Parameter i: The position of the element to remove. `index` must be
/// a valid index of the collection that is not equal to the collection's
/// end index.
///
/// - Returns: The removed element.
///
/// - Complexity: O(`1`) if the set is a unique value (with no live copies),
/// and the removed value is less than the largest value currently in the
/// set (named *max*). Otherwise the complexity is at worst O(*max*).
@discardableResult
public mutating func remove(at index: Index) -> Element {
let removed = _remove(index._value)
precondition(removed, "Invalid index")
return Int(bitPattern: index._value)
}

/// Returns the current set (already sorted).
///
/// - Complexity: O(1)
Expand Down
Expand Up @@ -97,7 +97,7 @@ extension BitSet {
///
/// - Complexity: O(`1`) if the set is a unique value (with no live copies),
/// and the removed value is less than the largest value currently in the
/// set (named *max*). Otherwise the complexity is O(*max*).
/// set (named *max*). Otherwise the complexity is at worst O(*max*).
@discardableResult
public mutating func remove(_ member: Int) -> Int? {
guard let m = UInt(exactly: member) else { return nil }
Expand Down
Expand Up @@ -9,6 +9,30 @@
//
//===----------------------------------------------------------------------===//

/// Run the supplied closure with all values in `items` in a loop,
/// recording the current value in the current test trace stack.
public func withEvery<Element>(
_ label: String,
by generator: () -> Element?,
file: StaticString = #file,
line: UInt = #line,
run body: (Element) throws -> Void
) rethrows {
let context = TestContext.current
while let item = generator() {
let entry = context.push("\(label): \(item)", file: file, line: line)
var done = false
defer {
context.pop(entry)
if !done {
print(context.currentTrace(title: "Throwing trace"))
}
}
try body(item)
done = true
}
}

/// Run the supplied closure with all values in `items` in a loop,
/// recording the current value in the current test trace stack.
public func withEvery<S: Sequence>(
Expand Down
Expand Up @@ -9,6 +9,11 @@
//
//===----------------------------------------------------------------------===//

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

init()

init<S: Sequence>(
uniqueKeysWithValues keysAndValues: S
) where S.Element == (Key, Value)

init<Keys: Sequence, Values: Sequence>(
uniqueKeys keys: Keys,
values: Values
) where Keys.Element == Key, Values.Element == Value

init<S: Sequence>(
_ keysAndValues: S,
uniquingKeysWith combine: (Value, Value) throws -> Value
Expand Down Expand Up @@ -90,19 +86,28 @@ public protocol DictionaryAPIChecker<Key, Value> {
by keyForValue: (S.Element) throws -> Key
) rethrows where Value == [S.Element]
#endif
}

extension Dictionary: DictionaryAPIChecker {}

/// Additional entry points provided by this package that aren't provided
/// by `Dictionary` (yet?).
public protocol DictionaryAPIExtras: DictionaryAPIChecker {
// Extras (not in the Standard Library)

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

init<S: Sequence>(
uniqueKeysWithValues keysAndValues: S
) where S.Element == Element

init<Keys: Sequence, Values: Sequence>(
uniqueKeys keys: Keys,
values: Values
) where Keys.Element == Key, Values.Element == Value

init<S: Sequence>(
_ keysAndValues: S,
uniquingKeysWith combine: (Value, Value) throws -> Value
Expand All @@ -118,7 +123,13 @@ public protocol DictionaryAPIChecker<Key, Value> {
uniquingKeysWith combine: (Value, Value) throws -> Value
) rethrows -> Self where S.Element == Element

#if false
mutating func updateValue<R>(
forKey key: Key,
default defaultValue: @autoclosure () -> Value,
with body: (inout Value) throws -> R
) rethrows -> R

#if false
// Potential additions implemented by PersistentDictionary:

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

#endif
#endif
}
71 changes: 71 additions & 0 deletions Sources/_CollectionsTestSupport/Utilities/SetAPIChecker.swift
@@ -0,0 +1,71 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Collections open source project
//
// Copyright (c) 2022 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

/// A test protocol for validating that set-like types implement users'
/// baseline expectations.
///
/// To ensure maximum utility, this protocol refines neither `Collection` nor
/// `SetAlgebra` although it does share some of the same requirements.
public protocol SetAPIChecker {
associatedtype Element
associatedtype Index

var isEmpty: Bool { get }
var count: Int { get }

init()

mutating func remove(at index: Index) -> Element

func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> Self

func isSubset<S: Sequence>(of other: S) -> Bool
where S.Element == Element

func isSuperset<S: Sequence>(of other: S) -> Bool
where S.Element == Element

func isStrictSubset<S: Sequence>(of other: S) -> Bool
where S.Element == Element

func isStrictSuperset<S: Sequence>(of other: S) -> Bool
where S.Element == Element

func isDisjoint<S: Sequence>(with other: S) -> Bool
where S.Element == Element


func intersection<S: Sequence>(_ other: S) -> Self
where S.Element == Element

func union<S: Sequence>(_ other: __owned S) -> Self
where S.Element == Element

__consuming func subtracting<S: Sequence>(_ other: S) -> Self
where S.Element == Element

func symmetricDifference<S: Sequence>(_ other: __owned S) -> Self
where S.Element == Element

mutating func formIntersection<S: Sequence>(_ other: S)
where S.Element == Element

mutating func formUnion<S: Sequence>(_ other: __owned S)
where S.Element == Element

mutating func subtract<S: Sequence>(_ other: S)
where S.Element == Element

mutating func formSymmetricDifference<S: Sequence>(_ other: __owned S)
where S.Element == Element
}

extension Set: SetAPIChecker {}
28 changes: 28 additions & 0 deletions Tests/BitCollectionsTests/BitSetTests.swift
Expand Up @@ -13,6 +13,8 @@ import XCTest
import _CollectionsTestSupport
import BitCollections

extension BitSet: SetAPIChecker {}

final class BitSetTest: CollectionTestCase {
func test_empty_initializer() {
let set = BitSet()
Expand Down Expand Up @@ -288,6 +290,32 @@ final class BitSetTest: CollectionTestCase {
}
}

func test_remove_at() {
let count = 100
withEvery("seed", in: 0 ..< 10) { seed in
var rng = RepeatableRandomNumberGenerator(seed: seed)
var actual = BitSet(0 ..< count)
var expected = Set<Int>(0 ..< count)
var c = count

func nextOffset() -> Int? {
guard let next = (0 ..< c).randomElement(using: &rng)
else { return nil }
c -= 1
return next
}

withEvery("offset", by: nextOffset) { offset in
let i = actual.index(actual.startIndex, offsetBy: offset)
let old = actual.remove(at: i)

let old2 = expected.remove(old)
expectEqual(old, old2)
}
expectEqual(Array(actual), expected.sorted())
}
}

func test_member_subscript_getter() {
withInterestingSets("input", maximum: 1000) { input in
let bitset = BitSet(input)
Expand Down
Expand Up @@ -14,7 +14,7 @@ import XCTest

import _CollectionsTestSupport

extension OrderedDictionary: DictionaryAPIChecker {}
extension OrderedDictionary: DictionaryAPIExtras {}

class OrderedDictionaryTests: CollectionTestCase {
func test_empty() {
Expand Down
Expand Up @@ -13,6 +13,8 @@ import XCTest
@_spi(Testing) import OrderedCollections
import _CollectionsTestSupport

extension OrderedSet: SetAPIChecker {}

class OrderedSetTests: CollectionTestCase {
func test_init_uncheckedUniqueElements_concrete() {
withEvery("count", in: 0 ..< 20) { count in
Expand Down
Expand Up @@ -12,7 +12,7 @@
import _CollectionsTestSupport
@testable import PersistentCollections

extension PersistentDictionary: DictionaryAPIChecker {}
extension PersistentDictionary: DictionaryAPIExtras {}

class PersistentDictionaryTests: CollectionTestCase {
func test_empty() {
Expand Down

0 comments on commit 78a29df

Please sign in to comment.