// 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 bufferID: UInt { return unsafeBitCast(_core._owner, to: UInt.self) } var nativeCapacity: Int { return _core.nativeBuffer!.capacity } var capacity: Int { return _core.nativeBuffer?.capacity ?? 0 } } extension Substring { var bufferID: UInt { return _ephemeralContent.bufferID } } var StringTests = TestSuite("StringTests") StringTests.test("sizeof") { expectEqual(3 * MemoryLayout.size, MemoryLayout.size) } StringTests.test("AssociatedTypes-UTF8View") { typealias View = String.UTF8View expectCollectionAssociatedTypes( collectionType: View.self, iteratorType: IndexingIterator.self, subSequenceType: View.self, indexType: View.Index.self, indexDistanceType: Int.self, indicesType: DefaultIndices.self) } StringTests.test("AssociatedTypes-UTF16View") { typealias View = String.UTF16View expectCollectionAssociatedTypes( collectionType: View.self, iteratorType: IndexingIterator.self, subSequenceType: View.self, indexType: View.Index.self, indexDistanceType: Int.self, indicesType: View.Indices.self) } StringTests.test("AssociatedTypes-UnicodeScalarView") { typealias View = String.UnicodeScalarView expectCollectionAssociatedTypes( collectionType: View.self, iteratorType: View.Iterator.self, subSequenceType: View.self, indexType: View.Index.self, indexDistanceType: Int.self, indicesType: DefaultBidirectionalIndices.self) } StringTests.test("AssociatedTypes-CharacterView") { typealias View = String.CharacterView expectCollectionAssociatedTypes( collectionType: View.self, iteratorType: IndexingIterator.self, subSequenceType: View.self, indexType: View.Index.self, indexDistanceType: Int.self, indicesType: DefaultBidirectionalIndices.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("indexComparability") { 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("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" // FIXME: this traps right now when trying to construct Character("ab"). expectEqual("a", acceptor[donor.startIndex]) } 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.. initialSize || sliceEnd > initialSize || sliceEnd < sliceStart { continue } var s0 = String(repeating: "x", count: initialSize) let originalIdentity = s0.bufferID s0 = s0[s0.index(_nth: sliceStart).. Int { let core = s._core guard let buf = core.nativeBuffer else { return 0 } let offset = (core._baseAddress! - buf.start) / core.elementWidth return buf.capacity - core.count - offset } func stringWithUnusedCapacity() -> (String, Int) { var s0 = String(repeating: "x", count: 17) if unusedCapacity(s0) == 0 { s0 += "y" } let cap = unusedCapacity(s0) expectNotEqual(0, cap) // This sorta checks for the original bug expectEqual( cap, unusedCapacity(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) } } 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) expectEqual(1, s._core.elementWidth) return s } StringTests.test("stringCoreExtensibility") .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.. _StringCore { var x = _StringCore() // make sure some - but not all - replacements will have to grow the buffer x.reserveCapacity(base._core.count * 3 / 2) x.append(contentsOf: base._core) // In case the core was widened and lost its capacity x.reserveCapacity(base._core.count * 3 / 2) return x } StringTests.test("StringCoreReplace") { let narrow = "01234567890" let wide = "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪ" for s1 in [narrow, wide] { for s2 in [narrow, wide] { checkRangeReplaceable( { makeStringCore(s1) }, { makeStringCore(s2 + s2)[0..<$0] } ) checkRangeReplaceable( { makeStringCore(s1) }, { Array(makeStringCore(s2 + s2)[0..<$0]) } ) } } } StringTests.test("CharacterViewReplace") { let narrow = "01234567890" let wide = "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪ" for s1 in [narrow, wide] { for s2 in [narrow, wide] { checkRangeReplaceable( { String.CharacterView(makeStringCore(s1)) }, { String.CharacterView(makeStringCore(s2 + s2)[0..<$0]) } ) checkRangeReplaceable( { String.CharacterView(makeStringCore(s1)) }, { Array(String.CharacterView(makeStringCore(s2 + s2)[0..<$0])) } ) } } } StringTests.test("UnicodeScalarViewReplace") { let narrow = "01234567890" let wide = "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪ" for s1 in [narrow, wide] { for s2 in [narrow, wide] { checkRangeReplaceable( { String(makeStringCore(s1)).unicodeScalars }, { String(makeStringCore(s2 + s2)[0..<$0]).unicodeScalars } ) checkRangeReplaceable( { String(makeStringCore(s1)).unicodeScalars }, { Array(String(makeStringCore(s2 + s2)[0..<$0]).unicodeScalars) } ) } } } 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._core.elementWidth)") let id1 = s.bufferID s.insert(contentsOf: repeatElement(x, count: oldCap + 2), at: s.endIndex) print("extending by \(oldCap + 2) -> \(s.capacity), width = \(s._core.elementWidth)") 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 i in 0..<20 { s += "x" s2 = s } expectLE(s.nativeCapacity, 34) } StringTests.test("Construction") { expectEqual("abc", String(["a", "b", "c"] as [Character])) } StringTests.test("Conversions") { do { var c: Character = "a" let x = String(c) expectTrue(x._core.isASCII) var s: String = "a" expectEqual(s, x) } do { var c: Character = "\u{B977}" let x = String(c) expectFalse(x._core.isASCII) var 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)? where C.CodeUnit : FixedWidthInteger { 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.characters let rangeToReplace = test.rangeSelection.range(in: c) let newCharacters : [Character] = Array(test.newElements.characters) 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.characters 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.characters let rangeToReplace = test.rangeSelection.closedRange(in: c) let newCharacters = Array(test.newElements.characters) 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.characters 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.characters 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.characters let rangeToRemove = test.rangeSelection.closedRange(in: c) theString.removeSubrange(rangeToRemove) expectEqual( test.closedExpected, theString, stackTrace: SourceLocStack().with(test.loc)) } } runAllTests()