// RUN: %target-run-simple-swift // REQUIRES: executable_test // XFAIL: interpret import StdlibUnittest import StdlibCollectionUnittest #if _runtime(_ObjC) import Foundation // For NSRange #endif extension Collection { internal func index(_nth n: Int) -> Index { precondition(n >= 0) return index(startIndex, offsetBy: numericCast(n)) } internal func index(_nthLast n: Int) -> Index { precondition(n >= 0) return index(endIndex, offsetBy: -numericCast(n)) } } extension String { internal func index(_nth n: Int) -> Index { return characters.index(_nth: n) } internal func index(_nthLast n: Int) -> Index { return characters.index(_nthLast: n) } } extension String { var nativeCapacity: Int { precondition(_guts._isNative) return _guts.capacity } var capacity: Int { return _guts.capacity } var unusedCapacity: Int { return Swift.max(0, _guts.capacity - _guts.count) } var bufferID: ObjectIdentifier? { return _rawIdentifier() } func _rawIdentifier() -> ObjectIdentifier? { return _guts._objectIdentifier } } extension Substring { var bufferID: ObjectIdentifier? { return _wholeString.bufferID } } // A thin wrapper around _StringGuts implementing RangeReplaceableCollection struct StringGutsCollection: RangeReplaceableCollection, RandomAccessCollection { typealias Element = UTF16.CodeUnit typealias Index = Int typealias Indices = CountableRange init(_ guts: _StringGuts) { self._guts = guts } init() { self.init(_StringGuts()) } var _guts: _StringGuts var startIndex: Index { return 0 } var endIndex: Index { return _guts.count } var indices: Indices { return startIndex.. Element { return _guts[position] } mutating func replaceSubrange( _ subrange: Range, with newElements: C ) where C : Collection, C.Element == Element { _guts.replaceSubrange(subrange, with: newElements) } mutating func reserveCapacity(_ n: Int) { _guts.reserveCapacity(n) } } var StringTests = TestSuite("StringTests") StringTests.test("sizeof") { #if arch(i386) || arch(arm) expectEqual(12, MemoryLayout.size) #else expectEqual(16, MemoryLayout.size) #endif } StringTests.test("AssociatedTypes-UTF8View") { typealias View = String.UTF8View expectCollectionAssociatedTypes( collectionType: View.self, iteratorType: View.Iterator.self, subSequenceType: Substring.UTF8View.self, indexType: View.Index.self, indicesType: DefaultIndices.self) } StringTests.test("AssociatedTypes-UTF16View") { typealias View = String.UTF16View expectCollectionAssociatedTypes( collectionType: View.self, iteratorType: IndexingIterator.self, subSequenceType: Substring.UTF16View.self, indexType: View.Index.self, indicesType: View.Indices.self) } StringTests.test("AssociatedTypes-UnicodeScalarView") { typealias View = String.UnicodeScalarView expectCollectionAssociatedTypes( collectionType: View.self, iteratorType: View.Iterator.self, subSequenceType: Substring.UnicodeScalarView.self, indexType: View.Index.self, indicesType: DefaultIndices.self) } StringTests.test("AssociatedTypes-CharacterView") { typealias View = String.CharacterView expectCollectionAssociatedTypes( collectionType: View.self, iteratorType: IndexingIterator.self, subSequenceType: View.self, indexType: View.Index.self, indicesType: DefaultIndices.self) } func checkUnicodeScalarViewIteration( _ expectedScalars: [UInt32], _ str: String ) { do { let us = str.unicodeScalars var i = us.startIndex let end = us.endIndex var decoded: [UInt32] = [] while i != end { expectTrue(i < us.index(after: i)) // Check for Comparable conformance decoded.append(us[i].value) i = us.index(after: i) } expectEqual(expectedScalars, decoded) } do { let us = str.unicodeScalars let start = us.startIndex var i = us.endIndex var decoded: [UInt32] = [] while i != start { i = us.index(before: i) decoded.append(us[i].value) } expectEqual(expectedScalars, decoded) } } StringTests.test("unicodeScalars") { checkUnicodeScalarViewIteration([], "") checkUnicodeScalarViewIteration([ 0x0000 ], "\u{0000}") checkUnicodeScalarViewIteration([ 0x0041 ], "A") checkUnicodeScalarViewIteration([ 0x007f ], "\u{007f}") checkUnicodeScalarViewIteration([ 0x0080 ], "\u{0080}") checkUnicodeScalarViewIteration([ 0x07ff ], "\u{07ff}") checkUnicodeScalarViewIteration([ 0x0800 ], "\u{0800}") checkUnicodeScalarViewIteration([ 0xd7ff ], "\u{d7ff}") checkUnicodeScalarViewIteration([ 0x8000 ], "\u{8000}") checkUnicodeScalarViewIteration([ 0xe000 ], "\u{e000}") checkUnicodeScalarViewIteration([ 0xfffd ], "\u{fffd}") checkUnicodeScalarViewIteration([ 0xffff ], "\u{ffff}") checkUnicodeScalarViewIteration([ 0x10000 ], "\u{00010000}") checkUnicodeScalarViewIteration([ 0x10ffff ], "\u{0010ffff}") } StringTests.test("Index/Comparable") { let empty = "" expectTrue(empty.startIndex == empty.endIndex) expectFalse(empty.startIndex != empty.endIndex) expectTrue(empty.startIndex <= empty.endIndex) expectTrue(empty.startIndex >= empty.endIndex) expectFalse(empty.startIndex > empty.endIndex) expectFalse(empty.startIndex < empty.endIndex) let nonEmpty = "borkus biqualificated" expectFalse(nonEmpty.startIndex == nonEmpty.endIndex) expectTrue(nonEmpty.startIndex != nonEmpty.endIndex) expectTrue(nonEmpty.startIndex <= nonEmpty.endIndex) expectFalse(nonEmpty.startIndex >= nonEmpty.endIndex) expectFalse(nonEmpty.startIndex > nonEmpty.endIndex) expectTrue(nonEmpty.startIndex < nonEmpty.endIndex) } StringTests.test("Index/Hashable") { let s = "abcdef" let t = Set(s.indices) expectEqual(s.count, t.count) expectTrue(t.contains(s.startIndex)) } StringTests.test("ForeignIndexes/Valid") { // It is actually unclear what the correct behavior is. This test is just a // change detector. // // Design, document, implement invalidation model // for foreign String indexes do { let donor = "abcdef" let acceptor = "uvwxyz" expectEqual("u", acceptor[donor.startIndex]) expectEqual("wxy", acceptor[donor.index(_nth: 2).. String.Index caches the grapheme " + "cluster size, but it is not always correct to use")) .code { let donor = "\u{1f601}\u{1f602}\u{1f603}" let acceptor = "abcdef" // Adjust donor.startIndex to ensure it caches a stride let start = donor.index(before: donor.index(after: donor.startIndex)) // FIXME: this traps right now when trying to construct Character("ab"). expectEqual("a", acceptor[start]) } StringTests.test("ForeignIndexes/subscript(Index)/OutOfBoundsTrap") { let donor = "abcdef" let acceptor = "uvw" expectEqual("u", acceptor[donor.index(_nth: 0)]) expectEqual("v", acceptor[donor.index(_nth: 1)]) expectEqual("w", acceptor[donor.index(_nth: 2)]) let i = donor.index(_nth: 3) expectCrashLater() _ = acceptor[i] } StringTests.test("String/subscript(_:Range)") { let s = "foobar" let from = s.startIndex let to = s.index(before: s.endIndex) let actual = s[from.. size || sliceEnd > size || sliceEnd < sliceStart { continue } var s0 = String(repeating: "x", count: size) let originalIdentity = s0.bufferID s0 = s0[s0.index(_nth: sliceStart).. size || sliceEnd > size || sliceEnd < sliceStart { continue } let s0 = String(repeating: "x", count: size) let originalIdentity = s0.bufferID let s1 = Substring( _base: s0, s0.index(_nth: sliceStart).. initialSize || sliceEnd > initialSize || sliceEnd < sliceStart { continue } var s0 = String(repeating: "x", count: initialSize) s0 = s0[s0.index(_nth: sliceStart).. (String, Int) { var s0 = String(repeating: "x", count: 17) if s0.unusedCapacity == 0 { s0 += "y" } let cap = s0.unusedCapacity expectNotEqual(0, cap) // This sorta checks for the original bug expectEqual( cap, s0[s0.index(_nth: 1)..(String, Int) in let (s0, unused) = stringWithUnusedCapacity() return (s0[s0.index(_nth: 5)..(Substring, Int) in let (s0, unused) = stringWithUnusedCapacity() return (s0[s0.index(_nth: 5)..(Substring, Int) in let (s0, unused) = stringWithUnusedCapacity() return (s0[...], unused) }() let originalID = s.bufferID s += "z" expectEqual(originalID, s.bufferID) s += String(repeating: "z", count: unused - 1) expectEqual(originalID, s.bufferID) s += "." expectNotEqual(originalID, s.bufferID) unused += 0 // warning suppression } } StringTests.test("COW/removeSubrange/start") { var str = "12345678" let literalIdentity = str.bufferID // Check literal-to-heap reallocation. do { let slice = str expectEqual(literalIdentity, str.bufferID) expectEqual(literalIdentity, slice.bufferID) expectEqual("12345678", str) expectEqual("12345678", slice) // This mutation should reallocate the string. str.removeSubrange(str.startIndex..(_ content: S) -> String where S.Iterator.Element == Character { var s = String() s.append(contentsOf: content) expectTrue(s._guts.isSingleByte) return s } StringTests.test("stringGutsExtensibility") .skip(.nativeRuntime("Foundation dependency")) .code { #if _runtime(_ObjC) let ascii = UTF16.CodeUnit(UnicodeScalar("X").value) let nonAscii = UTF16.CodeUnit(UnicodeScalar("é").value) for k in 0..<3 { for count in 1..<16 { for boundary in 0.. _StringGuts { var x = _StringGuts() // make sure some - but not all - replacements will have to grow the buffer x.reserveCapacity(base._guts.count * 3 / 2) let capacity = x.capacity x.append(base._guts) // Widening the guts should not make it lose its capacity, // but the allocator may decide to get more storage. expectGE(x.capacity, capacity) return x } StringTests.test("StringGutsReplace") { let narrow = "01234567890" let wide = "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪ" for s1 in [narrow, wide] { for s2 in [narrow, wide] { let g1 = makeStringGuts(s1) let g2 = makeStringGuts(s2 + s2) checkRangeReplaceable( { StringGutsCollection(g1) }, { StringGutsCollection(g2)[0..<$0] } ) checkRangeReplaceable( { StringGutsCollection(g1) }, { Array(StringGutsCollection(g2))[0..<$0] } ) } } } StringTests.test("CharacterViewReplace") { let narrow = "01234567890" let wide = "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪ" for s1 in [narrow, wide] { for s2 in [narrow, wide] { let g1 = makeStringGuts(s1) let g2 = makeStringGuts(s2 + s2) checkRangeReplaceable( { () -> String._CharacterView in String._CharacterView(String(g1)) }, { String._CharacterView(String(g2._extractSlice(0..<$0))) } ) checkRangeReplaceable( { String._CharacterView(String(g1)) }, { Array(String._CharacterView(String(g2)))[0..<$0] } ) } } } StringTests.test("UnicodeScalarViewReplace") { let narrow = "01234567890" let wide = "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪ" for s1 in [narrow, wide] { for s2 in [narrow, wide] { checkRangeReplaceable( { String(makeStringGuts(s1)).unicodeScalars }, { String(makeStringGuts(s2 + s2)._extractSlice(0..<$0)).unicodeScalars } ) checkRangeReplaceable( { String(makeStringGuts(s1)).unicodeScalars }, { Array(String(makeStringGuts(s2 + s2)).unicodeScalars)[0..<$0] } ) } } } StringTests.test("reserveCapacity") { var s = "" let id0 = s.bufferID let oldCap = s.capacity let x: Character = "x" // Help the typechecker - s.insert(contentsOf: repeatElement(x, count: s.capacity + 1), at: s.endIndex) expectNotEqual(id0, s.bufferID) s = "" print("empty capacity \(s.capacity)") s.reserveCapacity(oldCap + 2) print("reserving \(oldCap + 2) -> \(s.capacity), width = \(s._guts.byteWidth)") let id1 = s.bufferID s.insert(contentsOf: repeatElement(x, count: oldCap + 2), at: s.endIndex) print("extending by \(oldCap + 2) -> \(s.capacity), width = \(s._guts.byteWidth)") expectEqual(id1, s.bufferID) s.insert(contentsOf: repeatElement(x, count: s.capacity + 100), at: s.endIndex) expectNotEqual(id1, s.bufferID) } StringTests.test("toInt") { expectNil(Int("")) expectNil(Int("+")) expectNil(Int("-")) expectOptionalEqual(20, Int("+20")) expectOptionalEqual(0, Int("0")) expectOptionalEqual(-20, Int("-20")) expectNil(Int("-cc20")) expectNil(Int(" -20")) expectNil(Int(" \t 20ddd")) expectOptionalEqual(Int.min, Int("\(Int.min)")) expectOptionalEqual(Int.min + 1, Int("\(Int.min + 1)")) expectOptionalEqual(Int.max, Int("\(Int.max)")) expectOptionalEqual(Int.max - 1, Int("\(Int.max - 1)")) expectNil(Int("\(Int.min)0")) expectNil(Int("\(Int.max)0")) // Make a String from an Int, mangle the String's characters, // then print if the new String is or is not still an Int. func testConvertabilityOfStringWithModification( _ initialValue: Int, modification: (_ chars: inout [UTF8.CodeUnit]) -> Void ) { var chars = Array(String(initialValue).utf8) modification(&chars) let str = String._fromWellFormedCodeUnitSequence(UTF8.self, input: chars) expectNil(Int(str)) } testConvertabilityOfStringWithModification(Int.min) { $0[2] += 1; () // underflow by lots } testConvertabilityOfStringWithModification(Int.max) { $0[1] += 1; () // overflow by lots } // Test values lower than min. do { let base = UInt(Int.max) expectOptionalEqual(Int.min + 1, Int("-\(base)")) expectOptionalEqual(Int.min, Int("-\(base + 1)")) for i in 2..<20 { expectNil(Int("-\(base + UInt(i))")) } } // Test values greater than min. do { let base = UInt(Int.max) for i in UInt(0)..<20 { expectOptionalEqual(-Int(base - i) , Int("-\(base - i)")) } } // Test values greater than max. do { let base = UInt(Int.max) expectOptionalEqual(Int.max, Int("\(base)")) for i in 1..<20 { expectNil(Int("\(base + UInt(i))")) } } // Test values lower than max. do { let base = Int.max for i in 0..<20 { expectOptionalEqual(base - i, Int("\(base - i)")) } } } // Make sure strings don't grow unreasonably quickly when appended-to StringTests.test("growth") { var s = "" var s2 = s for _ in 0..<20 { s += "x" s2 = s } expectEqual(s2, s) expectLE(s.nativeCapacity, 34) } StringTests.test("Construction") { expectEqual("abc", String(["a", "b", "c"] as [Character])) } StringTests.test("Conversions") { do { let c: Character = "a" let x = String(c) expectTrue(x._guts.isASCII) let s: String = "a" expectEqual(s, x) } do { let c: Character = "\u{B977}" let x = String(c) expectFalse(x._guts.isASCII) let s: String = "\u{B977}" expectEqual(s, x) } } // Check the internal functions are correct for ASCII values StringTests.test( "forall x: Int8, y: Int8 . x < 128 ==> x (_ s: String, as codec: C.Type) -> (result: String, repairsMade: Bool)? { let units = s.unicodeScalars.map({ $0.value }) + [0] return units.map({ C.CodeUnit($0) }).withUnsafeBufferPointer { String.decodeCString($0.baseAddress, as: C.self) } } StringTests.test("String.decodeCString/UTF8") { let actual = decodeCString("foobar", as: UTF8.self) expectFalse(actual!.repairsMade) expectEqual("foobar", actual!.result) } StringTests.test("String.decodeCString/UTF16") { let actual = decodeCString("foobar", as: UTF16.self) expectFalse(actual!.repairsMade) expectEqual("foobar", actual!.result) } StringTests.test("String.decodeCString/UTF32") { let actual = decodeCString("foobar", as: UTF32.self) expectFalse(actual!.repairsMade) expectEqual("foobar", actual!.result) } internal struct ReplaceSubrangeTest { let original: String let newElements: String let rangeSelection: RangeSelection let expected: String let closedExpected: String? let loc: SourceLoc internal init( original: String, newElements: String, rangeSelection: RangeSelection, expected: String, closedExpected: String? = nil, file: String = #file, line: UInt = #line ) { self.original = original self.newElements = newElements self.rangeSelection = rangeSelection self.expected = expected self.closedExpected = closedExpected self.loc = SourceLoc(file, line, comment: "replaceSubrange() test data") } } internal struct RemoveSubrangeTest { let original: String let rangeSelection: RangeSelection let expected: String let closedExpected: String let loc: SourceLoc internal init( original: String, rangeSelection: RangeSelection, expected: String, closedExpected: String? = nil, file: String = #file, line: UInt = #line ) { self.original = original self.rangeSelection = rangeSelection self.expected = expected self.closedExpected = closedExpected ?? expected self.loc = SourceLoc(file, line, comment: "replaceSubrange() test data") } } let replaceSubrangeTests = [ ReplaceSubrangeTest( original: "", newElements: "", rangeSelection: .emptyRange, expected: "" ), ReplaceSubrangeTest( original: "", newElements: "meela", rangeSelection: .emptyRange, expected: "meela" ), ReplaceSubrangeTest( original: "eela", newElements: "m", rangeSelection: .leftEdge, expected: "meela", closedExpected: "mela" ), ReplaceSubrangeTest( original: "meel", newElements: "a", rangeSelection: .rightEdge, expected: "meela", closedExpected: "meea" ), ReplaceSubrangeTest( original: "a", newElements: "meel", rangeSelection: .leftEdge, expected: "meela", closedExpected: "meel" ), ReplaceSubrangeTest( original: "m", newElements: "eela", rangeSelection: .rightEdge, expected: "meela", closedExpected: "eela" ), ReplaceSubrangeTest( original: "alice", newElements: "bob", rangeSelection: .offsets(1, 1), expected: "aboblice", closedExpected: "abobice" ), ReplaceSubrangeTest( original: "alice", newElements: "bob", rangeSelection: .offsets(1, 2), expected: "abobice", closedExpected: "abobce" ), ReplaceSubrangeTest( original: "alice", newElements: "bob", rangeSelection: .offsets(1, 3), expected: "abobce", closedExpected: "abobe" ), ReplaceSubrangeTest( original: "alice", newElements: "bob", rangeSelection: .offsets(1, 4), expected: "abobe", closedExpected: "abob" ), ReplaceSubrangeTest( original: "alice", newElements: "bob", rangeSelection: .offsets(1, 5), expected: "abob" ), ReplaceSubrangeTest( original: "bob", newElements: "meela", rangeSelection: .offsets(1, 2), expected: "bmeelab", closedExpected: "bmeela" ), ] let removeSubrangeTests = [ RemoveSubrangeTest( original: "", rangeSelection: .emptyRange, expected: "" ), RemoveSubrangeTest( original: "a", rangeSelection: .middle, expected: "" ), RemoveSubrangeTest( original: "perdicus", rangeSelection: .leftHalf, expected: "icus" ), RemoveSubrangeTest( original: "perdicus", rangeSelection: .rightHalf, expected: "perd" ), RemoveSubrangeTest( original: "alice", rangeSelection: .middle, expected: "ae" ), RemoveSubrangeTest( original: "perdicus", rangeSelection: .middle, expected: "pes" ), RemoveSubrangeTest( original: "perdicus", rangeSelection: .offsets(1, 2), expected: "prdicus", closedExpected: "pdicus" ), RemoveSubrangeTest( original: "perdicus", rangeSelection: .offsets(3, 6), expected: "perus", closedExpected: "pers" ) ] StringTests.test("String.replaceSubrange()/characters/range") { for test in replaceSubrangeTests { var theString = test.original let c = test.original let rangeToReplace = test.rangeSelection.range(in: c) let newCharacters : [Character] = Array(test.newElements) theString.replaceSubrange(rangeToReplace, with: newCharacters) expectEqual( test.expected, theString, stackTrace: SourceLocStack().with(test.loc)) } } StringTests.test("String.replaceSubrange()/string/range") { for test in replaceSubrangeTests { var theString = test.original let c = test.original let rangeToReplace = test.rangeSelection.range(in: c) theString.replaceSubrange(rangeToReplace, with: test.newElements) expectEqual( test.expected, theString, stackTrace: SourceLocStack().with(test.loc)) } } StringTests.test("String.replaceSubrange()/characters/closedRange") { for test in replaceSubrangeTests { guard let closedExpected = test.closedExpected else { continue } var theString = test.original let c = test.original let rangeToReplace = test.rangeSelection.closedRange(in: c) let newCharacters = Array(test.newElements) theString.replaceSubrange(rangeToReplace, with: newCharacters) expectEqual( closedExpected, theString, stackTrace: SourceLocStack().with(test.loc)) } } StringTests.test("String.replaceSubrange()/string/closedRange") { for test in replaceSubrangeTests { guard let closedExpected = test.closedExpected else { continue } var theString = test.original let c = test.original let rangeToReplace = test.rangeSelection.closedRange(in: c) theString.replaceSubrange(rangeToReplace, with: test.newElements) expectEqual( closedExpected, theString, stackTrace: SourceLocStack().with(test.loc)) } } StringTests.test("String.removeSubrange()/range") { for test in removeSubrangeTests { var theString = test.original let c = test.original let rangeToRemove = test.rangeSelection.range(in: c) theString.removeSubrange(rangeToRemove) expectEqual( test.expected, theString, stackTrace: SourceLocStack().with(test.loc)) } } StringTests.test("String.removeSubrange()/closedRange") { for test in removeSubrangeTests { switch test.rangeSelection { case .emptyRange: continue default: break } var theString = test.original let c = test.original let rangeToRemove = test.rangeSelection.closedRange(in: c) theString.removeSubrange(rangeToRemove) expectEqual( test.closedExpected, theString, stackTrace: SourceLocStack().with(test.loc)) } } //===----------------------------------------------------------------------===// // COW(🐄) tests //===----------------------------------------------------------------------===// public let testSuffix = "z" StringTests.test("COW.Smoke") { var s1 = "Cypseloides" + testSuffix let identity1 = s1._rawIdentifier() var s2 = s1 expectEqual(identity1, s2._rawIdentifier()) s2.append(" cryptus") expectTrue(identity1 != s2._rawIdentifier()) s1.remove(at: s1.startIndex) expectEqual(identity1, s1._rawIdentifier()) _fixLifetime(s1) _fixLifetime(s2) } struct COWStringTest { let test: String let name: String } var testCases: [COWStringTest] { return [ COWStringTest(test: "abcdefg", name: "ASCII"), COWStringTest(test: "🐮🐄🤠", name: "Unicode") ] } for test in testCases { StringTests.test("COW.\(test.name).IndexesDontAffectUniquenessCheck") { let s = test.test + testSuffix let identity1 = s._rawIdentifier() let startIndex = s.startIndex let endIndex = s.endIndex expectNotEqual(startIndex, endIndex) expectLT(startIndex, endIndex) expectLE(startIndex, endIndex) expectGT(endIndex, startIndex) expectGE(endIndex, startIndex) expectEqual(identity1, s._rawIdentifier()) // Keep indexes alive during the calls above _fixLifetime(startIndex) _fixLifetime(endIndex) } } for test in testCases { StringTests.test("COW.\(test.name).SubscriptWithIndexDoesNotReallocate") { let s = test.test + testSuffix let identity1 = s._rawIdentifier() let startIndex = s.startIndex let empty = startIndex == s.endIndex expectNotEqual((s.startIndex < s.endIndex), empty) expectLE(s.startIndex, s.endIndex) expectEqual((s.startIndex >= s.endIndex), empty) expectGT(s.endIndex, s.startIndex) expectEqual(identity1, s._rawIdentifier()) } } for test in testCases { StringTests.test("COW.\(test.name).RemoveAtDoesNotReallocate") { do { var s = test.test + testSuffix let identity1 = s._rawIdentifier() let index1 = s.startIndex expectEqual(identity1, s._rawIdentifier()) let _ = s.remove(at: index1) expectEqual(identity1, s._rawIdentifier()) } do { let s1 = test.test + testSuffix let identity1 = s1._rawIdentifier() var s2 = s1 expectEqual(identity1, s1._rawIdentifier()) expectEqual(identity1, s2._rawIdentifier()) let index1 = s1.startIndex expectEqual(identity1, s1._rawIdentifier()) expectEqual(identity1, s2._rawIdentifier()) let _ = s2.remove(at: index1) expectEqual(identity1, s1._rawIdentifier()) expectTrue(identity1 == s2._rawIdentifier()) } } } for test in testCases { StringTests.test("COW.\(test.name).RemoveAtDoesNotReallocate") { do { var s = test.test + testSuffix expectGT(s.count, 0) s.removeAll() let identity1 = s._rawIdentifier() expectEqual(0, s.count) expectEqual(identity1, s._rawIdentifier()) } do { var s = test.test + testSuffix let identity1 = s._rawIdentifier() expectGT(s.count, 3) s.removeAll(keepingCapacity: true) expectEqual(identity1, s._rawIdentifier()) expectEqual(0, s.count) } do { let s1 = test.test + testSuffix let identity1 = s1._rawIdentifier() expectGT(s1.count, 0) var s2 = s1 s2.removeAll() let identity2 = s2._rawIdentifier() expectEqual(identity1, s1._rawIdentifier()) expectTrue(identity2 != identity1) expectGT(s1.count, 0) expectEqual(0, s2.count) // Keep variables alive. _fixLifetime(s1) _fixLifetime(s2) } do { let s1 = test.test + testSuffix let identity1 = s1._rawIdentifier() expectGT(s1.count, 0) var s2 = s1 s2.removeAll(keepingCapacity: true) let identity2 = s2._rawIdentifier() expectEqual(identity1, s1._rawIdentifier()) expectTrue(identity2 != identity1) expectGT(s1.count, 0) expectEqual(0, s2.count) // Keep variables alive. _fixLifetime(s1) _fixLifetime(s2) } } } for test in testCases { StringTests.test("COW.\(test.name).CountDoesNotReallocate") { let s = test.test + testSuffix let identity1 = s._rawIdentifier() expectGT(s.count, 0) expectEqual(identity1, s._rawIdentifier()) } } for test in testCases { StringTests.test("COW.\(test.name).GenerateDoesNotReallocate") { let s = test.test + testSuffix let identity1 = s._rawIdentifier() var iter = s.makeIterator() var copy = String() while let value = iter.next() { copy.append(value) } expectEqual(copy, s) expectEqual(identity1, s._rawIdentifier()) } } for test in testCases { StringTests.test("COW.\(test.name).EqualityTestDoesNotReallocate") { let s1 = test.test + testSuffix let identity1 = s1._rawIdentifier() var s2 = test.test + testSuffix let identity2 = s2._rawIdentifier() expectEqual(s1, s2) expectEqual(identity1, s1._rawIdentifier()) expectEqual(identity2, s2._rawIdentifier()) s2.remove(at: s2.startIndex) expectNotEqual(s1, s2) expectEqual(identity1, s1._rawIdentifier()) expectEqual(identity2, s2._rawIdentifier()) } } runAllTests()