@@ -66,3 +66,193 @@ func XCTAssertEqualSequences<S1: Sequence, S2: Sequence>(
66
66
}
67
67
68
68
func XCTAssertLazy< S: LazySequenceProtocol > ( _: S ) { }
69
+
70
+ /// Tests that all index traversal methods behave as expected.
71
+ ///
72
+ /// Verifies the correctness of the implementations of `startIndex`, `endIndex`,
73
+ /// `indices`, `count`, `isEmpty`, `index(before:)`, `index(after:)`,
74
+ /// `index(_:offsetBy:)`, `index(_:offsetBy:limitedBy:)`, and
75
+ /// `distance(from:to:)` by calling them with just about all possible input
76
+ /// combinations. When provided, the `indices` function is used to to test the
77
+ /// collection methods against.
78
+ ///
79
+ /// - Parameters:
80
+ /// - collections: The collections to be validated.
81
+ /// - indices: A closure that returns the expected indices of the given
82
+ /// collection, including its `endIndex`, in ascending order. Only use this
83
+ /// parameter if you are able to compute the indices of the collection
84
+ /// independently of the `Collection` conformance, e.g. by using the
85
+ /// contents of the collection directly.
86
+ ///
87
+ /// - Complexity: O(*n*^3) for each collection, where *n* is the length of the
88
+ /// collection.
89
+ func validateIndexTraversals< C> (
90
+ _ collections: C ... ,
91
+ indices: ( ( C ) -> [ C . Index ] ) ? = nil ,
92
+ file: StaticString = #file, line: UInt = #line
93
+ ) where C: BidirectionalCollection {
94
+ for c in collections {
95
+ let indicesIncludingEnd = indices ? ( c) ?? ( c. indices + [ c. endIndex] )
96
+ let count = indicesIncludingEnd. count - 1
97
+
98
+ XCTAssertEqual (
99
+ c. count, count,
100
+ " Count mismatch " ,
101
+ file: file, line: line)
102
+ XCTAssertEqual (
103
+ c. isEmpty, count == 0 ,
104
+ " Emptiness mismatch " ,
105
+ file: file, line: line)
106
+ XCTAssertEqual (
107
+ c. startIndex, indicesIncludingEnd. first,
108
+ " `startIndex` does not equal the first index " ,
109
+ file: file, line: line)
110
+ XCTAssertEqual (
111
+ c. endIndex, indicesIncludingEnd. last,
112
+ " `endIndex` does not equal the last index " ,
113
+ file: file, line: line)
114
+
115
+ // `index(after:)`
116
+ do {
117
+ var index = c. startIndex
118
+
119
+ for (offset, expected) in indicesIncludingEnd. enumerated ( ) . dropFirst ( ) {
120
+ c. formIndex ( after: & index)
121
+ XCTAssertEqual (
122
+ index, expected,
123
+ """
124
+ `startIndex` incremented \( offset) times does not equal index at \
125
+ offset \( offset)
126
+ """ ,
127
+ file: file, line: line)
128
+ }
129
+ }
130
+
131
+ // `index(before:)`
132
+ do {
133
+ var index = c. endIndex
134
+
135
+ for (offset, expected) in indicesIncludingEnd. enumerated ( ) . dropLast ( ) . reversed ( ) {
136
+ c. formIndex ( before: & index)
137
+ XCTAssertEqual (
138
+ index, expected,
139
+ """
140
+ `endIndex` decremented \( count - offset) times does not equal index \
141
+ at offset \( offset)
142
+ """ ,
143
+ file: file, line: line)
144
+ }
145
+ }
146
+
147
+ // `indices`
148
+ XCTAssertEqual ( c. indices. count, count)
149
+ for (offset, index) in c. indices. enumerated ( ) {
150
+ XCTAssertEqual (
151
+ index, indicesIncludingEnd [ offset] ,
152
+ " Index mismatch at offset \( offset) in `indices` " ,
153
+ file: file, line: line)
154
+ }
155
+
156
+ // index comparison
157
+ for (offsetA, a) in indicesIncludingEnd. enumerated ( ) {
158
+ XCTAssertEqual (
159
+ a, a,
160
+ " Index at offset \( offsetA) does not equal itself " ,
161
+ file: file, line: line)
162
+ XCTAssertFalse (
163
+ a < a,
164
+ " Index at offset \( offsetA) is less than itself " ,
165
+ file: file, line: line)
166
+
167
+ for (offsetB, b) in indicesIncludingEnd [ ..< offsetA] . enumerated ( ) {
168
+ XCTAssertNotEqual (
169
+ a, b,
170
+ " Index at offset \( offsetA) equals index at offset \( offsetB) " ,
171
+ file: file, line: line)
172
+ XCTAssertLessThan (
173
+ b, a,
174
+ """
175
+ Index at offset \( offsetB) is not less than index at offset \( offsetA)
176
+ """ ,
177
+ file: file, line: line)
178
+ }
179
+ }
180
+
181
+ // `index(_:offsetBy:)` and `distance(from:to:)`
182
+ for (startOffset, start) in indicesIncludingEnd. enumerated ( ) {
183
+ for (endOffset, end) in indicesIncludingEnd. enumerated ( ) {
184
+ let distance = endOffset - startOffset
185
+
186
+ XCTAssertEqual (
187
+ c. index ( start, offsetBy: distance) , end,
188
+ """
189
+ Index at offset \( startOffset) offset by \( distance) does not equal \
190
+ index at offset \( endOffset)
191
+ """ ,
192
+ file: file, line: line)
193
+ XCTAssertEqual (
194
+ c. distance ( from: start, to: end) , distance,
195
+ """
196
+ Distance from index at offset \( startOffset) to index at offset \
197
+ \( endOffset) does not equal \( distance)
198
+ """ ,
199
+ file: file, line: line)
200
+ }
201
+ }
202
+
203
+ // `index(_:offsetBy:limitedBy:)`
204
+ for (startOffset, start) in indicesIncludingEnd. enumerated ( ) {
205
+ for (limitOffset, limit) in indicesIncludingEnd. enumerated ( ) {
206
+ // verifies that the target index corresponding to each offset in
207
+ // `range` can or cannot be reached from `start` using
208
+ // `chain.index(start, offsetBy: _, limitedBy: limit)`, depending on the
209
+ // value of `pastLimit`
210
+ func checkTargetRange( _ range: ClosedRange < Int > , pastLimit: Bool ) {
211
+ for targetOffset in range {
212
+ let distance = targetOffset - startOffset
213
+ let end = c. index ( start, offsetBy: distance, limitedBy: limit)
214
+
215
+ if pastLimit {
216
+ XCTAssertNil (
217
+ end,
218
+ """
219
+ Index at offset \( startOffset) offset by \( distance) limited \
220
+ by index at offset \( limitOffset) does not equal `nil`
221
+ """ ,
222
+ file: file, line: line)
223
+ } else {
224
+ XCTAssertEqual (
225
+ end, indicesIncludingEnd [ targetOffset] ,
226
+ """
227
+ Index at offset \( startOffset) offset by \( distance) limited \
228
+ by index at offset \( limitOffset) does not equal index at \
229
+ offset \( targetOffset)
230
+ """ ,
231
+ file: file, line: line)
232
+ }
233
+ }
234
+ }
235
+
236
+ // forward offsets
237
+ if limit >= start {
238
+ // the limit has an effect
239
+ checkTargetRange ( startOffset... limitOffset, pastLimit: false )
240
+ checkTargetRange ( ( limitOffset + 1 ) ... ( count + 1 ) , pastLimit: true )
241
+ } else {
242
+ // the limit has no effect
243
+ checkTargetRange ( startOffset... count, pastLimit: false )
244
+ }
245
+
246
+ // backward offsets
247
+ if limit <= start {
248
+ // the limit has an effect
249
+ checkTargetRange ( limitOffset... startOffset, pastLimit: false )
250
+ checkTargetRange ( - 1 ... ( limitOffset - 1 ) , pastLimit: true )
251
+ } else {
252
+ // the limit has no effect
253
+ checkTargetRange ( 0 ... startOffset, pastLimit: false )
254
+ }
255
+ }
256
+ }
257
+ }
258
+ }
0 commit comments