mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Clean up a few general patterns that are now obviated by canImport This aligns more generally with the cleanup that the Swift Package Manager has already done in their automated XCTest-plumbing tool in apple/swift-package-manager#1826.
2283 lines
66 KiB
Swift
2283 lines
66 KiB
Swift
// RUN: %empty-directory(%t)
|
||
// RUN: %target-clang -fobjc-arc %S/Inputs/NSSlowString/NSSlowString.m -c -o %t/NSSlowString.o
|
||
// RUN: %target-build-swift -I %S/Inputs/NSSlowString/ %t/NSSlowString.o %s -Xfrontend -disable-access-control -o %t/String
|
||
|
||
// RUN: %target-codesign %t/String
|
||
// RUN: %target-run %t/String
|
||
// REQUIRES: executable_test
|
||
// XFAIL: interpret
|
||
|
||
// With a non-optimized stdlib the test takes very long.
|
||
// REQUIRES: optimized_stdlib
|
||
|
||
import StdlibUnittest
|
||
import StdlibCollectionUnittest
|
||
|
||
#if _runtime(_ObjC)
|
||
import NSSlowString
|
||
import Foundation // For NSRange
|
||
#endif
|
||
|
||
#if os(Windows)
|
||
import ucrt
|
||
#endif
|
||
|
||
extension Collection {
|
||
internal func index(_nth n: Int) -> Index {
|
||
precondition(n >= 0)
|
||
return index(startIndex, offsetBy: n)
|
||
}
|
||
internal func index(_nthLast n: Int) -> Index {
|
||
precondition(n >= 0)
|
||
return index(endIndex, offsetBy: -n)
|
||
}
|
||
}
|
||
|
||
extension String {
|
||
var nativeCapacity: Int {
|
||
switch self._classify()._form {
|
||
case ._native: break
|
||
default: preconditionFailure()
|
||
}
|
||
return self._classify()._capacity
|
||
}
|
||
var capacity: Int {
|
||
return self._classify()._capacity
|
||
}
|
||
var unusedCapacity: Int {
|
||
return Swift.max(0, self._classify()._capacity - self._classify()._count)
|
||
}
|
||
var bufferID: ObjectIdentifier? {
|
||
return _rawIdentifier()
|
||
}
|
||
func _rawIdentifier() -> ObjectIdentifier? {
|
||
return self._classify()._objectIdentifier
|
||
}
|
||
|
||
var byteWidth: Int {
|
||
return _classify()._isASCII ? 1 : 2
|
||
}
|
||
}
|
||
|
||
extension Substring {
|
||
var bufferID: ObjectIdentifier? {
|
||
return base.bufferID
|
||
}
|
||
}
|
||
|
||
// A thin wrapper around _StringGuts implementing RangeReplaceableCollection
|
||
struct StringFauxUTF16Collection: RangeReplaceableCollection, RandomAccessCollection {
|
||
typealias Element = UTF16.CodeUnit
|
||
typealias Index = Int
|
||
typealias Indices = CountableRange<Int>
|
||
|
||
init(_ guts: _StringGuts) {
|
||
self._str = String(guts)
|
||
}
|
||
|
||
init() {
|
||
self.init(_StringGuts())
|
||
}
|
||
|
||
var _str: String
|
||
var _guts: _StringGuts { return _str._guts }
|
||
|
||
var startIndex: Index { return 0 }
|
||
var endIndex: Index { return _str.utf16.count }
|
||
var indices: Indices { return startIndex..<endIndex }
|
||
|
||
subscript(position: Index) -> Element {
|
||
return _str.utf16[_str._toUTF16Index(position)]
|
||
}
|
||
|
||
mutating func replaceSubrange<C>(
|
||
_ subrange: Range<Index>,
|
||
with newElements: C
|
||
) where C : Collection, C.Element == Element {
|
||
var utf16 = Array(_str.utf16)
|
||
utf16.replaceSubrange(subrange, with: newElements)
|
||
self._str = String(decoding: utf16, as: UTF16.self)
|
||
}
|
||
|
||
mutating func reserveCapacity(_ n: Int) {
|
||
_str.reserveCapacity(n)
|
||
}
|
||
}
|
||
|
||
var StringTests = TestSuite("StringTests")
|
||
|
||
StringTests.test("sizeof") {
|
||
#if arch(i386) || arch(arm)
|
||
expectEqual(12, MemoryLayout<String>.size)
|
||
#else
|
||
expectEqual(16, MemoryLayout<String>.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<View>.self)
|
||
}
|
||
|
||
StringTests.test("AssociatedTypes-UTF16View") {
|
||
typealias View = String.UTF16View
|
||
expectCollectionAssociatedTypes(
|
||
collectionType: View.self,
|
||
iteratorType: View.Iterator.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<View>.self)
|
||
}
|
||
|
||
StringTests.test("AssociatedTypes-CharacterView") {
|
||
expectCollectionAssociatedTypes(
|
||
collectionType: String.self,
|
||
iteratorType: String.Iterator.self,
|
||
subSequenceType: Substring.self,
|
||
indexType: String.Index.self,
|
||
indicesType: DefaultIndices<String>.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.
|
||
//
|
||
// <rdar://problem/18037897> 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)..<donor.index(_nth: 5)])
|
||
}
|
||
do {
|
||
let donor = "abcdef"
|
||
let acceptor = "\u{1f601}\u{1f602}\u{1f603}"
|
||
expectEqual("\u{1f601}", acceptor[donor.startIndex])
|
||
|
||
// Scalar alignment fixes and checks were added in 5.1, so we don't get the
|
||
// expected behavior on prior runtimes.
|
||
guard _hasSwift_5_1() else { return }
|
||
|
||
// Donor's second index is scalar-aligned in donor, but not acceptor. This
|
||
// will trigger a stdlib assertion.
|
||
let donorSecondIndex = donor.index(after: donor.startIndex)
|
||
if _isStdlibInternalChecksEnabled() {
|
||
expectCrash { _ = acceptor[donorSecondIndex] }
|
||
} else {
|
||
expectEqual(1, acceptor[donorSecondIndex].utf8.count)
|
||
expectEqual(0x9F, acceptor[donorSecondIndex].utf8.first!)
|
||
}
|
||
}
|
||
}
|
||
|
||
StringTests.test("ForeignIndexes/UnexpectedCrash") {
|
||
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))
|
||
|
||
// Grapheme stride cache under noop scalar alignment was fixed in 5.1, so we
|
||
// get a different answer prior.
|
||
guard _hasSwift_5_1() else { return }
|
||
|
||
// `start` has a cached stride greater than 1, so subscript will trigger an
|
||
// assertion when it makes a multi-grapheme-cluster Character.
|
||
if _isStdlibInternalChecksEnabled() {
|
||
expectCrash { _ = acceptor[start] }
|
||
} else {
|
||
expectEqual("abcd", String(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..<to]
|
||
expectEqual("fooba", actual)
|
||
}
|
||
|
||
StringTests.test("String/subscript(_:ClosedRange)") {
|
||
let s = "foobar"
|
||
let from = s.startIndex
|
||
let to = s.index(before: s.endIndex)
|
||
let actual = s[from...to]
|
||
expectEqual(s, actual)
|
||
}
|
||
|
||
StringTests.test("ForeignIndexes/subscript(Range)/OutOfBoundsTrap/1") {
|
||
let donor = "abcdef"
|
||
let acceptor = "uvw"
|
||
|
||
expectEqual("uvw", acceptor[donor.startIndex..<donor.index(_nth: 3)])
|
||
|
||
let r = donor.startIndex..<donor.index(_nth: 4)
|
||
expectCrashLater()
|
||
_ = acceptor[r]
|
||
}
|
||
|
||
StringTests.test("ForeignIndexes/subscript(Range)/OutOfBoundsTrap/2") {
|
||
let donor = "abcdef"
|
||
let acceptor = "uvw"
|
||
|
||
expectEqual("uvw", acceptor[donor.startIndex..<donor.index(_nth: 3)])
|
||
|
||
let r = donor.index(_nth: 4)..<donor.index(_nth: 5)
|
||
expectCrashLater()
|
||
_ = acceptor[r]
|
||
}
|
||
|
||
StringTests.test("ForeignIndexes/subscript(ClosedRange)/OutOfBoundsTrap/1") {
|
||
let donor = "abcdef"
|
||
let acceptor = "uvw"
|
||
|
||
expectEqual("uvw", acceptor[donor.startIndex...donor.index(_nth: 2)])
|
||
|
||
let r = donor.startIndex...donor.index(_nth: 3)
|
||
expectCrashLater()
|
||
_ = acceptor[r]
|
||
}
|
||
|
||
StringTests.test("ForeignIndexes/subscript(ClosedRange)/OutOfBoundsTrap/2") {
|
||
let donor = "abcdef"
|
||
let acceptor = "uvw"
|
||
|
||
expectEqual("uvw", acceptor[donor.startIndex...donor.index(_nth: 2)])
|
||
|
||
let r = donor.index(_nth: 3)...donor.index(_nth: 5)
|
||
expectCrashLater()
|
||
_ = acceptor[r]
|
||
}
|
||
|
||
StringTests.test("ForeignIndexes/replaceSubrange/OutOfBoundsTrap/1") {
|
||
let donor = "abcdef"
|
||
var acceptor = "uvw"
|
||
|
||
acceptor.replaceSubrange(
|
||
donor.startIndex..<donor.index(_nth: 1), with: "u")
|
||
expectEqual("uvw", acceptor)
|
||
|
||
let r = donor.startIndex..<donor.index(_nth: 4)
|
||
expectCrashLater()
|
||
acceptor.replaceSubrange(r, with: "")
|
||
}
|
||
|
||
StringTests.test("ForeignIndexes/replaceSubrange/OutOfBoundsTrap/2") {
|
||
let donor = "abcdef"
|
||
var acceptor = "uvw"
|
||
|
||
acceptor.replaceSubrange(
|
||
donor.startIndex..<donor.index(_nth: 1), with: "u")
|
||
expectEqual("uvw", acceptor)
|
||
|
||
let r = donor.index(_nth: 4)..<donor.index(_nth: 5)
|
||
expectCrashLater()
|
||
acceptor.replaceSubrange(r, with: "")
|
||
}
|
||
|
||
StringTests.test("ForeignIndexes/removeAt/OutOfBoundsTrap") {
|
||
do {
|
||
let donor = "abcdef"
|
||
var acceptor = "uvw"
|
||
|
||
let removed = acceptor.remove(at: donor.startIndex)
|
||
expectEqual("u", removed)
|
||
expectEqual("vw", acceptor)
|
||
}
|
||
|
||
let donor = "abcdef"
|
||
var acceptor = "uvw"
|
||
|
||
let i = donor.index(_nth: 4)
|
||
expectCrashLater()
|
||
acceptor.remove(at: i)
|
||
}
|
||
|
||
StringTests.test("ForeignIndexes/removeSubrange/OutOfBoundsTrap/1") {
|
||
do {
|
||
let donor = "abcdef"
|
||
var acceptor = "uvw"
|
||
|
||
acceptor.removeSubrange(
|
||
donor.startIndex..<donor.index(after: donor.startIndex))
|
||
expectEqual("vw", acceptor)
|
||
}
|
||
|
||
let donor = "abcdef"
|
||
var acceptor = "uvw"
|
||
|
||
let r = donor.startIndex..<donor.index(_nth: 4)
|
||
expectCrashLater()
|
||
acceptor.removeSubrange(r)
|
||
}
|
||
|
||
StringTests.test("ForeignIndexes/removeSubrange/OutOfBoundsTrap/2") {
|
||
let donor = "abcdef"
|
||
var acceptor = "uvw"
|
||
|
||
let r = donor.index(_nth: 4)..<donor.index(_nth: 5)
|
||
expectCrashLater()
|
||
acceptor.removeSubrange(r)
|
||
}
|
||
|
||
StringTests.test("hasPrefix")
|
||
.skip(.nativeRuntime("String.hasPrefix undefined without _runtime(_ObjC)"))
|
||
.code {
|
||
#if _runtime(_ObjC)
|
||
expectTrue("".hasPrefix(""))
|
||
expectFalse("".hasPrefix("a"))
|
||
expectTrue("a".hasPrefix(""))
|
||
expectTrue("a".hasPrefix("a"))
|
||
|
||
// U+0301 COMBINING ACUTE ACCENT
|
||
// U+00E1 LATIN SMALL LETTER A WITH ACUTE
|
||
expectFalse("abc".hasPrefix("a\u{0301}"))
|
||
expectFalse("a\u{0301}bc".hasPrefix("a"))
|
||
expectTrue("\u{00e1}bc".hasPrefix("a\u{0301}"))
|
||
expectTrue("a\u{0301}bc".hasPrefix("\u{00e1}"))
|
||
#else
|
||
expectUnreachable()
|
||
#endif
|
||
}
|
||
|
||
StringTests.test("literalConcatenation") {
|
||
do {
|
||
// UnicodeScalarLiteral + UnicodeScalarLiteral
|
||
var s = "1" + "2"
|
||
expectType(String.self, &s)
|
||
expectEqual("12", s)
|
||
}
|
||
do {
|
||
// UnicodeScalarLiteral + ExtendedGraphemeClusterLiteral
|
||
var s = "1" + "a\u{0301}"
|
||
expectType(String.self, &s)
|
||
expectEqual("1a\u{0301}", s)
|
||
}
|
||
do {
|
||
// UnicodeScalarLiteral + StringLiteral
|
||
var s = "1" + "xyz"
|
||
expectType(String.self, &s)
|
||
expectEqual("1xyz", s)
|
||
}
|
||
|
||
do {
|
||
// ExtendedGraphemeClusterLiteral + UnicodeScalar
|
||
var s = "a\u{0301}" + "z"
|
||
expectType(String.self, &s)
|
||
expectEqual("a\u{0301}z", s)
|
||
}
|
||
do {
|
||
// ExtendedGraphemeClusterLiteral + ExtendedGraphemeClusterLiteral
|
||
var s = "a\u{0301}" + "e\u{0302}"
|
||
expectType(String.self, &s)
|
||
expectEqual("a\u{0301}e\u{0302}", s)
|
||
}
|
||
do {
|
||
// ExtendedGraphemeClusterLiteral + StringLiteral
|
||
var s = "a\u{0301}" + "xyz"
|
||
expectType(String.self, &s)
|
||
expectEqual("a\u{0301}xyz", s)
|
||
}
|
||
|
||
do {
|
||
// StringLiteral + UnicodeScalar
|
||
var s = "xyz" + "1"
|
||
expectType(String.self, &s)
|
||
expectEqual("xyz1", s)
|
||
}
|
||
do {
|
||
// StringLiteral + ExtendedGraphemeClusterLiteral
|
||
var s = "xyz" + "a\u{0301}"
|
||
expectType(String.self, &s)
|
||
expectEqual("xyza\u{0301}", s)
|
||
}
|
||
do {
|
||
// StringLiteral + StringLiteral
|
||
var s = "xyz" + "abc"
|
||
expectType(String.self, &s)
|
||
expectEqual("xyzabc", s)
|
||
}
|
||
}
|
||
|
||
StringTests.test("substringDoesNotCopy/Swift3")
|
||
.xfail(.always("Swift 3 compatibility: Self-sliced Strings are copied"))
|
||
.code {
|
||
|
||
let size = 16
|
||
for sliceStart in [0, 2, 8, size] {
|
||
for sliceEnd in [0, 2, 8, sliceStart + 1] {
|
||
if sliceStart > size || sliceEnd > size || sliceEnd < sliceStart {
|
||
continue
|
||
}
|
||
var s0 = String(repeating: "x", count: size)
|
||
let originalIdentity = s0.bufferID
|
||
s0 = String(s0[s0.index(_nth: sliceStart)..<s0.index(_nth: sliceEnd)])
|
||
expectEqual(originalIdentity, s0.bufferID)
|
||
}
|
||
}
|
||
}
|
||
|
||
StringTests.test("substringDoesNotCopy/Swift4") {
|
||
|
||
let size = 16
|
||
for sliceStart in [0, 2, 8, size] {
|
||
for sliceEnd in [0, 2, 8, sliceStart + 1] {
|
||
if sliceStart > size || sliceEnd > size || sliceEnd < sliceStart {
|
||
continue
|
||
}
|
||
let s0 = String(repeating: "x", count: size)
|
||
let originalIdentity = s0.bufferID
|
||
let s1 = s0[s0.index(_nth: sliceStart)..<s0.index(_nth: sliceEnd)]
|
||
expectEqual(s1.bufferID, originalIdentity)
|
||
}
|
||
}
|
||
}
|
||
|
||
StringTests.test("appendToEmptyString") {
|
||
let x = "Bumfuzzle"
|
||
expectNil(x.bufferID)
|
||
|
||
// Appending to empty string literal should replace it.
|
||
var a1 = ""
|
||
a1 += x
|
||
expectNil(a1.bufferID)
|
||
|
||
// Appending to native string should keep the existing buffer.
|
||
var b1 = ""
|
||
b1.reserveCapacity(20)
|
||
let b1ID = b1.bufferID
|
||
b1 += x
|
||
expectEqual(b1.bufferID, b1ID)
|
||
|
||
// .append(_:) should have the same behavior as +=
|
||
var a2 = ""
|
||
a2.append(x)
|
||
expectNil(a2.bufferID)
|
||
|
||
var b2 = ""
|
||
b2.reserveCapacity(20)
|
||
let b2ID = b2.bufferID
|
||
b2.append(x)
|
||
expectEqual(b2.bufferID, b2ID)
|
||
}
|
||
|
||
StringTests.test("Swift3Slice/Empty") {
|
||
let size = 16
|
||
let s = String(repeating: "x", count: size)
|
||
expectNotNil(s.bufferID)
|
||
for i in 0 ... size {
|
||
let slice = s[s.index(_nth: i)..<s.index(_nth: i)]
|
||
// Empty substrings still have indices relative to their base and can refer
|
||
// to the whole string. If the whole string has storage, so should its
|
||
// substring.
|
||
expectNotNil(slice.bufferID)
|
||
}
|
||
}
|
||
|
||
StringTests.test("Swift3Slice/Full") {
|
||
let size = 16
|
||
let s = String(repeating: "x", count: size)
|
||
let slice = s[s.startIndex..<s.endIndex]
|
||
// Most Swift 3 substrings are extracted into their own buffer,
|
||
// but if the substring covers the full original string, it is used instead.
|
||
expectEqual(slice.bufferID, s.bufferID)
|
||
}
|
||
|
||
StringTests.test("appendToSubstring") {
|
||
for initialSize in 1..<16 {
|
||
for sliceStart in [0, 2, 8, initialSize] {
|
||
for sliceEnd in [0, 2, 8, sliceStart + 1] {
|
||
if sliceStart > initialSize || sliceEnd > initialSize ||
|
||
sliceEnd < sliceStart {
|
||
continue
|
||
}
|
||
var s0 = String(repeating: "x", count: initialSize)
|
||
s0 = String(s0[s0.index(_nth: sliceStart)..<s0.index(_nth: sliceEnd)])
|
||
s0 += "x"
|
||
expectEqual(
|
||
String(
|
||
repeating: "x",
|
||
count: sliceEnd - sliceStart + 1),
|
||
s0)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
StringTests.test("appendToSubstringBug")
|
||
.xfail(.always("Swift 3 compatibility: Self-sliced Strings are copied"))
|
||
.code {
|
||
// String used to have a heap overflow bug when one attempted to append to a
|
||
// substring that pointed to the end of a string buffer.
|
||
//
|
||
// Unused capacity
|
||
// VVV
|
||
// String buffer [abcdefghijk ]
|
||
// ^ ^
|
||
// +----+
|
||
// Substring -----------+
|
||
//
|
||
// In the example above, there are only three elements of unused capacity.
|
||
// The bug was that the implementation mistakenly assumed 9 elements of
|
||
// unused capacity (length of the prefix "abcdef" plus truly unused elements
|
||
// at the end).
|
||
|
||
func stringWithUnusedCapacity() -> (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, String(s0[s0.index(_nth: 1)..<s0.endIndex]).unusedCapacity)
|
||
|
||
return (s0, cap)
|
||
}
|
||
|
||
do {
|
||
var (s, _) = { ()->(String, Int) in
|
||
let (s0, unused) = stringWithUnusedCapacity()
|
||
return (String(s0[s0.index(_nth: 5)..<s0.endIndex]), unused)
|
||
}()
|
||
let originalID = s.bufferID
|
||
// Appending to a String always results in storage that
|
||
// starts at the beginning of its native buffer
|
||
s += "z"
|
||
expectNotEqual(originalID, s.bufferID)
|
||
}
|
||
|
||
do {
|
||
var (s, _) = { ()->(Substring, Int) in
|
||
let (s0, unused) = stringWithUnusedCapacity()
|
||
return (s0[s0.index(_nth: 5)..<s0.endIndex], unused)
|
||
}()
|
||
let originalID = s.bufferID
|
||
// FIXME: Ideally, appending to a Substring with a unique buffer reference
|
||
// does not reallocate unless necessary. Today, however, it appears to do
|
||
// so unconditionally unless the slice falls at the beginning of its buffer.
|
||
s += "z"
|
||
expectNotEqual(originalID, s.bufferID)
|
||
}
|
||
|
||
// Try again at the beginning of the buffer
|
||
do {
|
||
var (s, unused) = { ()->(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"
|
||
str.reserveCapacity(1024) // Ensure on heap
|
||
let literalIdentity = str.bufferID
|
||
|
||
// Check literal-to-heap reallocation.
|
||
do {
|
||
let slice = str
|
||
expectNotNil(literalIdentity)
|
||
expectEqual(literalIdentity, str.bufferID)
|
||
expectEqual(literalIdentity, slice.bufferID)
|
||
expectEqual("12345678", str)
|
||
expectEqual("12345678", slice)
|
||
|
||
// This mutation should reallocate the string.
|
||
str.removeSubrange(str.startIndex..<str.index(_nth: 1))
|
||
expectNotEqual(literalIdentity, str.bufferID)
|
||
expectEqual(literalIdentity, slice.bufferID)
|
||
let heapStrIdentity = str.bufferID
|
||
expectEqual("2345678", str)
|
||
expectEqual("12345678", slice)
|
||
|
||
// No more reallocations are expected.
|
||
str.removeSubrange(str.startIndex..<str.index(_nth: 1))
|
||
expectEqual(heapStrIdentity, str.bufferID)
|
||
expectEqual(literalIdentity, slice.bufferID)
|
||
expectEqual("345678", str)
|
||
expectEqual("12345678", slice)
|
||
}
|
||
|
||
// Check heap-to-heap reallocation.
|
||
expectEqual("345678", str)
|
||
do {
|
||
str.reserveCapacity(1024) // Ensure on heap
|
||
let heapStrIdentity1 = str.bufferID
|
||
|
||
let slice = str
|
||
expectNotNil(heapStrIdentity1)
|
||
expectEqual(heapStrIdentity1, str.bufferID)
|
||
expectEqual(heapStrIdentity1, slice.bufferID)
|
||
expectEqual("345678", str)
|
||
expectEqual("345678", slice)
|
||
|
||
// This mutation should reallocate the string.
|
||
str.removeSubrange(str.startIndex..<str.index(_nth: 1))
|
||
expectNotEqual(heapStrIdentity1, str.bufferID)
|
||
expectEqual(heapStrIdentity1, slice.bufferID)
|
||
let heapStrIdentity2 = str.bufferID
|
||
expectEqual("45678", str)
|
||
expectEqual("345678", slice)
|
||
|
||
// No more reallocations are expected.
|
||
str.removeSubrange(str.startIndex..<str.index(_nth: 1))
|
||
expectEqual(heapStrIdentity2, str.bufferID)
|
||
expectEqual(heapStrIdentity1, slice.bufferID)
|
||
expectEqual("5678", str)
|
||
expectEqual("345678", slice)
|
||
}
|
||
}
|
||
|
||
StringTests.test("COW/removeSubrange/end") {
|
||
var str = "12345678"
|
||
str.reserveCapacity(1024) // Ensure on heap
|
||
let literalIdentity = str.bufferID
|
||
|
||
// Check literal-to-heap reallocation.
|
||
expectEqual("12345678", str)
|
||
do {
|
||
let slice = str
|
||
expectNotNil(literalIdentity)
|
||
expectEqual(literalIdentity, str.bufferID)
|
||
expectEqual(literalIdentity, slice.bufferID)
|
||
expectEqual("12345678", str)
|
||
expectEqual("12345678", slice)
|
||
|
||
// This mutation should reallocate the string.
|
||
str.removeSubrange(str.index(_nthLast: 1)..<str.endIndex)
|
||
expectNotEqual(literalIdentity, str.bufferID)
|
||
expectEqual(literalIdentity, slice.bufferID)
|
||
let heapStrIdentity = str.bufferID
|
||
expectEqual("1234567", str)
|
||
expectEqual("12345678", slice)
|
||
|
||
// No more reallocations are expected.
|
||
str.append("x")
|
||
str.removeSubrange(str.index(_nthLast: 1)..<str.endIndex)
|
||
expectEqual(heapStrIdentity, str.bufferID)
|
||
expectEqual(literalIdentity, slice.bufferID)
|
||
expectEqual("1234567", str)
|
||
expectEqual("12345678", slice)
|
||
|
||
str.removeSubrange(str.index(_nthLast: 1)..<str.endIndex)
|
||
str.append("x")
|
||
str.removeSubrange(str.index(_nthLast: 1)..<str.endIndex)
|
||
expectEqual(heapStrIdentity, str.bufferID)
|
||
expectEqual(literalIdentity, slice.bufferID)
|
||
expectEqual("123456", str)
|
||
expectEqual("12345678", slice)
|
||
}
|
||
|
||
// Check heap-to-heap reallocation.
|
||
expectEqual("123456", str)
|
||
do {
|
||
str.reserveCapacity(1024) // Ensure on heap
|
||
let heapStrIdentity1 = str.bufferID
|
||
|
||
let slice = str
|
||
expectNotNil(heapStrIdentity1)
|
||
expectEqual(heapStrIdentity1, str.bufferID)
|
||
expectEqual(heapStrIdentity1, slice.bufferID)
|
||
expectEqual("123456", str)
|
||
expectEqual("123456", slice)
|
||
|
||
// This mutation should reallocate the string.
|
||
str.removeSubrange(str.index(_nthLast: 1)..<str.endIndex)
|
||
expectNotEqual(heapStrIdentity1, str.bufferID)
|
||
expectEqual(heapStrIdentity1, slice.bufferID)
|
||
let heapStrIdentity = str.bufferID
|
||
expectEqual("12345", str)
|
||
expectEqual("123456", slice)
|
||
|
||
// No more reallocations are expected.
|
||
str.append("x")
|
||
str.removeSubrange(str.index(_nthLast: 1)..<str.endIndex)
|
||
expectEqual(heapStrIdentity, str.bufferID)
|
||
expectEqual(heapStrIdentity1, slice.bufferID)
|
||
expectEqual("12345", str)
|
||
expectEqual("123456", slice)
|
||
|
||
str.removeSubrange(str.index(_nthLast: 1)..<str.endIndex)
|
||
str.append("x")
|
||
str.removeSubrange(str.index(_nthLast: 1)..<str.endIndex)
|
||
expectEqual(heapStrIdentity, str.bufferID)
|
||
expectEqual(heapStrIdentity1, slice.bufferID)
|
||
expectEqual("1234", str)
|
||
expectEqual("123456", slice)
|
||
}
|
||
}
|
||
|
||
StringTests.test("COW/replaceSubrange/end") {
|
||
// Check literal-to-heap reallocation.
|
||
do {
|
||
var str = "12345678"
|
||
str.reserveCapacity(1024) // Ensure on heap
|
||
let literalIdentity = str.bufferID
|
||
|
||
var slice = str[str.startIndex..<str.index(_nth: 7)]
|
||
expectNotNil(literalIdentity)
|
||
expectEqual(literalIdentity, str.bufferID)
|
||
expectEqual(literalIdentity, slice.bufferID)
|
||
expectEqual("12345678", str)
|
||
expectEqual("1234567", slice)
|
||
|
||
// This mutation should reallocate the string.
|
||
slice.replaceSubrange(slice.endIndex..<slice.endIndex, with: "a")
|
||
expectNotEqual(literalIdentity, slice.bufferID)
|
||
expectEqual(literalIdentity, str.bufferID)
|
||
let heapStrIdentity = slice.bufferID
|
||
expectEqual("1234567a", slice)
|
||
expectEqual("12345678", str)
|
||
|
||
// No more reallocations are expected.
|
||
slice.replaceSubrange(
|
||
slice.index(_nthLast: 1)..<slice.endIndex, with: "b")
|
||
expectEqual(heapStrIdentity, slice.bufferID)
|
||
expectEqual(literalIdentity, str.bufferID)
|
||
|
||
expectEqual("1234567b", slice)
|
||
expectEqual("12345678", str)
|
||
}
|
||
|
||
// Check literal-to-heap reallocation.
|
||
do {
|
||
var str = "12345678"
|
||
let literalIdentity = str.bufferID
|
||
|
||
// Move the string to the heap.
|
||
str.reserveCapacity(32)
|
||
expectNotEqual(literalIdentity, str.bufferID)
|
||
let heapStrIdentity1 = str.bufferID
|
||
expectNotNil(heapStrIdentity1)
|
||
|
||
// FIXME: We have to use Swift 4's Substring to get the desired storage
|
||
// semantics; in Swift 3 mode, self-sliced strings get allocated a new
|
||
// buffer immediately.
|
||
var slice = str[str.startIndex..<str.index(_nth: 7)]
|
||
expectEqual(heapStrIdentity1, str.bufferID)
|
||
expectEqual(heapStrIdentity1, slice.bufferID)
|
||
|
||
// This mutation should reallocate the string.
|
||
slice.replaceSubrange(slice.endIndex..<slice.endIndex, with: "a")
|
||
expectNotEqual(heapStrIdentity1, slice.bufferID)
|
||
expectEqual(heapStrIdentity1, str.bufferID)
|
||
let heapStrIdentity2 = slice.bufferID
|
||
expectEqual("1234567a", slice)
|
||
expectEqual("12345678", str)
|
||
|
||
// No more reallocations are expected.
|
||
slice.replaceSubrange(
|
||
slice.index(_nthLast: 1)..<slice.endIndex, with: "b")
|
||
expectEqual(heapStrIdentity2, slice.bufferID)
|
||
expectEqual(heapStrIdentity1, str.bufferID)
|
||
|
||
expectEqual("1234567b", slice)
|
||
expectEqual("12345678", str)
|
||
}
|
||
}
|
||
|
||
func asciiString<
|
||
S: Sequence
|
||
>(_ content: S) -> String
|
||
where S.Iterator.Element == Character {
|
||
var s = String()
|
||
s.append(contentsOf: content)
|
||
expectTrue(s._classify()._isASCII)
|
||
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..<count {
|
||
|
||
var x = (
|
||
k == 0 ? asciiString("b")
|
||
: k == 1 ? ("b" as NSString as String)
|
||
: ("b" as NSMutableString as String)
|
||
)
|
||
|
||
if k == 0 { expectTrue(x._guts.isFastUTF8) }
|
||
|
||
for i in 0..<count {
|
||
x.append(String(
|
||
decoding: repeatElement(i < boundary ? ascii : nonAscii, count: 3),
|
||
as: UTF16.self))
|
||
}
|
||
// Make sure we can append pure ASCII to wide storage
|
||
x.append(String(
|
||
decoding: repeatElement(ascii, count: 2), as: UTF16.self))
|
||
|
||
expectEqualSequence(
|
||
[UTF16.CodeUnit(UnicodeScalar("b").value)]
|
||
+ Array(repeatElement(ascii, count: 3*boundary))
|
||
+ repeatElement(nonAscii, count: 3*(count - boundary))
|
||
+ repeatElement(ascii, count: 2),
|
||
StringFauxUTF16Collection(x.utf16)
|
||
)
|
||
}
|
||
}
|
||
}
|
||
#else
|
||
expectUnreachable()
|
||
#endif
|
||
}
|
||
|
||
StringTests.test("stringGutsReserve")
|
||
.skip(.nativeRuntime("Foundation dependency"))
|
||
.code {
|
||
#if _runtime(_ObjC)
|
||
guard #available(macOS 10.13, iOS 11.0, tvOS 11.0, *) else { return }
|
||
for k in 0...7 {
|
||
var base: String
|
||
var startedNative: Bool
|
||
let shared: String = "X"
|
||
|
||
// Managed native, unmanaged native, or small
|
||
func isSwiftNative(_ s: String) -> Bool {
|
||
switch s._classify()._form {
|
||
case ._native: return true
|
||
case ._small: return true
|
||
case ._immortal: return true
|
||
default: return false
|
||
}
|
||
}
|
||
|
||
switch k {
|
||
case 0: (base, startedNative) = (String(), true)
|
||
case 1: (base, startedNative) = (asciiString("x"), true)
|
||
case 2: (base, startedNative) = ("Ξ", true)
|
||
#if arch(i386) || arch(arm)
|
||
case 3: (base, startedNative) = ("x" as NSString as String, false)
|
||
case 4: (base, startedNative) = ("x" as NSMutableString as String, false)
|
||
#else
|
||
case 3: (base, startedNative) = ("x" as NSString as String, true)
|
||
case 4: (base, startedNative) = ("x" as NSMutableString as String, true)
|
||
#endif
|
||
case 5: (base, startedNative) = (shared, true)
|
||
case 6: (base, startedNative) = ("xá" as NSString as String, false)
|
||
case 7: (base, startedNative) = ("xá" as NSMutableString as String, false)
|
||
default:
|
||
fatalError("case unhandled!")
|
||
}
|
||
expectEqual(isSwiftNative(base), startedNative)
|
||
|
||
let originalBuffer = base.bufferID
|
||
let isUnique = base._guts.isUniqueNative
|
||
let startedUnique =
|
||
startedNative &&
|
||
base._classify()._objectIdentifier != nil &&
|
||
isUnique
|
||
|
||
base.reserveCapacity(16)
|
||
// Now it's unique
|
||
|
||
// If it was already native and unique, no reallocation
|
||
if startedUnique && startedNative {
|
||
expectEqual(originalBuffer, base.bufferID)
|
||
}
|
||
else {
|
||
expectNotEqual(originalBuffer, base.bufferID)
|
||
}
|
||
|
||
// Reserving up to the capacity in a unique native buffer is a no-op
|
||
let nativeBuffer = base.bufferID
|
||
let currentCapacity = base.capacity
|
||
base.reserveCapacity(currentCapacity)
|
||
expectEqual(nativeBuffer, base.bufferID)
|
||
|
||
// Reserving more capacity should reallocate
|
||
base.reserveCapacity(currentCapacity + 1)
|
||
expectNotEqual(nativeBuffer, base.bufferID)
|
||
|
||
// None of this should change the string contents
|
||
var expected: String
|
||
switch k {
|
||
case 0: expected = ""
|
||
case 1,3,4: expected = "x"
|
||
case 2: expected = "Ξ"
|
||
case 5: expected = shared
|
||
case 6,7: expected = "xá"
|
||
default:
|
||
fatalError("case unhandled!")
|
||
}
|
||
expectEqual(expected, base)
|
||
}
|
||
#else
|
||
expectUnreachable()
|
||
#endif
|
||
}
|
||
|
||
func makeStringGuts(_ base: String) -> _StringGuts {
|
||
var x = String(_StringGuts())
|
||
// make sure some - but not all - replacements will have to grow the buffer
|
||
x.reserveCapacity(base._classify()._count * 3 / 2)
|
||
let capacity = x.capacity
|
||
x.append(base)
|
||
// 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._guts
|
||
}
|
||
|
||
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(
|
||
{ StringFauxUTF16Collection(g1) },
|
||
{ StringFauxUTF16Collection(g2)[0..<$0] }
|
||
)
|
||
checkRangeReplaceable(
|
||
{ StringFauxUTF16Collection(g1) },
|
||
{ Array(StringFauxUTF16Collection(g2))[0..<$0] }
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
StringTests.test("UnicodeScalarViewReplace") {
|
||
let narrow = "01234567890"
|
||
let wide = "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪ"
|
||
for s1 in [narrow, wide] {
|
||
for s2 in [narrow, wide] {
|
||
let doubleS2 = Array(String(makeStringGuts(s2 + s2)).utf16)
|
||
checkRangeReplaceable(
|
||
{ () -> String.UnicodeScalarView in String(makeStringGuts(s1)).unicodeScalars },
|
||
{ String(decoding: doubleS2[0..<$0], as: UTF16.self).unicodeScalars }
|
||
)
|
||
checkRangeReplaceable(
|
||
{ String(makeStringGuts(s1)).unicodeScalars },
|
||
{ Array(String(makeStringGuts(s2 + s2)).unicodeScalars)[0..<$0] }
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
StringTests.test("StringRRC") {
|
||
let narrow = "01234567890"
|
||
let wide = "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪ"
|
||
for s1 in [narrow, wide] {
|
||
for s2 in [narrow, wide] {
|
||
let doubleS2 = Array(String(makeStringGuts(s2 + s2)).utf16)
|
||
checkRangeReplaceable(
|
||
{ () -> String in String(makeStringGuts(s1)) },
|
||
{ String(decoding: doubleS2[0..<$0], as: UTF16.self) }
|
||
)
|
||
checkRangeReplaceable(
|
||
{ String(makeStringGuts(s1)) },
|
||
{ Array(String(makeStringGuts(s2 + s2)))[0..<$0] }
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
StringTests.test("reserveCapacity") {
|
||
var s = ""
|
||
let id0 = s.bufferID
|
||
let oldCap = s.capacity
|
||
let x: Character = "x" // Help the typechecker - <rdar://problem/17128913>
|
||
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) [actual capacity: \(s.capacity)]")
|
||
let id1 = s.bufferID
|
||
s.insert(contentsOf: repeatElement(x, count: oldCap + 2), at: s.endIndex)
|
||
print("extending by \(oldCap + 2) [actual capacity: \(s.capacity)]")
|
||
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("-"))
|
||
expectEqual(20, Int("+20"))
|
||
expectEqual(0, Int("0"))
|
||
expectEqual(-20, Int("-20"))
|
||
expectNil(Int("-cc20"))
|
||
expectNil(Int(" -20"))
|
||
expectNil(Int(" \t 20ddd"))
|
||
|
||
expectEqual(Int.min, Int("\(Int.min)"))
|
||
expectEqual(Int.min + 1, Int("\(Int.min + 1)"))
|
||
expectEqual(Int.max, Int("\(Int.max)"))
|
||
expectEqual(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(decoding: chars, as: UTF8.self)
|
||
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)
|
||
expectEqual(Int.min + 1, Int("-\(base)"))
|
||
expectEqual(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 {
|
||
expectEqual(-Int(base - i) , Int("-\(base - i)"))
|
||
}
|
||
}
|
||
|
||
// Test values greater than max.
|
||
do {
|
||
let base = UInt(Int.max)
|
||
expectEqual(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 {
|
||
expectEqual(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, 40)
|
||
}
|
||
|
||
StringTests.test("Construction") {
|
||
expectEqual("abc", String(["a", "b", "c"] as [Character]))
|
||
}
|
||
|
||
StringTests.test("Conversions") {
|
||
// Whether we are natively ASCII or small ASCII
|
||
func isKnownASCII(_ s: String) -> Bool {
|
||
return s.byteWidth == 1
|
||
}
|
||
do {
|
||
let c: Character = "a"
|
||
let x = String(c)
|
||
expectTrue(isKnownASCII(x))
|
||
|
||
let s: String = "a"
|
||
expectEqual(s, x)
|
||
}
|
||
|
||
do {
|
||
let c: Character = "\u{B977}"
|
||
let x = String(c)
|
||
expectFalse(isKnownASCII(x))
|
||
|
||
let s: String = "\u{B977}"
|
||
expectEqual(s, x)
|
||
}
|
||
}
|
||
|
||
|
||
#if canImport(Glibc)
|
||
import Glibc
|
||
#endif
|
||
|
||
StringTests.test("lowercased()") {
|
||
// Use setlocale so tolower() is correct on ASCII.
|
||
setlocale(LC_ALL, "C")
|
||
|
||
// Check the ASCII domain.
|
||
let asciiDomain: [Int32] = Array(0..<128)
|
||
expectEqualFunctionsForDomain(
|
||
asciiDomain,
|
||
{ String(UnicodeScalar(Int(tolower($0)))!) },
|
||
{ String(UnicodeScalar(Int($0))!).lowercased() })
|
||
|
||
expectEqual("", "".lowercased())
|
||
expectEqual("abcd", "abCD".lowercased())
|
||
expectEqual("абвг", "абВГ".lowercased())
|
||
expectEqual("たちつてと", "たちつてと".lowercased())
|
||
|
||
//
|
||
// Special casing.
|
||
//
|
||
|
||
// U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE
|
||
// to lower case:
|
||
// U+0069 LATIN SMALL LETTER I
|
||
// U+0307 COMBINING DOT ABOVE
|
||
expectEqual("\u{0069}\u{0307}", "\u{0130}".lowercased())
|
||
|
||
// U+0049 LATIN CAPITAL LETTER I
|
||
// U+0307 COMBINING DOT ABOVE
|
||
// to lower case:
|
||
// U+0069 LATIN SMALL LETTER I
|
||
// U+0307 COMBINING DOT ABOVE
|
||
expectEqual("\u{0069}\u{0307}", "\u{0049}\u{0307}".lowercased())
|
||
}
|
||
|
||
StringTests.test("uppercased()") {
|
||
// Use setlocale so toupper() is correct on ASCII.
|
||
setlocale(LC_ALL, "C")
|
||
|
||
// Check the ASCII domain.
|
||
let asciiDomain: [Int32] = Array(0..<128)
|
||
expectEqualFunctionsForDomain(
|
||
asciiDomain,
|
||
{ String(UnicodeScalar(Int(toupper($0)))!) },
|
||
{ String(UnicodeScalar(Int($0))!).uppercased() })
|
||
|
||
expectEqual("", "".uppercased())
|
||
expectEqual("ABCD", "abCD".uppercased())
|
||
expectEqual("АБВГ", "абВГ".uppercased())
|
||
expectEqual("たちつてと", "たちつてと".uppercased())
|
||
|
||
//
|
||
// Special casing.
|
||
//
|
||
|
||
// U+0069 LATIN SMALL LETTER I
|
||
// to upper case:
|
||
// U+0049 LATIN CAPITAL LETTER I
|
||
expectEqual("\u{0049}", "\u{0069}".uppercased())
|
||
|
||
// U+00DF LATIN SMALL LETTER SHARP S
|
||
// to upper case:
|
||
// U+0053 LATIN CAPITAL LETTER S
|
||
// U+0073 LATIN SMALL LETTER S
|
||
// But because the whole string is converted to uppercase, we just get two
|
||
// U+0053.
|
||
expectEqual("\u{0053}\u{0053}", "\u{00df}".uppercased())
|
||
|
||
// U+FB01 LATIN SMALL LIGATURE FI
|
||
// to upper case:
|
||
// U+0046 LATIN CAPITAL LETTER F
|
||
// U+0069 LATIN SMALL LETTER I
|
||
// But because the whole string is converted to uppercase, we get U+0049
|
||
// LATIN CAPITAL LETTER I.
|
||
expectEqual("\u{0046}\u{0049}", "\u{fb01}".uppercased())
|
||
}
|
||
|
||
StringTests.test("unicodeViews") {
|
||
// Check the UTF views work with slicing
|
||
|
||
// U+FFFD REPLACEMENT CHARACTER
|
||
// U+1F3C2 SNOWBOARDER
|
||
// U+2603 SNOWMAN
|
||
let winter = "\u{1F3C2}\u{2603}"
|
||
|
||
// slices
|
||
// First scalar is 4 bytes long, so this should be invalid
|
||
expectNil(
|
||
String(winter.utf8[
|
||
winter.utf8.startIndex
|
||
..<
|
||
winter.utf8.index(after: winter.utf8.index(after: winter.utf8.startIndex))
|
||
]))
|
||
|
||
/*
|
||
// FIXME: note changed String(describing:) results
|
||
expectEqual(
|
||
"\u{FFFD}",
|
||
String(describing:
|
||
winter.utf8[
|
||
winter.utf8.startIndex
|
||
..<
|
||
winter.utf8.index(after: winter.utf8.index(after: winter.utf8.startIndex))
|
||
]))
|
||
*/
|
||
|
||
expectEqual(
|
||
"\u{1F3C2}", String(
|
||
winter.utf8[winter.utf8.startIndex..<winter.utf8.index(_nth: 4)]))
|
||
|
||
expectEqual(
|
||
"\u{1F3C2}", String(
|
||
winter.utf16[winter.utf16.startIndex..<winter.utf16.index(_nth: 2)]))
|
||
|
||
expectEqual(
|
||
"\u{1F3C2}", String(
|
||
winter.unicodeScalars[
|
||
winter.unicodeScalars.startIndex..<winter.unicodeScalars.index(_nth: 1)
|
||
]))
|
||
|
||
// views
|
||
expectEqual(
|
||
winter, String(
|
||
winter.utf8[winter.utf8.startIndex..<winter.utf8.index(_nth: 7)]))
|
||
|
||
expectEqual(
|
||
winter, String(
|
||
winter.utf16[winter.utf16.startIndex..<winter.utf16.index(_nth: 3)]))
|
||
|
||
expectEqual(
|
||
winter, String(
|
||
winter.unicodeScalars[
|
||
winter.unicodeScalars.startIndex..<winter.unicodeScalars.index(_nth: 2)
|
||
]))
|
||
|
||
let ga = "\u{304b}\u{3099}"
|
||
expectEqual(ga, String(ga.utf8[ga.utf8.startIndex..<ga.utf8.index(_nth: 6)]))
|
||
}
|
||
|
||
// Validate that index conversion does something useful for Cocoa
|
||
// programmers.
|
||
StringTests.test("indexConversion")
|
||
.skip(.nativeRuntime("Foundation dependency"))
|
||
.code {
|
||
#if _runtime(_ObjC)
|
||
let re : NSRegularExpression
|
||
do {
|
||
re = try NSRegularExpression(
|
||
pattern: "([^ ]+)er", options: NSRegularExpression.Options())
|
||
} catch { fatalError("couldn't build regexp: \(error)") }
|
||
|
||
let s = "go further into the larder to barter."
|
||
|
||
var matches: [String] = []
|
||
|
||
re.enumerateMatches(
|
||
in: s, options: NSRegularExpression.MatchingOptions(), range: NSRange(0..<s.utf16.count)
|
||
) {
|
||
result, flags, stop
|
||
in
|
||
let r = result!.range(at: 1)
|
||
let start = String.Index(_encodedOffset: r.location)
|
||
let end = String.Index(_encodedOffset: r.location + r.length)
|
||
matches.append(String(s.utf16[start..<end])!)
|
||
}
|
||
|
||
expectEqual(["furth", "lard", "bart"], matches)
|
||
#else
|
||
expectUnreachable()
|
||
#endif
|
||
}
|
||
|
||
StringTests.test("String.append(_: UnicodeScalar)") {
|
||
var s = ""
|
||
|
||
do {
|
||
// U+0061 LATIN SMALL LETTER A
|
||
let input: UnicodeScalar = "\u{61}"
|
||
s.append(String(input))
|
||
expectEqual(["\u{61}"], Array(s.unicodeScalars))
|
||
}
|
||
do {
|
||
// U+304B HIRAGANA LETTER KA
|
||
let input: UnicodeScalar = "\u{304b}"
|
||
s.append(String(input))
|
||
expectEqual(["\u{61}", "\u{304b}"], Array(s.unicodeScalars))
|
||
}
|
||
do {
|
||
// U+3099 COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK
|
||
let input: UnicodeScalar = "\u{3099}"
|
||
s.append(String(input))
|
||
expectEqual(["\u{61}", "\u{304b}", "\u{3099}"], Array(s.unicodeScalars))
|
||
}
|
||
do {
|
||
// U+1F425 FRONT-FACING BABY CHICK
|
||
let input: UnicodeScalar = "\u{1f425}"
|
||
s.append(String(input))
|
||
expectEqual(
|
||
["\u{61}", "\u{304b}", "\u{3099}", "\u{1f425}"],
|
||
Array(s.unicodeScalars))
|
||
}
|
||
}
|
||
|
||
StringTests.test("String.append(_: Character)") {
|
||
let baseCharacters: [Character] = [
|
||
// U+0061 LATIN SMALL LETTER A
|
||
"\u{61}",
|
||
|
||
// U+304B HIRAGANA LETTER KA
|
||
// U+3099 COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK
|
||
"\u{304b}\u{3099}",
|
||
|
||
// U+3072 HIRAGANA LETTER HI
|
||
// U+309A COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
|
||
"\u{3072}\u{309A}",
|
||
|
||
// U+1F425 FRONT-FACING BABY CHICK
|
||
"\u{1f425}",
|
||
|
||
// U+0061 LATIN SMALL LETTER A
|
||
// U+0300 COMBINING GRAVE ACCENT
|
||
// U+0301 COMBINING ACUTE ACCENT
|
||
"\u{61}\u{0300}\u{0301}",
|
||
|
||
// U+0061 LATIN SMALL LETTER A
|
||
// U+0300 COMBINING GRAVE ACCENT
|
||
// U+0301 COMBINING ACUTE ACCENT
|
||
// U+0302 COMBINING CIRCUMFLEX ACCENT
|
||
"\u{61}\u{0300}\u{0301}\u{0302}",
|
||
|
||
// U+0061 LATIN SMALL LETTER A
|
||
// U+0300 COMBINING GRAVE ACCENT
|
||
// U+0301 COMBINING ACUTE ACCENT
|
||
// U+0302 COMBINING CIRCUMFLEX ACCENT
|
||
// U+0303 COMBINING TILDE
|
||
"\u{61}\u{0300}\u{0301}\u{0302}\u{0303}",
|
||
]
|
||
let baseStrings = [""] + baseCharacters.map { String($0) }
|
||
|
||
for baseIdx in baseStrings.indices {
|
||
for prefix in ["", " "] {
|
||
let base = baseStrings[baseIdx]
|
||
for inputIdx in baseCharacters.indices {
|
||
let input = (prefix + String(baseCharacters[inputIdx])).last!
|
||
var s = base
|
||
s.append(input)
|
||
expectEqualSequence(
|
||
Array(base) + [input],
|
||
Array(s),
|
||
"baseIdx=\(baseIdx) inputIdx=\(inputIdx)")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
internal func decodeCString<
|
||
C : UnicodeCodec
|
||
>(_ 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"
|
||
),
|
||
ReplaceSubrangeTest(
|
||
original: "bobbobbobbobbobbobbobbobbobbob",
|
||
newElements: "meela",
|
||
rangeSelection: .offsets(1, 2),
|
||
expected: "bmeelabbobbobbobbobbobbobbobbobbob",
|
||
closedExpected: "bmeelabobbobbobbobbobbobbobbobbob"
|
||
),
|
||
ReplaceSubrangeTest(
|
||
original: "bob",
|
||
newElements: "meelameelameelameelameela",
|
||
rangeSelection: .offsets(1, 2),
|
||
expected: "bmeelameelameelameelameelab",
|
||
closedExpected: "bmeelameelameelameelameela"
|
||
),
|
||
]
|
||
|
||
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"
|
||
),
|
||
RemoveSubrangeTest(
|
||
original: "perdicusaliceandbob",
|
||
rangeSelection: .offsets(3, 6),
|
||
expected: "perusaliceandbob",
|
||
closedExpected: "persaliceandbob"
|
||
)
|
||
]
|
||
|
||
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))
|
||
|
||
// Test unique-native optimized path
|
||
var uniqNative = String(Array(test.original))
|
||
uniqNative.replaceSubrange(rangeToReplace, with: newCharacters)
|
||
expectEqual(theString, uniqNative,
|
||
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))
|
||
|
||
// Test unique-native optimized path
|
||
var uniqNative = String(Array(test.original))
|
||
uniqNative.replaceSubrange(rangeToReplace, with: test.newElements)
|
||
expectEqual(theString, uniqNative,
|
||
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))
|
||
|
||
// Test unique-native optimized path
|
||
var uniqNative = String(Array(test.original))
|
||
uniqNative.replaceSubrange(rangeToReplace, with: newCharacters)
|
||
expectEqual(theString, uniqNative,
|
||
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))
|
||
|
||
// Test unique-native optimized path
|
||
var uniqNative = String(Array(test.original))
|
||
uniqNative.replaceSubrange(rangeToReplace, with: test.newElements)
|
||
expectEqual(theString, uniqNative,
|
||
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))
|
||
|
||
// Test unique-native optimized path
|
||
var uniqNative = String(Array(test.original))
|
||
uniqNative.removeSubrange(rangeToRemove)
|
||
expectEqual(theString, uniqNative,
|
||
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))
|
||
|
||
// Test unique-native optimized path
|
||
var uniqNative = String(Array(test.original))
|
||
uniqNative.removeSubrange(rangeToRemove)
|
||
expectEqual(theString, uniqNative,
|
||
stackTrace: SourceLocStack().with(test.loc))
|
||
}
|
||
}
|
||
|
||
//===----------------------------------------------------------------------===//
|
||
// COW(🐄) tests
|
||
//===----------------------------------------------------------------------===//
|
||
|
||
public let testSuffix = "z"
|
||
StringTests.test("COW.Smoke") {
|
||
var s1 = "COW Smoke 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: "abcdefghijklmnopqrxtuvwxyz", 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())
|
||
}
|
||
}
|
||
|
||
enum _Ordering: Int, Equatable {
|
||
case less = -1
|
||
case equal = 0
|
||
case greater = 1
|
||
|
||
var flipped: _Ordering {
|
||
switch self {
|
||
case .less: return .greater
|
||
case .equal: return .equal
|
||
case .greater: return .less
|
||
}
|
||
}
|
||
|
||
init(signedNotation int: Int) {
|
||
self = int < 0 ? .less : int == 0 ? .equal : .greater
|
||
}
|
||
}
|
||
|
||
struct ComparisonTestCase {
|
||
var strings: [String]
|
||
// var test: (String, String) -> Void
|
||
var comparison: _Ordering
|
||
|
||
init(_ strings: [String], _ comparison: _Ordering) {
|
||
self.strings = strings
|
||
self.comparison = comparison
|
||
}
|
||
|
||
func test() {
|
||
for pair in zip(strings, strings[1...]) {
|
||
switch comparison {
|
||
case .less:
|
||
expectLT(pair.0, pair.1)
|
||
if !pair.0.isEmpty {
|
||
// Test mixed String/Substring
|
||
expectTrue(pair.0.dropLast() < pair.1)
|
||
}
|
||
case .greater:
|
||
expectGT(pair.0, pair.1)
|
||
if !pair.1.isEmpty {
|
||
// Test mixed String/Substring
|
||
expectTrue(pair.0 > pair.1.dropLast())
|
||
}
|
||
case .equal:
|
||
expectEqual(pair.0, pair.1)
|
||
if !pair.0.isEmpty {
|
||
// Test mixed String/Substring
|
||
expectTrue(pair.0.dropLast() == pair.1.dropLast())
|
||
expectFalse(pair.0.dropFirst() == pair.1)
|
||
expectFalse(pair.0 == pair.1.dropFirst())
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func testOpaqueStrings() {
|
||
#if _runtime(_ObjC)
|
||
let opaqueStrings = strings.map { NSSlowString(string: $0) as String }
|
||
for pair in zip(opaqueStrings, opaqueStrings[1...]) {
|
||
switch comparison {
|
||
case .less:
|
||
expectLT(pair.0, pair.1)
|
||
case .greater:
|
||
expectGT(pair.0, pair.1)
|
||
case .equal:
|
||
expectEqual(pair.0, pair.1)
|
||
}
|
||
}
|
||
expectEqualSequence(strings, opaqueStrings)
|
||
#endif
|
||
}
|
||
|
||
func testOpaqueSubstrings() {
|
||
#if _runtime(_ObjC)
|
||
for pair in zip(strings, strings[1...]) {
|
||
let string1 = pair.0.dropLast()
|
||
let string2 = pair.1
|
||
let opaqueString = (NSSlowString(string: pair.0) as String).dropLast()
|
||
|
||
guard string1.count > 0 else { return }
|
||
|
||
expectEqual(string1, opaqueString)
|
||
expectEqual(string1 < string2, opaqueString < string2)
|
||
expectEqual(string1 > string2, opaqueString > string2)
|
||
expectEqual(string1 == string2, opaqueString == string2)
|
||
}
|
||
#endif
|
||
}
|
||
}
|
||
|
||
let comparisonTestCases = [
|
||
ComparisonTestCase(["a", "a"], .equal),
|
||
ComparisonTestCase(["abcdefg", "abcdefg"], .equal),
|
||
ComparisonTestCase(["", "Z", "a", "b", "c", "\u{00c5}", "á"], .less),
|
||
|
||
ComparisonTestCase(["ábcdefg", "ábcdefgh", "ábcdefghi"], .less),
|
||
ComparisonTestCase(["abcdefg", "abcdefgh", "abcdefghi"], .less),
|
||
|
||
ComparisonTestCase(["á", "\u{0061}\u{0301}"], .equal),
|
||
ComparisonTestCase(["à", "\u{0061}\u{0301}", "â", "\u{e3}", "a\u{0308}"], .less),
|
||
|
||
// Exploding scalars AND exploding segments
|
||
ComparisonTestCase(["\u{fa2}", "\u{fa1}\u{fb7}"], .equal),
|
||
ComparisonTestCase([
|
||
"\u{fa2}\u{fa2}\u{fa2}\u{fa2}",
|
||
"\u{fa1}\u{fb7}\u{fa1}\u{fb7}\u{fa1}\u{fb7}\u{fa1}\u{fb7}"
|
||
], .equal),
|
||
ComparisonTestCase([
|
||
"\u{fa2}\u{fa2}\u{fa2}\u{fa2}\u{fa2}\u{fa2}\u{fa2}\u{fa2}",
|
||
"\u{fa1}\u{fb7}\u{fa1}\u{fb7}\u{fa1}\u{fb7}\u{fa1}\u{fb7}\u{fa1}\u{fb7}\u{fa1}\u{fb7}\u{fa1}\u{fb7}\u{fa1}\u{fb7}"
|
||
], .equal),
|
||
ComparisonTestCase([
|
||
"a\u{fa2}\u{fa2}a\u{fa2}\u{fa2}\u{fa2}\u{fa2}\u{fa2}\u{fa2}",
|
||
"a\u{fa1}\u{fb7}\u{fa1}\u{fb7}a\u{fa1}\u{fb7}\u{fa1}\u{fb7}\u{fa1}\u{fb7}\u{fa1}\u{fb7}\u{fa1}\u{fb7}\u{fa1}\u{fb7}"
|
||
], .equal),
|
||
|
||
ComparisonTestCase(["a\u{301}\u{0301}", "\u{e1}"], .greater),
|
||
ComparisonTestCase(["😀", "😀"], .equal),
|
||
ComparisonTestCase(["\u{2f9df}", "\u{8f38}"], .equal),
|
||
ComparisonTestCase([
|
||
"a",
|
||
"\u{2f9df}", // D87E DDDF as written, but normalizes to 8f38
|
||
"\u{2f9df}\u{2f9df}", // D87E DDDF as written, but normalizes to 8f38
|
||
"👨🏻", // D83D DC68 D83C DFFB
|
||
"👨🏻⚕️", // D83D DC68 D83C DFFB 200D 2695 FE0F
|
||
"👩⚕️", // D83D DC69 200D 2695 FE0F
|
||
"👩🏾", // D83D DC69 D83C DFFE
|
||
"👩🏾⚕", // D83D DC69 D83C DFFE 200D 2695 FE0F
|
||
"😀", // D83D DE00
|
||
"😅", // D83D DE05
|
||
"🧀" // D83E DDC0 -- aka a really big scalar
|
||
], .less),
|
||
|
||
|
||
ComparisonTestCase(["f̛̗̘̙̜̹̺̻̼͇͈͉͍͎̽̾̿̀́͂̓̈́͆͊͋͌̚ͅ͏͓͔͕͖͙͚͐͑͒͗͛ͣͤͥͦ͘͜͟͢͝͞͠͡", "ơ̗̘̙̜̹̺̻̼͇͈͉͍͎̽̾̿̀́͂̓̈́͆͊͋͌̚ͅ͏͓͔͕͖͙͚͐͑͒͗͛ͥͦͧͨͩͪͫͬͭͮ͘"], .less),
|
||
ComparisonTestCase(["\u{f90b}", "\u{5587}"], .equal),
|
||
|
||
ComparisonTestCase(["a\u{1D160}a", "a\u{1D158}\u{1D1C7}"], .less),
|
||
|
||
ComparisonTestCase(["a\u{305}\u{315}", "a\u{315}\u{305}"], .equal),
|
||
ComparisonTestCase(["a\u{315}bz", "a\u{315}\u{305}az"], .greater),
|
||
|
||
ComparisonTestCase(["\u{212b}", "\u{00c5}"], .equal),
|
||
ComparisonTestCase([
|
||
"A",
|
||
"a",
|
||
"aa",
|
||
"ae",
|
||
"ae🧀",
|
||
"az",
|
||
"aze\u{300}",
|
||
"ae\u{301}",
|
||
"ae\u{301}ae\u{301}",
|
||
"ae\u{301}ae\u{301}ae\u{301}",
|
||
"ae\u{301}ae\u{301}ae\u{301}ae\u{301}",
|
||
"ae\u{301}ae\u{301}ae\u{301}ae\u{301}ae\u{301}",
|
||
"ae\u{301}ae\u{301}ae\u{301}ae\u{301}ae\u{301}ae\u{301}",
|
||
"ae\u{301}ae\u{301}ae\u{301}ae\u{301}ae\u{301}ae\u{301}ae\u{301}",
|
||
"ae\u{301}ae\u{301}ae\u{301}ae\u{301}ae\u{301}ae\u{301}ae\u{301}ae\u{301}",
|
||
"ae\u{301}\u{302}",
|
||
"ae\u{302}",
|
||
"ae\u{302}{303}",
|
||
"ae\u{302}🧀",
|
||
"ae\u{303}",
|
||
"x\u{0939}x",
|
||
"x\u{0939}\u{093a}x",
|
||
"x\u{0939}\u{093a}\u{093b}x",
|
||
"\u{f90b}\u{f90c}\u{f90d}", // Normalizes to BMP scalars
|
||
"\u{FFEE}", // half width CJK dot
|
||
"🧀", // D83E DDC0 -- aka a really big scalar
|
||
], .less),
|
||
|
||
ComparisonTestCase(["ư̴̵̶̷̸̗̘̙̜̹̺̻̼͇͈͉͍͎̽̾̿̀́͂̓̈́͆͊͋͌̚ͅ͏͓͔͕͖͙͚͐͑͒͗͛ͣͤͥͦͧͨͩͪͫͬͭͮ͘͜͟͢͝͞͠͡", "ì̡̢̧̨̝̞̟̠̣̤̥̦̩̪̫̬̭̮̯̰̹̺̻̼͇͈͉͍͎́̂̃̄̉̊̋̌̍̎̏̐̑̒̓̽̾̿̀́͂̓̈́͆͊͋͌ͅ͏͓͔͕͖͙͐͑͒͗ͬͭͮ͘"], .greater),
|
||
ComparisonTestCase(["ư̴̵̶̷̸̗̘̙̜̹̺̻̼͇͈͉͍͎̽̾̿̀́͂̓̈́͆͊͋͌̚ͅ͏͓͔͕͖͙͚͐͑͒͗͛ͣͤͥͦͧͨͩͪͫͬͭͮ͘͜͟͢͝͞͠͡", "aì̡̢̧̨̝̞̟̠̣̤̥̦̩̪̫̬̭̮̯̰̹̺̻̼͇͈͉͍͎́̂̃̄̉̊̋̌̍̎̏̐̑̒̓̽̾̿̀́͂̓̈́͆͊͋͌ͅ͏͓͔͕͖͙͐͑͒͗ͬͭͮ͘"], .greater),
|
||
ComparisonTestCase(["ì̡̢̧̨̝̞̟̠̣̤̥̦̩̪̫̬̭̮̯̰̹̺̻̼͇͈͉͍͎́̂̃̄̉̊̋̌̍̎̏̐̑̒̓̽̾̿̀́͂̓̈́͆͊͋͌ͅ͏͓͔͕͖͙͐͑͒͗ͬͭͮ͘", "ì̡̢̧̨̝̞̟̠̣̤̥̦̩̪̫̬̭̮̯̰̹̺̻̼͇͈͉͍͎́̂̃̄̉̊̋̌̍̎̏̐̑̒̓̽̾̿̀́͂̓̈́͆͊͋͌ͅ͏͓͔͕͖͙͐͑͒͗ͬͭͮ͘"], .equal)
|
||
]
|
||
|
||
for test in comparisonTestCases {
|
||
StringTests.test("Comparison.\(test.strings)") {
|
||
test.test()
|
||
}
|
||
|
||
StringTests.test("Comparison.OpaqueString.\(test.strings)")
|
||
.skip(.linuxAny(reason: "NSSlowString requires ObjC interop"))
|
||
.code {
|
||
test.testOpaqueStrings()
|
||
}
|
||
|
||
StringTests.test("Comparison.OpaqueSubstring.\(test.strings)")
|
||
.skip(.linuxAny(reason: "NSSlowString requires ObjC interop"))
|
||
.code {
|
||
test.testOpaqueSubstrings()
|
||
}
|
||
}
|
||
|
||
StringTests.test("Comparison.Substrings") {
|
||
let str = "abcdefg"
|
||
let expectedStr = "bcdef"
|
||
let substring = str.dropFirst().dropLast()
|
||
|
||
expectEqual(expectedStr, substring)
|
||
}
|
||
|
||
StringTests.test("Comparison.Substrings/Opaque")
|
||
.skip(.linuxAny(reason: "NSSlowString requires ObjC interop"))
|
||
.code {
|
||
#if _runtime(_ObjC)
|
||
let str = NSSlowString(string: "abcdefg") as String
|
||
let expectedStr = NSSlowString(string: "bcdef") as String
|
||
let substring = str.dropFirst().dropLast()
|
||
|
||
expectEqual(expectedStr, substring)
|
||
#endif
|
||
}
|
||
|
||
StringTests.test("NormalizationBufferCrashRegressionTest") {
|
||
let str = "\u{0336}\u{0344}\u{0357}\u{0343}\u{0314}\u{0351}\u{0340}\u{0300}\u{0340}\u{0360}\u{0314}\u{0357}\u{0315}\u{0301}\u{0344}a"
|
||
let set = Set([str])
|
||
|
||
expectTrue(set.contains(str))
|
||
}
|
||
|
||
StringTests.test("NormalizationCheck") {
|
||
let str = "\u{0336}\u{0344}\u{0357}\u{0343}\u{0314}\u{0351}\u{0340}\u{0300}\u{0340}\u{0360}\u{0314}\u{0357}\u{0315}\u{0301}\u{0344}a"
|
||
let nfcCodeUnits = str._nfcCodeUnits
|
||
let expectedCodeUnits: [UInt8] = [0xCC, 0xB6, 0xCC, 0x88, 0xCC, 0x81, 0xCD, 0x97, 0xCC, 0x93, 0xCC, 0x94, 0xCD, 0x91, 0xCC, 0x80, 0xCC, 0x80, 0xCC, 0x80, 0xCC, 0x94, 0xCD, 0x97, 0xCC, 0x81, 0xCC, 0x88, 0xCC, 0x81, 0xCC, 0x95, 0xCD, 0xA0, 0x61]
|
||
|
||
expectEqual(expectedCodeUnits, nfcCodeUnits)
|
||
}
|
||
|
||
StringTests.test("NormalizationCheck/Opaque")
|
||
.skip(.linuxAny(reason: "NSSlowString requires ObjC interop"))
|
||
.code {
|
||
#if _runtime(_ObjC)
|
||
let str = "\u{0336}\u{0344}\u{0357}\u{0343}\u{0314}\u{0351}\u{0340}\u{0300}\u{0340}\u{0360}\u{0314}\u{0357}\u{0315}\u{0301}\u{0344}a"
|
||
let opaqueString = NSSlowString(string: str) as String
|
||
let nfcCodeUnits = opaqueString._nfcCodeUnits
|
||
let expectedCodeUnits: [UInt8] = [0xCC, 0xB6, 0xCC, 0x88, 0xCC, 0x81, 0xCD, 0x97, 0xCC, 0x93, 0xCC, 0x94, 0xCD, 0x91, 0xCC, 0x80, 0xCC, 0x80, 0xCC, 0x80, 0xCC, 0x94, 0xCD, 0x97, 0xCC, 0x81, 0xCC, 0x88, 0xCC, 0x81, 0xCC, 0x95, 0xCD, 0xA0, 0x61]
|
||
|
||
expectEqual(expectedCodeUnits, nfcCodeUnits)
|
||
#endif
|
||
}
|
||
|
||
runAllTests()
|