| //===--- CheckCollectionInstance.swift.gyb --------------------*- swift -*-===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 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 |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| %{ |
| from gyb_stdlib_unittest_support import TRACE, stackTrace, trace |
| from gyb_stdlib_support import TRAVERSALS, collectionForTraversal |
| }% |
| |
| import StdlibUnittest |
| |
| public struct CollectionMisuseResiliencyChecks { |
| public enum FailureKind { |
| case none |
| case trap |
| } |
| |
| public var creatingOutOfBoundsIndicesBehavior: FailureKind = .trap |
| public var subscriptOnOutOfBoundsIndicesBehavior: FailureKind = .trap |
| public var subscriptRangeOnOutOfBoundsRangesBehavior: FailureKind = .trap |
| |
| public static var all: CollectionMisuseResiliencyChecks { |
| return CollectionMisuseResiliencyChecks() |
| } |
| |
| public static var none: CollectionMisuseResiliencyChecks { |
| return CollectionMisuseResiliencyChecks( |
| creatingOutOfBoundsIndicesBehavior: .none, |
| subscriptOnOutOfBoundsIndicesBehavior: .none, |
| subscriptRangeOnOutOfBoundsRangesBehavior: .none) |
| } |
| } |
| |
| % for inc, protocol, direction, end in ( |
| % ('inc', 'Collection', 'after', 'end'), |
| % ('dec', 'BidirectionalCollection', 'before', 'start')): |
| |
| /// Test that the elements of `instances` satisfy |
| /// ${'some of ' if inc == 'dec' else ''}the semantic |
| /// requirements of `${protocol}`, using `equalityOracle` to |
| /// generate equality expectations from pairs of positions in |
| /// `instances`. |
| /// |
| /// - Precondition: ${'''`endIndex` is reachable from all |
| /// elements of `instances`.''' if inc == 'inc' else '''all |
| /// elements of `instances` are reachable from `startIndex`.'''} |
| public func check${inc.capitalize()}rementable<Instances, BaseCollection>( |
| _ instances: Instances, |
| of baseCollection: BaseCollection, |
| equalityOracle: (Instances.Index, Instances.Index) -> Bool, |
| ${end}Index: Instances.Element, ${TRACE} |
| ) where |
| Instances : Collection, |
| BaseCollection : ${protocol}, |
| Instances.Element == BaseCollection.Index { |
| |
| checkEquatable(instances, oracle: equalityOracle, ${trace}) |
| for i in instances { |
| if i != ${end}Index { |
| let next = baseCollection.index(${direction}: i) |
| // index(${direction}:) gets us a new index value |
| expectNotEqual(i, next, ${trace}) |
| |
| // Which is the same as if we apply formIndex(${direction}:) |
| var j = i |
| baseCollection.formIndex(${direction}: &j) |
| expectEqual(j, next, ${trace}) |
| } |
| } |
| } |
| %end |
| |
| internal func _checkIncrementalAdvance<Instances, BaseCollection>( |
| _ instances: Instances, |
| of baseCollection : BaseCollection, |
| equalityOracle: (Instances.Index, Instances.Index) -> Bool, |
| limit: Instances.Element, |
| sign: Int, // 1 or -1 |
| next: (Instances.Element) -> Instances.Element, |
| ${TRACE} |
| ) where |
| Instances : Collection, |
| BaseCollection : Collection, |
| Instances.Element == BaseCollection.Index { |
| for i in instances { |
| let d: Int = sign > 0 ? |
| baseCollection.distance(from: i, to: limit) : |
| -baseCollection.distance(from: limit, to: i) |
| |
| var offset: Int = 0 |
| for _ in 0...Int64(d * sign) { |
| let j = baseCollection.index(i, offsetBy: offset) |
| let k = baseCollection.index(i, offsetBy: offset + sign, limitedBy: limit) ?? limit |
| let jAtLimit = offset == d |
| if jAtLimit { |
| expectEqual(limit, j, ${trace}) |
| } |
| expectEqual(jAtLimit ? j : next(j), k, ${trace}) |
| offset += sign |
| } |
| } |
| } |
| |
| /// Test that the elements of `instances` satisfy the semantic requirements of |
| /// index for `Collection`, using `equalityOracle` to generate equality |
| /// expectations from pairs of positions in `instances`. |
| /// |
| /// - Precondition: `endIndex` is reachable from all elements of |
| /// `instances` |
| public func checkForwardIndex<Instances, BaseCollection>( |
| _ instances: Instances, |
| of baseCollection: BaseCollection, |
| equalityOracle: (Instances.Index, Instances.Index) -> Bool, |
| endIndex: Instances.Element, ${TRACE} |
| ) where |
| Instances : Collection, |
| BaseCollection : Collection, |
| Instances.Element == BaseCollection.Index { |
| |
| checkIncrementable(instances, of: baseCollection, |
| equalityOracle: equalityOracle, endIndex: endIndex, ${trace}) |
| |
| _checkIncrementalAdvance(instances, of: baseCollection, |
| equalityOracle: equalityOracle, limit: endIndex, |
| sign: 1, next: { baseCollection.index(after: $0) }, ${trace}) |
| } |
| |
| /// Test that the elements of `instances` satisfy the semantic requirements of |
| /// index for `BidirectionalCollection`, using `equalityOracle` to generate |
| /// equality expectations from pairs of positions in `instances`. |
| /// |
| /// - Precondition: |
| /// - all elements of `instances` are reachable from `startIndex`. |
| /// - `endIndex` is reachable from all elements of `instances`. |
| public func checkBidirectionalIndex<Instances, BaseCollection>( |
| _ instances: Instances, |
| of baseCollection: BaseCollection, |
| equalityOracle: (Instances.Index, Instances.Index) -> Bool, |
| startIndex: Instances.Element, |
| endIndex: Instances.Element, |
| ${TRACE} |
| ) where |
| Instances: Collection, |
| BaseCollection : BidirectionalCollection, |
| Instances.Element == BaseCollection.Index { |
| |
| checkForwardIndex(instances, of: baseCollection, |
| equalityOracle: equalityOracle, endIndex: endIndex) |
| |
| checkDecrementable(instances, of: baseCollection, |
| equalityOracle: equalityOracle, startIndex: startIndex, ${trace}) |
| |
| _checkIncrementalAdvance(instances, of: baseCollection, |
| equalityOracle: equalityOracle, limit: startIndex, |
| sign: -1, next: { baseCollection.index(before: $0) }, ${trace}) |
| } |
| |
| /// Test that the elements of `instances` satisfy the semantic requirements of |
| /// index for `RandomAccessCollection`, using `advanceOracle` and |
| /// 'distanceOracle' to generate expectations about the results of |
| /// `advanced(by:)` and `distance(to:)` from pairs of positions in `instances` |
| /// and `distances`. |
| /// |
| /// - Precondition: |
| /// - all elements of `instances` are reachable from `startIndex`. |
| /// - `endIndex` is reachable from all elements of `instances`. |
| public func checkRandomAccessIndex<Instances, Distances, BaseCollection>( |
| _ instances: Instances, distances: Distances, |
| of baseCollection: BaseCollection, |
| distanceOracle: |
| (Instances.Index, Instances.Index) -> Distances.Element, |
| advanceOracle: |
| (Instances.Index, Distances.Index) -> Instances.Element, |
| startIndex: Instances.Iterator.Element, |
| endIndex: Instances.Iterator.Element, |
| ${TRACE} |
| ) where |
| Instances : Collection, |
| Distances : Collection, |
| BaseCollection : RandomAccessCollection, |
| Instances.Element == BaseCollection.Index, |
| Distances.Element == Int { |
| |
| checkBidirectionalIndex(instances, of: baseCollection, |
| equalityOracle: { distanceOracle($0, $1) == 0 }, |
| startIndex: startIndex, endIndex: endIndex, ${trace}) |
| |
| checkAdvancesAndDistances( |
| instances, distances: distances, |
| of: baseCollection, |
| distanceOracle: distanceOracle, |
| advanceOracle: advanceOracle, ${trace}) |
| } |
| |
| // Copies what checkStrideable is doing, but instead of calling |
| // advanced(by:) and distance(to:) on an Strideable's, |
| // calls corresponding methods on a base collection. |
| public func checkAdvancesAndDistances<Instances, Distances, BaseCollection>( |
| _ instances: Instances, distances: Distances, |
| of baseCollection: BaseCollection, |
| distanceOracle: |
| (Instances.Index, Instances.Index) -> Distances.Element, |
| advanceOracle: |
| (Instances.Index, Distances.Index) -> Instances.Element, |
| ${TRACE} |
| ) where |
| Instances : Collection, |
| Distances : Collection, |
| BaseCollection : Collection, |
| Instances.Element == BaseCollection.Index, |
| Distances.Element == Int { |
| |
| checkComparable( |
| instances, |
| oracle: { |
| let d = distanceOracle($1, $0); |
| return d < 0 ? .lt : d == 0 ? .eq : .gt |
| }, |
| ${trace}) |
| |
| for i in instances.indices { |
| let x = instances[i] |
| expectEqual(x, baseCollection.index(x, offsetBy: 0)) |
| |
| for j in distances.indices { |
| let y = distances[j] |
| expectEqual(advanceOracle(i, j), baseCollection.index(x, offsetBy: y)) |
| } |
| |
| for j in instances.indices { |
| let y = instances[j] |
| expectEqual(distanceOracle(i, j), baseCollection.distance(from: x, to: y)) |
| } |
| } |
| } |
| |
| // Generate two overloads: one for Array (which will get |
| // picked up when the caller passes a literal), and another that |
| // accepts any appropriate Collection type. |
| % for genericParam, Element, Expected in [ |
| % ('Expected: Collection', 'Expected.Element', 'Expected'), |
| % ('Element' , 'Element' , 'Array<Element>')]: |
| |
| // Top-level check for Collection instances. Alias for checkForwardCollection. |
| // Checks all slices: O(n^2). |
| public func checkCollection<${genericParam}, C : Collection>( |
| _ expected: ${Expected}, |
| _ collection: C, |
| ${TRACE}, |
| resiliencyChecks: CollectionMisuseResiliencyChecks = .all, |
| sameValue: (${Element}, ${Element}) -> Bool |
| ) where C.Element == ${Element} { |
| |
| checkForwardCollection(expected, collection, message(), |
| stackTrace: stackTrace, showFrame: showFrame, file: file, line: line, |
| resiliencyChecks: resiliencyChecks, |
| sameValue: sameValue) |
| } |
| |
| % for Traversal in TRAVERSALS: |
| % TraversalCollection = collectionForTraversal(Traversal) |
| |
| // Calls check${Traversal}Collection with default `sameValue`. |
| public func check${Traversal}Collection< |
| ${genericParam}, C : ${TraversalCollection} |
| >( |
| _ expected: ${Expected}, _ collection: C, |
| ${TRACE}, |
| resiliencyChecks: CollectionMisuseResiliencyChecks = .all |
| ) where |
| C.Element == ${Element}, |
| ${Element} : Equatable { |
| |
| check${Traversal}Collection( |
| expected, collection, ${trace}, |
| resiliencyChecks: resiliencyChecks) { $0 == $1 } |
| } |
| |
| // Top-Level check for all ${TraversalCollection} semantics on a single |
| // instance. This constrains SubSequence associated types in order to check |
| // slice semantics. |
| // Checks all slices: O(n^2). |
| public func check${Traversal}Collection< |
| ${genericParam}, C : ${TraversalCollection} |
| >( |
| _ expected: ${Expected}, _ collection: C, |
| ${TRACE}, |
| resiliencyChecks: CollectionMisuseResiliencyChecks = .all, |
| sameValue: (${Element}, ${Element}) -> Bool |
| ) where |
| C.Element == ${Element} { |
| |
| checkOneLevelOf${Traversal}Collection(expected, collection, ${trace}, |
| resiliencyChecks: resiliencyChecks, sameValue: sameValue) |
| |
| // Avoid validation of all possible (n^2) slices on large collection. |
| // Test cases should call checkOneLevelOf${Traversal}Collection instead. |
| expectLT(expected.count, 30) |
| |
| _checkSliceableWith${Traversal}Index(expected, collection, ${trace}, |
| resiliencyChecks: resiliencyChecks, sameValue: sameValue) |
| } |
| |
| // Helper for check${Traversal}Collection. Check that instance of `C`, |
| // `collection`, upholds the semantics of `${TraversalCollection}`, |
| // non-recursively. This does not check subsequences. It may be called for each |
| // subsequence without combinatorial explosion. Also, since recursive protocol |
| // constraints are not supported, our second level of checks cannot depend on the |
| // associated type properties of SubSequence. |
| // |
| // Checks all slices: O(n^2). |
| public func checkOneLevelOf${Traversal}Collection< |
| ${genericParam}, C : ${TraversalCollection} |
| >( |
| _ expected: ${Expected}, _ collection: C, |
| ${TRACE}, |
| resiliencyChecks: CollectionMisuseResiliencyChecks = .all, |
| sameValue: (${Element}, ${Element}) -> Bool |
| ) where C.Element == ${Element} { |
| |
| // A `Collection` is a multi-pass `Sequence`. |
| for _ in 0..<3 { |
| checkSequence( |
| expected, collection, ${trace}, |
| resiliencyChecks: resiliencyChecks, sameValue: sameValue) |
| } |
| |
| //===------------------------------------------------------------------===// |
| // Check Index semantics |
| //===------------------------------------------------------------------===// |
| |
| let succ = { collection.index(after: $0) } |
| % if Traversal != 'Forward': |
| let pred = { collection.index(before: $0) } |
| % end |
| // Advances up to 1 positions without passing endIndex. Don't use |
| // advanced(by: n) to do this because it's under test here. |
| let next = { $0 == collection.endIndex ? $0 : succ($0) } |
| |
| // advances up to 5 positions without passing endIndex. Picking a |
| // small constant to avoid complexity explosion on large input |
| // collections. |
| let next5 = { next(next(next(next(next($0))))) } |
| |
| let partWay0 = next5(collection.startIndex) |
| let partWay1 = next5(partWay0) |
| |
| % if Traversal == 'Forward': |
| |
| let instances = _allIndices(into: collection, |
| in: collection.startIndex..<partWay0) |
| checkForwardIndex(instances, of: collection, |
| equalityOracle: { $0 == $1 }, endIndex: partWay1, ${trace}) |
| |
| % elif Traversal == 'Bidirectional': |
| |
| let instances = _allIndices(into: collection, in: partWay0..<partWay1) |
| checkBidirectionalIndex(instances, of: collection, |
| equalityOracle: { $0 == $1 }, |
| startIndex: collection.startIndex, |
| endIndex: next5(partWay1), ${trace}) |
| |
| % else: |
| % assert(Traversal == 'RandomAccess') |
| typealias Distance = Int |
| |
| let count: Distance = collection.count |
| let offset0 = min(5, count) |
| let offset1 = min(10, count) |
| let offset2 = min(15, count) |
| |
| let distanceCandidates: [Distance] = [ |
| -11, -7, -5, -3, -2, -1, 0, 1, 2, 3, 5, 7, 11] |
| |
| let distances = distanceCandidates.filter { (x: Distance) -> Bool in |
| x + offset0 >= 0 && x + offset1 <= count |
| } |
| |
| func nextN(_ n: Distance, _ i: C.Index) -> C.Index { |
| return collection.index(i, offsetBy: n) |
| } |
| |
| let instances = _allIndices(into: collection, in: partWay0..<partWay1) |
| |
| checkRandomAccessIndex( |
| instances, |
| distances: distances, |
| of: collection, |
| distanceOracle: { (x:Int, y:Int) in numericCast(y - x) }, |
| advanceOracle: { x, y in nextN(distances[y], instances[x]) }, |
| startIndex: collection.startIndex, |
| endIndex: next5(partWay1), ${trace}) |
| |
| % end |
| |
| let expectedArray = Array(expected) |
| |
| // Check `count`. |
| expectEqual(Int64(expectedArray.count), Int64(collection.count), ${trace}) |
| |
| //===------------------------------------------------------------------===// |
| // Check Iteration behavior. |
| //===------------------------------------------------------------------===// |
| |
| for _ in 0..<3 { |
| do { |
| let startIndex = collection.startIndex |
| let endIndex = collection.endIndex |
| |
| for _ in collection.indices { |
| expectEqual( |
| startIndex, collection.startIndex, |
| "Iteration should not change startIndex", |
| stackTrace: ${stackTrace}) |
| |
| expectEqual( |
| endIndex, collection.endIndex, |
| "Iteration should not change endIndex", |
| stackTrace: ${stackTrace}) |
| } |
| } |
| |
| var allIndices = Array(collection.indices) |
| |
| if expectedArray.count >= 2 { |
| for i in 0..<allIndices.count-1 { |
| let successor1 = succ(allIndices[i]) |
| var successor2 = allIndices[i] |
| successor2 = succ(successor2) |
| var successor3 = allIndices[i] |
| successor3 = succ(successor3) |
| for s in [ successor1, successor2, successor3 ] { |
| expectEqual(allIndices[i + 1], s, ${trace}) |
| expectEqualTest( |
| expectedArray[i + 1], collection[s], ${trace}, sameValue: sameValue) |
| } |
| } |
| |
| % if Traversal == "Bidirectional": |
| |
| for i in 1..<allIndices.count { |
| let predecessor1 = pred(allIndices[i]) |
| var predecessor2 = allIndices[i] |
| predecessor2 = pred(predecessor2) |
| var predecessor3 = allIndices[i] |
| predecessor3 = pred(predecessor3) |
| for p in [ predecessor1, predecessor2, predecessor3 ] { |
| expectEqual(allIndices[i - 1], p, ${trace}) |
| expectEqualTest( |
| expectedArray[i - 1], collection[p], ${trace}, sameValue: sameValue) |
| } |
| } |
| for i in 1..<allIndices.count { |
| let index = succ(pred(allIndices[i])) |
| expectEqual(allIndices[i], index, ${trace}) |
| expectEqualTest( |
| expectedArray[i], collection[index], ${trace}, sameValue: sameValue) |
| } |
| |
| % end # Traversal == "Bidirectional" |
| |
| } // end of `if expectedArray.count >= 2` |
| |
| do { |
| var allIndices2: [C.Index] = [] |
| for i in collection.indices { |
| allIndices2.append(i) |
| } |
| |
| expectEqualSequence( |
| allIndices, allIndices2, "iteration should not invalidate indices", |
| stackTrace: ${stackTrace}) |
| |
| expectEqualSequence( |
| expectedArray, allIndices.map { collection[$0] }, |
| stackTrace: ${stackTrace}, sameValue: sameValue) |
| expectEqualSequence( |
| expectedArray, allIndices2.map { collection[$0] }, |
| stackTrace: ${stackTrace}, sameValue: sameValue) |
| } |
| } // end of `for _ in 0..<3` |
| |
| // FIXME: more checks for bidirectional and random access collections. |
| } |
| |
| // Helper for check${Traversal}Collection to check Slices. |
| // |
| // Checks all slices: O(n^2). |
| internal func _checkSliceableWith${Traversal}Index< |
| ${genericParam}, S : ${TraversalCollection} |
| >( |
| _ expected: ${Expected}, _ sliceable: S, ${TRACE}, |
| resiliencyChecks: CollectionMisuseResiliencyChecks = .all, |
| sameValue: (${Element}, ${Element}) -> Bool |
| ) where |
| S.Element == ${Element} { |
| |
| let expectedArray = Array(expected) |
| |
| let succ = { sliceable.index(after: $0) } |
| % if Traversal != "Forward": |
| let pred = { sliceable.index(before: $0) } |
| % end |
| |
| var start = sliceable.startIndex |
| for startNumericIndex in 0...expectedArray.count { |
| % if Traversal != "Forward": |
| if start != sliceable.endIndex { |
| start = succ(start) |
| start = pred(start) |
| start = succ(start) |
| start = pred(start) |
| } |
| % end |
| var end = start |
| for endNumericIndex in startNumericIndex...expectedArray.count { |
| % if Traversal != "Forward": |
| if end != sliceable.endIndex { |
| end = succ(end) |
| end = pred(end) |
| end = succ(end) |
| end = pred(end) |
| } |
| % end |
| let expectedSlice = expectedArray[startNumericIndex..<endNumericIndex] |
| let slice = sliceable[start..<end] |
| // For every possible slice, verify that the slice's bounds are identical |
| // to the indices used to form the slice. |
| expectEqual(start, slice.startIndex) |
| expectEqual(end, slice.endIndex) |
| |
| checkOneLevelOf${Traversal}Collection(expectedSlice, slice, ${trace}, |
| resiliencyChecks: resiliencyChecks, |
| sameValue: sameValue) |
| |
| if end != sliceable.endIndex { |
| end = succ(end) |
| } |
| } |
| if start != sliceable.endIndex { |
| start = succ(start) |
| } |
| } |
| } |
| |
| % end # Traversal |
| |
| % end # genericParam, Elements, Expected |
| |
| // Check RangeReplaceableCollection using a factory. |
| // |
| // Note: This does not invoke other collection tests. |
| public func checkRangeReplaceable<C, N>( |
| _ makeCollection: @escaping () -> C, |
| _ makeNewValues: (Int) -> N |
| ) where |
| C : RangeReplaceableCollection, |
| N : Collection, |
| C.Element : Equatable, |
| C.Element == N.Element { |
| |
| typealias A = C |
| |
| // First make an independent copy of the array that we can use for |
| // comparison later. |
| let source = Array<A.Element>(makeCollection()) |
| |
| for (ix, i) in source.indices.enumerated() { |
| for (jx_, j) in (i..<source.endIndex).enumerated() { |
| let jx = jx_ + ix |
| |
| let oldCount = jx - ix |
| for newCount in 0..<(2 * oldCount) { |
| let newValues = makeNewValues(newCount) |
| |
| func reportFailure(_ a: inout A, _ message: String) { |
| print("\(message) when replacing indices \(ix)...\(jx)") |
| print(" in \(Array(source)) with \(Array(newValues))") |
| print(" yielding \(Array(a))") |
| print("====================================") |
| expectTrue(false) |
| } |
| |
| var a = makeCollection() |
| |
| a.replaceSubrange(nthIndex(a, ix)..<nthIndex(a, jx), with: newValues) |
| let growth = newCount - oldCount |
| |
| let expectedCount = source.count + growth |
| let actualCount = numericCast(a.count) as Int |
| if actualCount != expectedCount { |
| reportFailure( |
| &a, "\(actualCount) != expected count \(expectedCount)") |
| } |
| |
| for (kx, k) in a.indices.enumerated() { |
| let expectedValue = kx < ix ? nth(source, kx) |
| : kx < jx + growth ? nth(newValues, kx - ix) |
| : nth(source, kx - growth) |
| |
| if a[k] != expectedValue { |
| reportFailure( |
| &a, |
| // FIXME: why do we need to break this string into two parts? |
| "a[\(kx)] = " |
| + "\(a[k]) != expected value \(expectedValue)") |
| } |
| } |
| } |
| } |
| } |
| } |