//===--- 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: Instances, of baseCollection: BaseCollection, equalityOracle: (Instances.Index, Instances.Index) -> Bool, ${end}Index: Instances.Iterator.Element, ${TRACE} ) where Instances : Collection, BaseCollection : ${protocol}, Instances.Iterator.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: Instances, of baseCollection : BaseCollection, equalityOracle: (Instances.Index, Instances.Index) -> Bool, limit: Instances.Iterator.Element, sign: BaseCollection.IndexDistance, // 1 or -1 next: (Instances.Iterator.Element) -> Instances.Iterator.Element, ${TRACE} ) where Instances : Collection, BaseCollection : Collection, Instances.Iterator.Element == BaseCollection.Index { for i in instances { let d: BaseCollection.IndexDistance = sign > 0 ? baseCollection.distance(from: i, to: limit) : -baseCollection.distance(from: limit, to: i) var offset: BaseCollection.IndexDistance = 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: Instances, of baseCollection: BaseCollection, equalityOracle: (Instances.Index, Instances.Index) -> Bool, endIndex: Instances.Iterator.Element, ${TRACE} ) where Instances : Collection, BaseCollection : Collection, Instances.Iterator.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: Instances, of baseCollection: BaseCollection, equalityOracle: (Instances.Index, Instances.Index) -> Bool, startIndex: Instances.Iterator.Element, endIndex: Instances.Iterator.Element, ${TRACE} ) where Instances: Collection, BaseCollection : BidirectionalCollection, Instances.Iterator.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: Instances, distances: Distances, of baseCollection: BaseCollection, distanceOracle: (Instances.Index, Instances.Index) -> Distances.Iterator.Element, advanceOracle: (Instances.Index, Distances.Index) -> Instances.Iterator.Element, startIndex: Instances.Iterator.Element, endIndex: Instances.Iterator.Element, ${TRACE} ) where Instances : Collection, Distances : Collection, BaseCollection : RandomAccessCollection, Instances.Iterator.Element == BaseCollection.Index, Distances.Iterator.Element == BaseCollection.IndexDistance { 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: Instances, distances: Distances, of baseCollection: BaseCollection, distanceOracle: (Instances.Index, Instances.Index) -> Distances.Iterator.Element, advanceOracle: (Instances.Index, Distances.Index) -> Instances.Iterator.Element, ${TRACE} ) where Instances : Collection, Distances : Collection, BaseCollection : Collection, Instances.Iterator.Element == BaseCollection.Index, Distances.Iterator.Element == BaseCollection.IndexDistance { 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.Iterator.Element', 'Expected'), % ('Element' , 'Element' , 'Array')]: // 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.Iterator.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.Iterator.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.Iterator.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.Iterator.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.. 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..= 2 { for i in 0..= 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.Iterator.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..( _ makeCollection: @escaping () -> C, _ makeNewValues: (Int) -> N ) where C : RangeReplaceableCollection, N : Collection, C.Iterator.Element : Equatable, C.Iterator.Element == N.Iterator.Element { typealias A = C // First make an independent copy of the array that we can use for // comparison later. let source = Array(makeCollection()) for (ix, i) in source.indices.enumerated() { for (jx_, j) in (i..