mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
444 lines
14 KiB
Swift
444 lines
14 KiB
Swift
// RUN: rm -rf %t
|
||
// RUN: mkdir -p %t
|
||
// RUN: %target-build-swift %s -o %t/a.out
|
||
// RUN: %target-run %t/a.out
|
||
// REQUIRES: executable_test
|
||
|
||
//
|
||
// Tests for the non-Foundation API of String
|
||
//
|
||
|
||
import StdlibUnittest
|
||
|
||
// Also import modules which are used by StdlibUnittest internally. This
|
||
// workaround is needed to link all required libraries in case we compile
|
||
// StdlibUnittest with -sil-serialize-all.
|
||
import SwiftPrivate
|
||
#if _runtime(_ObjC)
|
||
import ObjectiveC
|
||
import Foundation
|
||
import StdlibUnittestFoundationExtras
|
||
#endif
|
||
|
||
var StringTests = TestSuite("StringTests")
|
||
|
||
struct ComparisonTest {
|
||
let expectedUnicodeCollation: ExpectedComparisonResult
|
||
let lhs: String
|
||
let rhs: String
|
||
let loc: SourceLoc
|
||
let xfail: TestRunPredicate
|
||
|
||
init(
|
||
_ expectedUnicodeCollation: ExpectedComparisonResult,
|
||
_ lhs: String, _ rhs: String,
|
||
xfail: TestRunPredicate = .custom({false}, reason: ""),
|
||
file: String = #file, line: UInt = #line
|
||
) {
|
||
self.expectedUnicodeCollation = expectedUnicodeCollation
|
||
self.lhs = lhs
|
||
self.rhs = rhs
|
||
self.loc = SourceLoc(file, line, comment: "test data")
|
||
self.xfail = xfail
|
||
}
|
||
}
|
||
|
||
let comparisonTests = [
|
||
ComparisonTest(.eq, "", ""),
|
||
ComparisonTest(.lt, "", "a"),
|
||
|
||
// ASCII cases
|
||
ComparisonTest(.lt, "t", "tt"),
|
||
ComparisonTest(.gt, "t", "Tt",
|
||
xfail: .nativeRuntime(
|
||
"Compares in reverse with ICU, https://bugs.swift.org/browse/SR-530")),
|
||
ComparisonTest(.gt, "\u{0}", "",
|
||
xfail: .nativeRuntime(
|
||
"Null-related issue: https://bugs.swift.org/browse/SR-630")),
|
||
ComparisonTest(.eq, "\u{0}", "\u{0}"),
|
||
// Currently fails:
|
||
// ComparisonTest(.lt, "\r\n", "t"),
|
||
// ComparisonTest(.gt, "\r\n", "\n"),
|
||
// ComparisonTest(.lt, "\u{0}", "\u{0}\u{0}"),
|
||
|
||
// Whitespace
|
||
// U+000A LINE FEED (LF)
|
||
// U+000B LINE TABULATION
|
||
// U+000C FORM FEED (FF)
|
||
// U+0085 NEXT LINE (NEL)
|
||
// U+2028 LINE SEPARATOR
|
||
// U+2029 PARAGRAPH SEPARATOR
|
||
ComparisonTest(.gt, "\u{0085}", "\n"),
|
||
ComparisonTest(.gt, "\u{000b}", "\n"),
|
||
ComparisonTest(.gt, "\u{000c}", "\n"),
|
||
ComparisonTest(.gt, "\u{2028}", "\n"),
|
||
ComparisonTest(.gt, "\u{2029}", "\n"),
|
||
ComparisonTest(.gt, "\r\n\r\n", "\r\n"),
|
||
|
||
// U+0301 COMBINING ACUTE ACCENT
|
||
// U+00E1 LATIN SMALL LETTER A WITH ACUTE
|
||
ComparisonTest(.eq, "a\u{301}", "\u{e1}"),
|
||
ComparisonTest(.lt, "a", "a\u{301}"),
|
||
ComparisonTest(.lt, "a", "\u{e1}"),
|
||
|
||
// U+304B HIRAGANA LETTER KA
|
||
// U+304C HIRAGANA LETTER GA
|
||
// U+3099 COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK
|
||
ComparisonTest(.eq, "\u{304b}", "\u{304b}"),
|
||
ComparisonTest(.eq, "\u{304c}", "\u{304c}"),
|
||
ComparisonTest(.lt, "\u{304b}", "\u{304c}"),
|
||
ComparisonTest(.lt, "\u{304b}", "\u{304c}\u{3099}"),
|
||
ComparisonTest(.eq, "\u{304c}", "\u{304b}\u{3099}"),
|
||
ComparisonTest(.lt, "\u{304c}", "\u{304c}\u{3099}"),
|
||
|
||
// U+212B ANGSTROM SIGN
|
||
// U+030A COMBINING RING ABOVE
|
||
// U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE
|
||
ComparisonTest(.eq, "\u{212b}", "A\u{30a}"),
|
||
ComparisonTest(.eq, "\u{212b}", "\u{c5}"),
|
||
ComparisonTest(.eq, "A\u{30a}", "\u{c5}"),
|
||
ComparisonTest(.lt, "A\u{30a}", "a",
|
||
xfail: .nativeRuntime(
|
||
"Compares in reverse with ICU, https://bugs.swift.org/browse/SR-530")),
|
||
ComparisonTest(.lt, "A", "A\u{30a}"),
|
||
|
||
// U+2126 OHM SIGN
|
||
// U+03A9 GREEK CAPITAL LETTER OMEGA
|
||
ComparisonTest(.eq, "\u{2126}", "\u{03a9}"),
|
||
|
||
// U+0323 COMBINING DOT BELOW
|
||
// U+0307 COMBINING DOT ABOVE
|
||
// U+1E63 LATIN SMALL LETTER S WITH DOT BELOW
|
||
// U+1E69 LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE
|
||
ComparisonTest(.eq, "\u{1e69}", "s\u{323}\u{307}"),
|
||
ComparisonTest(.eq, "\u{1e69}", "s\u{307}\u{323}"),
|
||
ComparisonTest(.eq, "\u{1e69}", "\u{1e63}\u{307}"),
|
||
ComparisonTest(.eq, "\u{1e63}", "s\u{323}"),
|
||
ComparisonTest(.eq, "\u{1e63}\u{307}", "s\u{323}\u{307}"),
|
||
ComparisonTest(.eq, "\u{1e63}\u{307}", "s\u{307}\u{323}"),
|
||
ComparisonTest(.lt, "s\u{323}", "\u{1e69}"),
|
||
|
||
// U+FB01 LATIN SMALL LIGATURE FI
|
||
ComparisonTest(.eq, "\u{fb01}", "\u{fb01}"),
|
||
ComparisonTest(.lt, "fi", "\u{fb01}"),
|
||
|
||
// Test that Unicode collation is performed in deterministic mode.
|
||
//
|
||
// U+0301 COMBINING ACUTE ACCENT
|
||
// U+0341 COMBINING ACUTE TONE MARK
|
||
// U+0954 DEVANAGARI ACUTE ACCENT
|
||
//
|
||
// Collation elements from DUCET:
|
||
// 0301 ; [.0000.0024.0002] # COMBINING ACUTE ACCENT
|
||
// 0341 ; [.0000.0024.0002] # COMBINING ACUTE TONE MARK
|
||
// 0954 ; [.0000.0024.0002] # DEVANAGARI ACUTE ACCENT
|
||
//
|
||
// U+0301 and U+0954 don't decompose in the canonical decomposition mapping.
|
||
// U+0341 has a canonical decomposition mapping of U+0301.
|
||
ComparisonTest(.eq, "\u{0301}", "\u{0341}"),
|
||
ComparisonTest(.lt, "\u{0301}", "\u{0954}",
|
||
xfail: .nativeRuntime("Compares as equal with ICU")),
|
||
ComparisonTest(.lt, "\u{0341}", "\u{0954}",
|
||
xfail: .nativeRuntime("Compares as equal with ICU")),
|
||
]
|
||
|
||
func checkStringComparison(
|
||
expected: ExpectedComparisonResult,
|
||
_ lhs: String, _ rhs: String, _ stackTrace: SourceLocStack
|
||
) {
|
||
// String / String
|
||
expectEqual(expected.isEQ(), lhs == rhs, stackTrace: stackTrace)
|
||
expectEqual(expected.isNE(), lhs != rhs, stackTrace: stackTrace)
|
||
checkHashable(
|
||
expected.isEQ(), lhs, rhs, stackTrace: stackTrace.withCurrentLoc())
|
||
|
||
expectEqual(expected.isLT(), lhs < rhs, stackTrace: stackTrace)
|
||
expectEqual(expected.isLE(), lhs <= rhs, stackTrace: stackTrace)
|
||
expectEqual(expected.isGE(), lhs >= rhs, stackTrace: stackTrace)
|
||
expectEqual(expected.isGT(), lhs > rhs, stackTrace: stackTrace)
|
||
checkComparable(expected, lhs, rhs, stackTrace: stackTrace.withCurrentLoc())
|
||
|
||
#if _runtime(_ObjC)
|
||
// NSString / NSString
|
||
let lhsNSString = lhs as NSString
|
||
let rhsNSString = rhs as NSString
|
||
let expectedEqualUnicodeScalars =
|
||
Array(lhs.unicodeScalars) == Array(rhs.unicodeScalars)
|
||
// FIXME: Swift String and NSString comparison may not be equal.
|
||
expectEqual(
|
||
expectedEqualUnicodeScalars, lhsNSString == rhsNSString,
|
||
stackTrace: stackTrace)
|
||
expectEqual(
|
||
!expectedEqualUnicodeScalars, lhsNSString != rhsNSString,
|
||
stackTrace: stackTrace)
|
||
checkHashable(
|
||
expectedEqualUnicodeScalars, lhsNSString, rhsNSString,
|
||
stackTrace: stackTrace.withCurrentLoc())
|
||
#endif
|
||
}
|
||
|
||
for test in comparisonTests {
|
||
StringTests.test("String.{Equatable,Hashable,Comparable}: line \(test.loc.line)")
|
||
.xfail(test.xfail)
|
||
.code {
|
||
checkStringComparison(
|
||
test.expectedUnicodeCollation, test.lhs, test.rhs,
|
||
test.loc.withCurrentLoc())
|
||
checkStringComparison(
|
||
test.expectedUnicodeCollation.flip(), test.rhs, test.lhs,
|
||
test.loc.withCurrentLoc())
|
||
}
|
||
}
|
||
|
||
func checkCharacterComparison(
|
||
expected: ExpectedComparisonResult,
|
||
_ lhs: Character, _ rhs: Character, _ stackTrace: SourceLocStack
|
||
) {
|
||
// Character / Character
|
||
expectEqual(expected.isEQ(), lhs == rhs, stackTrace: stackTrace)
|
||
expectEqual(expected.isNE(), lhs != rhs, stackTrace: stackTrace)
|
||
checkHashable(
|
||
expected.isEQ(), lhs, rhs, stackTrace: stackTrace.withCurrentLoc())
|
||
|
||
expectEqual(expected.isLT(), lhs < rhs, stackTrace: stackTrace)
|
||
expectEqual(expected.isLE(), lhs <= rhs, stackTrace: stackTrace)
|
||
expectEqual(expected.isGE(), lhs >= rhs, stackTrace: stackTrace)
|
||
expectEqual(expected.isGT(), lhs > rhs, stackTrace: stackTrace)
|
||
checkComparable(expected, lhs, rhs, stackTrace: stackTrace.withCurrentLoc())
|
||
}
|
||
|
||
for test in comparisonTests {
|
||
if test.lhs.characters.count == 1 && test.rhs.characters.count == 1 {
|
||
StringTests.test("Character.{Equatable,Hashable,Comparable}: line \(test.loc.line)")
|
||
.xfail(test.xfail)
|
||
.code {
|
||
let lhsCharacter = Character(test.lhs)
|
||
let rhsCharacter = Character(test.rhs)
|
||
checkCharacterComparison(
|
||
test.expectedUnicodeCollation, lhsCharacter, rhsCharacter,
|
||
test.loc.withCurrentLoc())
|
||
checkCharacterComparison(
|
||
test.expectedUnicodeCollation.flip(), rhsCharacter, lhsCharacter,
|
||
test.loc.withCurrentLoc())
|
||
}
|
||
}
|
||
}
|
||
|
||
func checkHasPrefixHasSuffix(
|
||
lhs: String, _ rhs: String, _ stackTrace: SourceLocStack
|
||
) {
|
||
#if _runtime(_ObjC)
|
||
if lhs == "" {
|
||
return
|
||
}
|
||
if rhs == "" {
|
||
expectFalse(lhs.hasPrefix(rhs), stackTrace: stackTrace)
|
||
expectFalse(lhs.hasSuffix(rhs), stackTrace: stackTrace)
|
||
return
|
||
}
|
||
|
||
// To determine the expected results, compare grapheme clusters,
|
||
// scalar-to-scalar, of the NFD form of the strings.
|
||
let lhsNFDGraphemeClusters =
|
||
lhs.decomposedStringWithCanonicalMapping.characters.map {
|
||
Array(String($0).unicodeScalars)
|
||
}
|
||
let rhsNFDGraphemeClusters =
|
||
rhs.decomposedStringWithCanonicalMapping.characters.map {
|
||
Array(String($0).unicodeScalars)
|
||
}
|
||
let expectHasPrefix = lhsNFDGraphemeClusters.starts(
|
||
with: rhsNFDGraphemeClusters, isEquivalent: (==))
|
||
let expectHasSuffix =
|
||
lhsNFDGraphemeClusters.lazy.reversed().starts(
|
||
with: rhsNFDGraphemeClusters.lazy.reversed(), isEquivalent: (==))
|
||
|
||
expectEqual(expectHasPrefix, lhs.hasPrefix(rhs), stackTrace: stackTrace)
|
||
expectEqual(
|
||
expectHasPrefix, (lhs + "abc").hasPrefix(rhs), stackTrace: stackTrace)
|
||
expectEqual(expectHasSuffix, lhs.hasSuffix(rhs), stackTrace: stackTrace)
|
||
expectEqual(
|
||
expectHasSuffix, ("abc" + lhs).hasSuffix(rhs), stackTrace: stackTrace)
|
||
#endif
|
||
}
|
||
|
||
StringTests.test("hasPrefix,hasSuffix")
|
||
.skip(.nativeRuntime(
|
||
"String.has{Prefix,Suffix} defined when _runtime(_ObjC)"))
|
||
.code {
|
||
for test in comparisonTests {
|
||
checkHasPrefixHasSuffix(test.lhs, test.rhs, test.loc.withCurrentLoc())
|
||
checkHasPrefixHasSuffix(test.rhs, test.lhs, test.loc.withCurrentLoc())
|
||
}
|
||
}
|
||
|
||
StringTests.test("Failures{hasPrefix,hasSuffix}-CF")
|
||
.xfail(.custom({ true }, reason: "rdar://problem/19034601"))
|
||
.skip(.nativeRuntime(
|
||
"String.has{Prefix,Suffix} defined when _runtime(_ObjC)"))
|
||
.code {
|
||
let test = ComparisonTest(.lt, "\u{0}", "\u{0}\u{0}")
|
||
checkHasPrefixHasSuffix(test.lhs, test.rhs, test.loc.withCurrentLoc())
|
||
}
|
||
|
||
StringTests.test("Failures{hasPrefix,hasSuffix}")
|
||
.xfail(.custom({ true }, reason: "blocked on rdar://problem/19036555"))
|
||
.skip(.nativeRuntime(
|
||
"String.has{Prefix,Suffix} defined when _runtime(_ObjC)"))
|
||
.code {
|
||
let tests =
|
||
[ComparisonTest(.lt, "\r\n", "t"), ComparisonTest(.gt, "\r\n", "\n")]
|
||
tests.forEach {
|
||
checkHasPrefixHasSuffix($0.lhs, $0.rhs, $0.loc.withCurrentLoc())
|
||
}
|
||
}
|
||
|
||
StringTests.test("SameTypeComparisons") {
|
||
// U+0323 COMBINING DOT BELOW
|
||
// U+0307 COMBINING DOT ABOVE
|
||
// U+1E63 LATIN SMALL LETTER S WITH DOT BELOW
|
||
let xs = "\u{1e69}"
|
||
expectTrue(xs == "s\u{323}\u{307}")
|
||
expectFalse(xs != "s\u{323}\u{307}")
|
||
expectTrue("s\u{323}\u{307}" == xs)
|
||
expectFalse("s\u{323}\u{307}" != xs)
|
||
expectTrue("\u{1e69}" == "s\u{323}\u{307}")
|
||
expectFalse("\u{1e69}" != "s\u{323}\u{307}")
|
||
expectTrue(xs == xs)
|
||
expectFalse(xs != xs)
|
||
}
|
||
|
||
StringTests.test("CompareStringsWithUnpairedSurrogates")
|
||
.xfail(
|
||
.custom({ true },
|
||
reason: "<rdar://problem/18029104> Strings referring to underlying " +
|
||
"storage with unpaired surrogates compare unequal"))
|
||
.code {
|
||
let donor = "abcdef"
|
||
let acceptor = "\u{1f601}\u{1f602}\u{1f603}"
|
||
|
||
expectEqual("\u{fffd}\u{1f602}\u{fffd}",
|
||
acceptor[donor.startIndex.advanced(by: 1)..<donor.startIndex.advanced(by: 5)])
|
||
}
|
||
|
||
var CStringTests = TestSuite("CStringTests")
|
||
|
||
func getNullCString() -> UnsafeMutablePointer<CChar> {
|
||
return nil
|
||
}
|
||
|
||
func getASCIICString() -> (UnsafeMutablePointer<CChar>, dealloc: () -> ()) {
|
||
let up = UnsafeMutablePointer<CChar>(allocatingCapacity: 100)
|
||
up[0] = 0x61
|
||
up[1] = 0x62
|
||
up[2] = 0
|
||
return (up, { up.deallocateCapacity(100) })
|
||
}
|
||
|
||
func getNonASCIICString() -> (UnsafeMutablePointer<CChar>, dealloc: () -> ()) {
|
||
let up = UnsafeMutablePointer<UInt8>(allocatingCapacity: 100)
|
||
up[0] = 0xd0
|
||
up[1] = 0xb0
|
||
up[2] = 0xd0
|
||
up[3] = 0xb1
|
||
up[4] = 0
|
||
return (UnsafeMutablePointer(up), { up.deallocateCapacity(100) })
|
||
}
|
||
|
||
func getIllFormedUTF8String1(
|
||
) -> (UnsafeMutablePointer<CChar>, dealloc: () -> ()) {
|
||
let up = UnsafeMutablePointer<UInt8>(allocatingCapacity: 100)
|
||
up[0] = 0x41
|
||
up[1] = 0xed
|
||
up[2] = 0xa0
|
||
up[3] = 0x80
|
||
up[4] = 0x41
|
||
up[5] = 0
|
||
return (UnsafeMutablePointer(up), { up.deallocateCapacity(100) })
|
||
}
|
||
|
||
func getIllFormedUTF8String2(
|
||
) -> (UnsafeMutablePointer<CChar>, dealloc: () -> ()) {
|
||
let up = UnsafeMutablePointer<UInt8>(allocatingCapacity: 100)
|
||
up[0] = 0x41
|
||
up[0] = 0x41
|
||
up[1] = 0xed
|
||
up[2] = 0xa0
|
||
up[3] = 0x81
|
||
up[4] = 0x41
|
||
up[5] = 0
|
||
return (UnsafeMutablePointer(up), { up.deallocateCapacity(100) })
|
||
}
|
||
|
||
func asCCharArray(a: [UInt8]) -> [CChar] {
|
||
return a.map { CChar(bitPattern: $0) }
|
||
}
|
||
|
||
CStringTests.test("String.init(validatingUTF8:)") {
|
||
do {
|
||
let (s, dealloc) = getASCIICString()
|
||
expectOptionalEqual("ab", String(validatingUTF8: s))
|
||
dealloc()
|
||
}
|
||
do {
|
||
let (s, dealloc) = getNonASCIICString()
|
||
expectOptionalEqual("аб", String(validatingUTF8: s))
|
||
dealloc()
|
||
}
|
||
do {
|
||
let (s, dealloc) = getIllFormedUTF8String1()
|
||
expectEmpty(String(validatingUTF8: s))
|
||
dealloc()
|
||
}
|
||
}
|
||
|
||
CStringTests.test("String(cString:)") {
|
||
do {
|
||
let (s, dealloc) = getASCIICString()
|
||
let result = String(cString: s)
|
||
expectEqual("ab", result)
|
||
dealloc()
|
||
}
|
||
do {
|
||
let (s, dealloc) = getNonASCIICString()
|
||
let result = String(cString: s)
|
||
expectEqual("аб", result)
|
||
dealloc()
|
||
}
|
||
do {
|
||
let (s, dealloc) = getIllFormedUTF8String1()
|
||
let result = String(cString: s)
|
||
expectEqual("\u{41}\u{fffd}\u{fffd}\u{fffd}\u{41}", result)
|
||
dealloc()
|
||
}
|
||
}
|
||
|
||
CStringTests.test("String.decodeCString") {
|
||
do {
|
||
let s = getNullCString()
|
||
let result = String.decodeCString(UnsafePointer(s), `as`: UTF8.self)
|
||
expectEmpty(result)
|
||
}
|
||
do { // repairing
|
||
let (s, dealloc) = getIllFormedUTF8String1()
|
||
if let (result, repairsMade) = String.decodeCString(
|
||
UnsafePointer(s), as: UTF8.self, repairingInvalidCodeUnits: true) {
|
||
expectOptionalEqual("\u{41}\u{fffd}\u{fffd}\u{fffd}\u{41}", result)
|
||
expectTrue(repairsMade)
|
||
} else {
|
||
expectUnreachable("Expected .some()")
|
||
}
|
||
dealloc()
|
||
}
|
||
do { // non repairing
|
||
let (s, dealloc) = getIllFormedUTF8String1()
|
||
let result = String.decodeCString(
|
||
UnsafePointer(s), as: UTF8.self, repairingInvalidCodeUnits: false)
|
||
expectEmpty(result)
|
||
dealloc()
|
||
}
|
||
}
|
||
|
||
runAllTests()
|
||
|