Files
swift-mirror/validation-test/stdlib/Range.swift.gyb
Nate Cook e12e968570 Add contains(_:) methods to (Closed)Range (#76891)
The _StringProcessing module provides a generic, collection-based
`contains` method that performs poorly for ranges and closed ranges.
This addresses the primary issue by providing concrete overloads
for Range and ClosedRange which match the expected performance for
these operations.

This change also fixes an issue with the existing range overlap tests.
The generated `(Closed)Range.overlap` tests are ignoring the "other"
range type when generating ranges for testing, so all overlap tests
are only being run against ranges of the same type. This fixes things
so that heterogeneous testing is included.
2024-11-12 11:47:24 -08:00

873 lines
27 KiB
Swift

// RUN: %target-run-simple-swiftgyb
// REQUIRES: executable_test
// REQUIRES: reflection
import StdlibUnittest
import StdlibCollectionUnittest
extension Range {
static var _isHalfOpen: Bool { return true }
}
extension ClosedRange {
static var _isHalfOpen: Bool { return false }
}
protocol TestProtocol1 {}
struct ContainsTest {
let lowerBound: Int
let upperBound: Int
let value: Int
let loc: SourceLoc
var containedInHalfOpen: Bool {
return lowerBound <= value && value < upperBound
}
var containedInClosed: Bool {
return lowerBound <= value && value <= upperBound
}
init(
lowerBound: Int,
upperBound: Int,
value: Int,
file: String = #file, line: UInt = #line
) {
self.lowerBound = lowerBound
self.upperBound = upperBound
self.value = value
self.loc = SourceLoc(file, line, comment: "test data")
}
}
func generateContainsTests() -> [ContainsTest] {
let bounds = [ Int.min, -30, -10, 0, 10, 20, Int.max ]
var result: [ContainsTest] = []
for lowerBound in bounds {
for upperBound in bounds {
if lowerBound > upperBound { continue }
for value in bounds {
result.append(
ContainsTest(
lowerBound: lowerBound, upperBound: upperBound,
value: value))
}
}
}
return result
}
let containsTests: [ContainsTest] = generateContainsTests()
infix operator ..<* : RangeFormationPrecedence
infix operator ...* : RangeFormationPrecedence
enum VariantRange {
case halfOpen(lowerBound: Int, upperBound: Int)
case closed(lowerBound: Int, upperBound: Int)
var isHalfOpen: Bool {
switch self {
case .halfOpen:
return true
case .closed:
return false
}
}
var lowerBound: Int {
switch self {
case .halfOpen(let result, _):
return result
case .closed(let result, _):
return result
}
}
var upperBound: Int {
switch self {
case .halfOpen(_, let result):
return result
case .closed(_, let result):
return result
}
}
}
func ..<* (lhs: Int, rhs: Int) -> VariantRange {
return .halfOpen(lowerBound: lhs, upperBound: rhs)
}
func ...* (lhs: Int, rhs: Int) -> VariantRange {
return .closed(lowerBound: lhs, upperBound: rhs)
}
struct OverlapsTest {
let expected: Bool
let lhs: VariantRange
let rhs: VariantRange
let loc: SourceLoc
init(
expected: Bool,
lhs: VariantRange,
rhs: VariantRange,
file: String = #file, line: UInt = #line
) {
self.expected = expected
self.lhs = lhs
self.rhs = rhs
self.loc = SourceLoc(file, line, comment: "test data")
}
}
typealias ContainsRangeTest = OverlapsTest
let containsRangeTests: [ContainsRangeTest] = [
// Same bounds
ContainsRangeTest(expected: true, lhs: 0..<*10, rhs: 0..<*10),
ContainsRangeTest(expected: false, lhs: 0..<*10, rhs: 0...*10),
ContainsRangeTest(expected: true, lhs: 0..<*10, rhs: 0...*9),
ContainsRangeTest(expected: true, lhs: 0...*10, rhs: 0...*10),
ContainsRangeTest(expected: true, lhs: 0...*10, rhs: 0..<*10),
ContainsRangeTest(expected: true, lhs: 0...*10, rhs: 0..<*11),
// Interior
ContainsRangeTest(expected: true, lhs: 0..<*10, rhs: 1..<*9),
ContainsRangeTest(expected: true, lhs: 0..<*10, rhs: 1...*9),
ContainsRangeTest(expected: true, lhs: 0...*10, rhs: 1...*9),
ContainsRangeTest(expected: true, lhs: 0...*10, rhs: 1..<*9),
// Failures
ContainsRangeTest(expected: false, lhs: 0..<*10, rhs: -10..<*5),
ContainsRangeTest(expected: false, lhs: 0..<*10, rhs: -10...*5),
ContainsRangeTest(expected: false, lhs: 0...*10, rhs: -10...*5),
ContainsRangeTest(expected: false, lhs: 0...*10, rhs: -10..<*5),
ContainsRangeTest(expected: false, lhs: 0..<*10, rhs: 5..<*15),
ContainsRangeTest(expected: false, lhs: 0..<*10, rhs: 5...*15),
ContainsRangeTest(expected: false, lhs: 0...*10, rhs: 5...*15),
ContainsRangeTest(expected: false, lhs: 0...*10, rhs: 5..<*15),
// "Empty" ranges
ContainsRangeTest(expected: true, lhs: 0..<*0, rhs: 0..<*0),
ContainsRangeTest(expected: true, lhs: 0..<*0, rhs: 1..<*1),
ContainsRangeTest(expected: false, lhs: 0..<*0, rhs: 0...*0),
ContainsRangeTest(expected: true, lhs: 0...*0, rhs: 0...*0),
ContainsRangeTest(expected: true, lhs: 0...*0, rhs: 0..<*0),
ContainsRangeTest(expected: true, lhs: 0...*0, rhs: 1..<*1),
]
let overlapsTests: [OverlapsTest] = [
// 0-4, 5-10
OverlapsTest(expected: false, lhs: 0..<*4, rhs: 5..<*10),
OverlapsTest(expected: false, lhs: 0..<*4, rhs: 5...*10),
OverlapsTest(expected: false, lhs: 0...*4, rhs: 5..<*10),
OverlapsTest(expected: false, lhs: 0...*4, rhs: 5...*10),
// 0-5, 5-10
OverlapsTest(expected: false, lhs: 0..<*5, rhs: 5..<*10),
OverlapsTest(expected: false, lhs: 0..<*5, rhs: 5...*10),
OverlapsTest(expected: true, lhs: 0...*5, rhs: 5..<*10),
OverlapsTest(expected: true, lhs: 0...*5, rhs: 5...*10),
// 0-6, 5-10
OverlapsTest(expected: true, lhs: 0..<*6, rhs: 5..<*10),
OverlapsTest(expected: true, lhs: 0..<*6, rhs: 5...*10),
OverlapsTest(expected: true, lhs: 0...*6, rhs: 5..<*10),
OverlapsTest(expected: true, lhs: 0...*6, rhs: 5...*10),
// 0-20, 5-10
OverlapsTest(expected: true, lhs: 0..<*20, rhs: 5..<*10),
OverlapsTest(expected: true, lhs: 0..<*20, rhs: 5...*10),
OverlapsTest(expected: true, lhs: 0...*20, rhs: 5..<*10),
OverlapsTest(expected: true, lhs: 0...*20, rhs: 5...*10),
// 0-0, 0-5
OverlapsTest(expected: false, lhs: 0..<*0, rhs: 0..<*5),
OverlapsTest(expected: false, lhs: 0..<*0, rhs: 0...*5),
]
struct ClampedTest {
let expected: Range<Int>
let subject: Range<Int>
let limits: Range<Int>
let loc: SourceLoc
init(
expected: Range<Int>,
subject: Range<Int>,
limits: Range<Int>,
file: String = #file, line: UInt = #line
) {
self.expected = expected
self.subject = subject
self.limits = limits
self.loc = SourceLoc(file, line, comment: "test data")
}
}
let clampedTests: [ClampedTest] = [
ClampedTest(expected: 5..<5, subject: 0..<3, limits: 5..<10),
ClampedTest(expected: 5..<9, subject: 0..<9, limits: 5..<10),
ClampedTest(expected: 5..<10, subject: 0..<13, limits: 5..<10),
ClampedTest(expected: 7..<9, subject: 7..<9, limits: 5..<10),
ClampedTest(expected: 7..<10, subject: 7..<13, limits: 5..<10),
ClampedTest(expected: 10..<10, subject: 13..<15, limits: 5..<10),
]
struct OffsetByTest {
static let start: Int = 10
static let end: Int = 20
let advanceBy: Int
/// Instead of advancing from `startIndex`, advance from `endIndex`.
let advanceFromEnd: Bool
// `nil` if the test should fail for open ranges.
let newOpenIndex: Int?
// `nil` if the test should fail for closed ranges.
let newClosedIndex: Int?
let loc: SourceLoc
init(
advanceBy: Int, newOpenIndex: Int? = nil, newClosedIndex: Int? = nil,
advanceFromEnd: Bool = false, file: String = #file, line: UInt = #line
) {
self.advanceBy = advanceBy
self.newOpenIndex = newOpenIndex
self.newClosedIndex = newClosedIndex
self.advanceFromEnd = advanceFromEnd
self.loc = SourceLoc(file, line, comment: "index(_:offsetBy:) tests for countable ranges")
}
}
let offsetByTests: [OffsetByTest] = [
// Move forward, valid.
OffsetByTest(advanceBy: 4, newOpenIndex: 14, newClosedIndex: 14),
OffsetByTest(advanceBy: 0, newOpenIndex: 10, newClosedIndex: 10),
OffsetByTest(advanceBy: 10, newOpenIndex: 20, newClosedIndex: 20),
OffsetByTest(advanceBy: 11, newClosedIndex: 21),
// Move forward, invalid.
OffsetByTest(advanceBy: 12),
OffsetByTest(advanceBy: 25600),
// Move backward, valid.
OffsetByTest(advanceBy: -9, newOpenIndex: 11, newClosedIndex: 12,
advanceFromEnd: true),
OffsetByTest(advanceBy: -10, newOpenIndex: 10, newClosedIndex: 11,
advanceFromEnd: true),
OffsetByTest(advanceBy: -11, newClosedIndex: 10,
advanceFromEnd: true),
OffsetByTest(advanceBy: 0, newOpenIndex: 20, newClosedIndex: 21,
advanceFromEnd: true),
// Move backward, invalid.
OffsetByTest(advanceBy: -12, advanceFromEnd: true),
OffsetByTest(advanceBy: -2885, advanceFromEnd: true),
]
struct IndexDistanceTest {
static let start: Int = 10
static let end: Int = 30
// Leave either of these `nil` to use the 'right edge index', which differs
// between open and closed ranges.
let leftIndex: Int?
let rightIndex: Int?
let loc: SourceLoc
let openDistance: Int
let closedDistance: Int
/// Return the index `leftIndex` steps away from `start`, or the collection's
/// `endIndex` if `leftIndex` is `nil`.
func leftIndex<C: RandomAccessCollection>(of c: C) -> C.Index {
guard let leftIndex = leftIndex else {
return c.endIndex
}
let iterations = leftIndex - IndexDistanceTest.start
var idx = c.startIndex
for _ in 0..<iterations {
idx = c.index(after: idx)
}
return idx
}
/// Return the index `rightIndex` steps away from `start`, or the
/// collection's `endIndex` if `rightIndex` is `nil`.
func rightIndex<C: RandomAccessCollection>(of c: C) -> C.Index {
guard let rightIndex = rightIndex else {
return c.endIndex
}
let iterations = rightIndex - IndexDistanceTest.start
precondition(iterations >= 0)
var idx = c.startIndex
for _ in 0..<iterations {
idx = c.index(after: idx)
}
return idx
}
init(
leftIndex: Int? = nil, rightIndex: Int? = nil,
distance: Int, closedDistance: Int? = nil,
file: String = #file, line: UInt = #line
) {
self.leftIndex = leftIndex
self.rightIndex = rightIndex
self.openDistance = distance
// Only set closedDistance if testing against someRange.endIndex.
if closedDistance != nil {
precondition(leftIndex == nil || rightIndex == nil)
}
self.closedDistance = closedDistance ?? distance
self.loc = SourceLoc(file, line, comment: "distance(from:to:) tests for countable ranges")
}
}
let indexDistanceTests: [IndexDistanceTest] = [
// Forward, inside bounds.
IndexDistanceTest(leftIndex: 10, rightIndex: 29, distance: 19),
IndexDistanceTest(leftIndex: 15, rightIndex: 15, distance: 0),
IndexDistanceTest(leftIndex: 16, rightIndex: 29, distance: 13),
// Forward, at edge.
IndexDistanceTest(leftIndex: 10, distance: 20, closedDistance: 21),
IndexDistanceTest(leftIndex: 21, distance: 9, closedDistance: 10),
IndexDistanceTest(leftIndex: IndexDistanceTest.end, distance: 0, closedDistance: 1),
// Backwards, inside bounds.
IndexDistanceTest(leftIndex: 27, rightIndex: 16, distance: -11),
IndexDistanceTest(leftIndex: 15, rightIndex: 14, distance: -1),
// Backwards, at edge.
IndexDistanceTest(rightIndex: 18, distance: -12, closedDistance: -13),
IndexDistanceTest(rightIndex: 10, distance: -20, closedDistance: -21),
IndexDistanceTest(rightIndex: IndexDistanceTest.end, distance: 0, closedDistance: -1),
// Completely at edge.
IndexDistanceTest(distance: 0),
]
%{
all_range_types = [
('Range', '..<', 'MinimalComparableValue', ''),
('Range', '..<', 'MinimalStrideableValue', 'Countable'),
('ClosedRange', '...', 'MinimalComparableValue', ''),
('ClosedRange', '...', 'MinimalStrideableValue', 'Countable'),
]
}%
% for (Self, op, Bound, Countable) in all_range_types:
% TestSuite = Countable + Self + 'TestSuite'
% if Countable == '':
// Check that the generic parameter is called 'Bound'.
extension ${Self} where Bound : TestProtocol1 {
var _elementIsTestProtocol1: Bool {
fatalError("not implemented")
}
}
% end
var ${TestSuite} = TestSuite("${Self}")
${TestSuite}.test("init(uncheckedBounds:)")
.forEach(in: [(1, 2), (1, 1)]) {
(lowerInt, upperInt) in
let r = ${Self}(
uncheckedBounds: (lower: ${Bound}(lowerInt), upper: ${Bound}(upperInt)))
expectEqual(lowerInt, r.lowerBound.value)
expectEqual(upperInt, r.upperBound.value)
}
if #available(macOS 11.3, iOS 14.5, tvOS 14.5, watchOS 7.4, *) {
// Debug check was introduced in https://github.com/apple/swift/pull/34961
${TestSuite}.test("init(uncheckedBounds:) with invalid values")
.xfail(
.custom(
{ _isDebugAssertConfiguration() },
reason: "unchecked initializer still checks its input in Debug mode"))
.code {
let low = 2
let up = 1
// Check that 'init(uncheckedBounds:)' does not perform precondition checks,
// allowing to create ranges that break invariants.
let r = ${Self}(
uncheckedBounds: (lower: ${Bound}(low), upper: ${Bound}(up)))
expectEqual(low, r.lowerBound.value)
expectEqual(up, r.upperBound.value)
}
}
% for (DestinationSelf, _, _, _) in all_range_types:
${TestSuite}.test("init(${DestinationSelf})/whereBoundIsStrideable")
.forEach(in: [(0, 0), (1, 2), (10, 20), (Int.min, Int.max)]) {
(lowerInt, upperInt) in
let lower = MinimalStrideableValue(lowerInt)
let upper = MinimalStrideableValue(upperInt)
let source: ${Self}<MinimalStrideableValue> = lower${op}upper
let isSourceHalfOpen = ${Self}<MinimalStrideableValue>._isHalfOpen
let isDestinationHalfOpen =
${DestinationSelf}<MinimalStrideableValue>._isHalfOpen
let shouldTrap =
(source.isEmpty && !isDestinationHalfOpen) ||
(upperInt == Int.max && !isSourceHalfOpen && isDestinationHalfOpen)
if shouldTrap {
expectCrashLater()
}
let converted = ${DestinationSelf}(source)
if !shouldTrap {
expectEqual(lower.value, converted.lowerBound.value)
expectEqual(
upper.value + ((isSourceHalfOpen ? 0 : 1) - (isDestinationHalfOpen ? 0 : 1)),
converted.upperBound.value)
}
}
% end
${TestSuite}.test("lowerBound, upperBound") {
let _1 = ${Bound}(1, identity: 1010)
let _2 = ${Bound}(2, identity: 2020)
let range: ${Self}<${Bound}> = _1${op}_2
expectEqual(1, range.lowerBound.value)
expectEqual(1010, range.lowerBound.identity)
expectEqual(2, range.upperBound.value)
expectEqual(2020, range.upperBound.identity)
}
${TestSuite}.test("Equatable") {
let _1 = ${Bound}(1)
let _2 = ${Bound}(2)
let instances: [${Self}<${Bound}>] = [
_1${op}_1,
_1${op}_2,
_2${op}_2,
]
checkEquatable(instances, oracle: { $0 == $1 })
}
${TestSuite}.test("'${op}' traps when upperBound < lowerBound")
.crashOutputMatches(_isDebugAssertConfiguration() ?
"Range requires lowerBound <= upperBound" : "")
.code {
let _1 = ${Bound}(1)
let _2 = ${Bound}(2)
expectCrashLater()
let range: ${Self}<${Bound}> = _2${op}_1
_blackHole(range)
}
${TestSuite}.test("contains(_:)/staticDispatch") {
let start = ${Bound}(10)
let end = ${Bound}(20)
let range: ${Self}<${Bound}> = start${op}end
expectEqual(1, ${Bound}.timesLessWasCalled.value)
for test in 0..<30 {
% if 'Closed' in Self:
let expected = test >= start.value && test <= end.value
% else:
let expected = test >= start.value && test < end.value
% end
expectEqual(
expected, range.contains(${Bound}(test)),
"test=\(test)")
}
expectEqual(51, ${Bound}.timesLessWasCalled.value)
}
${TestSuite}.test("~=/staticDispatch") {
let start = ${Bound}(10)
let end = ${Bound}(20)
let range: ${Self}<${Bound}> = start${op}end
expectEqual(1, ${Bound}.timesLessWasCalled.value)
for test in 0..<30 {
% if 'Closed' in Self:
let expected = test >= start.value && test <= end.value
% else:
let expected = test >= start.value && test < end.value
% end
expectEqual(
expected, range ~= ${Bound}(test),
"test=\(test)")
}
expectEqual(51, ${Bound}.timesLessWasCalled.value)
}
% if 'Strideable' in Bound:
${TestSuite}.test("contains(_:)/dynamicDispatch") {
let start = ${Bound}(10)
let end = ${Bound}(20)
let range: ${Self}<${Bound}> = start${op}end
let loggingRange = LoggingCollection(wrapping: range)
expectEqual(1, ${Bound}.timesLessWasCalled.value)
for test in 0..<30 {
% if 'Closed' in Self:
let expected = test >= start.value && test <= end.value
% else:
let expected = test >= start.value && test < end.value
% end
expectEqual(
expected, loggingRange.contains(${Bound}(test)),
"test=\(test)")
}
expectEqual(51, MinimalStrideableValue.timesLessWasCalled.value)
}
% end
${TestSuite}.test("contains(_:)/semantics, ~=/semantics")
.forEach(in: containsTests) {
(test) in
// Check both static and dynamic dispatch.
let range: ${Self}<${Bound}> = ${Bound}(test.lowerBound)${op}${Bound}(test.upperBound)
% if 'Strideable' in Bound:
let loggingRange = LoggingCollection(wrapping: range)
% else:
let loggingRange = range
% end
let value = ${Bound}(test.value)
let expected =
${Self}<${Bound}>._isHalfOpen
? test.containedInHalfOpen
: test.containedInClosed
expectEqual(expected, range.contains(value))
expectEqual(expected, loggingRange.contains(value))
expectEqual(expected, range ~= value)
}
% for (OtherSelf, other_op, _, _) in all_range_types:
${TestSuite}.test("overlaps(${OtherSelf})/semantics")
.forEach(in: overlapsTests) {
(test) in
if test.lhs.isHalfOpen != ${Self}<${Bound}>._isHalfOpen ||
test.rhs.isHalfOpen != ${OtherSelf}<${Bound}>._isHalfOpen {
return
}
let lhs: ${Self}<${Bound}>
= ${Bound}(test.lhs.lowerBound)${op}${Bound}(test.lhs.upperBound)
let rhs: ${OtherSelf}<${Bound}>
= ${Bound}(test.rhs.lowerBound)${other_op}${Bound}(test.rhs.upperBound)
expectEqual(test.expected, lhs.overlaps(rhs))
expectEqual(test.expected, rhs.overlaps(lhs))
expectEqual(!lhs.isEmpty, lhs.overlaps(lhs))
expectEqual(!rhs.isEmpty, rhs.overlaps(rhs))
}
// ClosedRange.contains(_: Range) is only available for stridable bounds
% if Self == 'Range' or (Self == 'ClosedRange' and OtherSelf == 'ClosedRange') or 'Stridable' in Bound:
${TestSuite}.test("contains(${OtherSelf})/semantics")
.forEach(in: containsRangeTests) {
(test) in
if test.lhs.isHalfOpen != ${Self}<${Bound}>._isHalfOpen ||
test.rhs.isHalfOpen != ${OtherSelf}<${Bound}>._isHalfOpen {
return
}
let lhs: ${Self}<${Bound}>
= ${Bound}(test.lhs.lowerBound)${op}${Bound}(test.lhs.upperBound)
let rhs: ${OtherSelf}<${Bound}>
= ${Bound}(test.rhs.lowerBound)${other_op}${Bound}(test.rhs.upperBound)
expectEqual(test.expected, lhs.contains(rhs))
}
% end
% end
${TestSuite}.test("clamped(to:)/semantics")
.forEach(in: clampedTests) {
(test) in
let subject: ${Self}<${Bound}>
= ${Bound}(test.subject.lowerBound)${op}${Bound}(test.subject.upperBound)
let limits: ${Self}<${Bound}>
= ${Bound}(test.limits.lowerBound)${op}${Bound}(test.limits.upperBound)
expectEqual(
${Bound}(test.expected.lowerBound)${op}${Bound}(test.expected.upperBound),
subject.clamped(to: limits))
}
${TestSuite}.test("count/whereBoundIsStrideable") {
typealias Bound = MinimalStrideableValue
let start = Bound(10)
let end = Bound(20)
let range1: ${Self}<Bound> = start${op}start
let range2: ${Self}<Bound> = start${op}end
expectEqual(0, Bound.timesEqualEqualWasCalled.value)
expectEqual(2, Bound.timesLessWasCalled.value)
expectEqual(0, Bound.timesDistanceWasCalled.value)
expectEqual(0, Bound.timesAdvancedWasCalled.value)
expectEqual( 0 + (${Self}<Bound>._isHalfOpen ? 0 : 1), range1.count)
expectEqual(10 + (${Self}<Bound>._isHalfOpen ? 0 : 1), range2.count)
expectEqual(0, Bound.timesEqualEqualWasCalled.value)
expectEqual(2, Bound.timesLessWasCalled.value)
expectEqual(2, Bound.timesDistanceWasCalled.value)
expectEqual(0, Bound.timesAdvancedWasCalled.value)
}
% if 'Strideable' in Bound:
${TestSuite}.test("index(after:)/semantics").forEach(in: [3, 5, 10, 12, 86]) {
(endValue) in
let start = ${Bound}(0)
let end = ${Bound}(endValue)
let range: ${Self}<${Bound}> = start${op}end
var idx = range.startIndex
var previousValue: Int?
for _ in 0${op}(endValue - 1) {
// Advance the index to the last in-range position.
idx = range.index(after: idx)
if let previousValue = previousValue {
expectEqual(range[idx].value, previousValue + 1)
}
previousValue = range[idx].value
}
// Iterate once more to get to the 'after end' position.
idx = range.index(after: idx)
expectEqual(idx, range.endIndex)
// Now, iterate once more and expect a crash.
expectCrashLater()
_ = range.index(after: idx)
}
${TestSuite}.test("index(after:)/semantics/smallestRange") {
let range: ${Self}<${Bound}> = ${Bound}(0)${op}${Bound}(0)
var idx = range.startIndex
% if 'Closed' in Self:
// Should be able to advance the index once.
idx = range.index(after: idx)
% end
expectCrashLater()
_ = range.index(after: idx)
}
${TestSuite}.test("index(before:)/semantics").forEach(in: [3, 5, 10, 12, 86]) {
(endValue) in
let start = ${Bound}(0)
let end = ${Bound}(endValue)
let range: ${Self}<${Bound}> = start${op}end
var idx = range.endIndex
var previousValue: Int?
for _ in 0${op}(endValue) {
// Advance the index backwards until we reach `startIndex`.
// Precondition: for a..<b, advance endIndex (b - a) times to reach startIndex.
// Precondition: for a...b, advance endIndex (b - a) + 1 times to reach startIndex.
idx = range.index(before: idx)
if let previousValue = previousValue {
expectEqual(range[idx].value, previousValue - 1)
}
previousValue = range[idx].value
}
// At this point, we should be at `startIndex`.
expectEqual(idx, range.startIndex)
// Now, iterate once more and expect a crash.
expectCrashLater()
_ = range.index(before: idx)
}
${TestSuite}.test("index(before:)/semantics/smallestRange") {
let range: ${Self}<${Bound}> = ${Bound}(0)${op}${Bound}(0)
var idx = range.endIndex
% if 'Closed' in Self:
// Should be able to advance the index once.
idx = range.index(before: idx)
% end
expectCrashLater()
_ = range.index(before: idx)
}
${TestSuite}.test("index(offsetBy:)/semantics").forEach(in: offsetByTests) {
(test) in
let start = ${Bound}(OffsetByTest.start)
let end = ${Bound}(OffsetByTest.end)
let range: ${Self}<${Bound}> = start${op}end
let initialIdx = test.advanceFromEnd ? range.endIndex : range.startIndex
% if 'Closed' in Self:
if let newIndex = test.newClosedIndex {
% else:
if let newIndex = test.newOpenIndex {
% end
let idx = range.index(initialIdx, offsetBy: test.advanceBy)
if idx != range.endIndex {
expectEqual(newIndex, range[idx].value, stackTrace: SourceLocStack().with(test.loc))
}
} else {
expectCrashLater()
_ = range.index(initialIdx, offsetBy: test.advanceBy)
}
}
${TestSuite}.test("index(offsetBy:)/semantics/smallestRange/forward") {
let range: ${Self}<${Bound}> = ${Bound}(0)${op}${Bound}(0)
var idx = range.startIndex
// This should work.
_ = range.index(idx, offsetBy: 0)
% if 'Closed' in Self:
// Can advance once more.
_ = range.index(idx, offsetBy: 1)
expectCrashLater()
_ = range.index(idx, offsetBy: 2)
% else:
expectCrashLater()
_ = range.index(idx, offsetBy: 1)
% end
}
${TestSuite}.test("index(offsetBy:)/semantics/smallestRange/backward") {
let range: ${Self}<${Bound}> = ${Bound}(0)${op}${Bound}(0)
var idx = range.endIndex
// This should work.
_ = range.index(idx, offsetBy: 0)
% if 'Closed' in Self:
// Can advance once more.
_ = range.index(idx, offsetBy: -1)
expectCrashLater()
_ = range.index(idx, offsetBy: -2)
% else:
expectCrashLater()
_ = range.index(idx, offsetBy: -1)
% end
}
${TestSuite}.test("distance(from:to:)/semantics") {
for test in indexDistanceTests {
let start = ${Bound}(IndexDistanceTest.start)
let end = ${Bound}(IndexDistanceTest.end)
let range: ${Self}<${Bound}> = start${op}end
let leftIdx = test.leftIndex(of: range)
let rightIdx = test.rightIndex(of: range)
let distance = range.distance(from: leftIdx, to: rightIdx)
% if 'Closed' in Self:
expectEqual(test.closedDistance, distance, stackTrace: SourceLocStack().with(test.loc))
% else:
expectEqual(test.openDistance, distance, stackTrace: SourceLocStack().with(test.loc))
% end
}
}
${TestSuite}.test("distance(from:to:)/semantics/smallestRange") {
let range: ${Self}<${Bound}> = ${Bound}(0)${op}${Bound}(0)
var idx = range.endIndex
% if 'Closed' in Self:
expectEqual(1, range.distance(from: range.startIndex, to: range.endIndex))
% else:
expectEqual(0, range.distance(from: range.startIndex, to: range.endIndex))
% end
}
% end
${TestSuite}.test("isEmpty") {
let start = ${Bound}(10)
let end = ${Bound}(20)
let range1: ${Self}<${Bound}> = start${op}start
let range2: ${Self}<${Bound}> = start${op}end
expectEqual(0, ${Bound}.timesEqualEqualWasCalled.value)
expectEqual(2, ${Bound}.timesLessWasCalled.value)
expectEqual(${Self}<${Bound}>._isHalfOpen, range1.isEmpty)
expectFalse(range2.isEmpty)
expectEqual(
${Self}<${Bound}>._isHalfOpen ? 2 : 0,
${Bound}.timesEqualEqualWasCalled.value)
expectEqual(2, ${Bound}.timesLessWasCalled.value)
% if Bound == 'MinimalStrideableValue':
expectEqual(0, ${Bound}.timesDistanceWasCalled.value)
expectEqual(0, ${Bound}.timesAdvancedWasCalled.value)
% end
}
${TestSuite}.test("CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable") {
var r: ${Self}<CustomPrintableValue> =
CustomPrintableValue(1)${op}CustomPrintableValue(2)
expectPrinted("(value: 1).description${op}(value: 2).description", r)
expectDebugPrinted(
"${Self}(" +
"(value: 1).debugDescription${op}(value: 2).debugDescription" +
")",
r)
expectDumped(
"▿ ${Self}((value: 1).debugDescription${op}(value: 2).debugDescription)\n" +
" ▿ lowerBound: (value: 1).debugDescription\n" +
" - value: 1\n" +
" - identity: 0\n" +
" ▿ upperBound: (value: 2).debugDescription\n" +
" - value: 2\n" +
" - identity: 0\n",
r)
}
% end
CountableRangeTestSuite.test("AssociatedTypes") {
typealias Collection = Range<MinimalStrideableValue>
expectCollectionAssociatedTypes(
collectionType: Collection.self,
iteratorType: Collection.Iterator.self,
subSequenceType: Collection.self,
indexType: MinimalStrideableValue.self,
indicesType: Collection.self)
}
CountableClosedRangeTestSuite.test("AssociatedTypes") {
typealias Collection = ClosedRange<MinimalStrideableValue>
expectCollectionAssociatedTypes(
collectionType: Collection.self,
iteratorType: Collection.Iterator.self,
subSequenceType: Slice<Collection>.self,
indexType: Collection.Index.self,
indicesType: DefaultIndices<Collection>.self)
}
var MiscTestSuite = TestSuite("Misc")
MiscTestSuite.test("map()") {
// <rdar://problem/17054014> map method should exist on ranges
var result = (1..<4).map { $0*2 }
expectType(Array<Int>.self, &result)
expectEqualSequence([ 2, 4, 6 ], result)
}
MiscTestSuite.test("reversed()") {
var result = (0..<10).lazy.reversed()
typealias Expected = ReversedCollection<
LazyCollection<Range<Int>>>
expectType(Expected.self, &result)
expectEqualSequence(
[ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ],
result)
}
MiscTestSuite.test("Compatibility typealiases") {
let _: CountablePartialRangeFrom = 1...
let _: CountableRange = MinimalStrideableValue(3)..<MinimalStrideableValue(3)
let _: CountableClosedRange = MinimalStrideableValue(3)...MinimalStrideableValue(3)
}
runAllTests()