// RUN: %target-run-simple-swift // REQUIRES: executable_test // REQUIRES: objc_interop // FIXME: rdar://problem/31311598 // UNSUPPORTED: OS=ios // UNSUPPORTED: OS=tvos // UNSUPPORTED: OS=watchos // // Tests for the NSString APIs as exposed by String // import StdlibUnittest import Foundation import StdlibUnittestFoundationExtras // The most simple subclass of NSString that CoreFoundation does not know // about. class NonContiguousNSString : NSString { required init(coder aDecoder: NSCoder) { fatalError("don't call this initializer") } required init(itemProviderData data: Data, typeIdentifier: String) throws { fatalError("don't call this initializer") } override init() { _value = [] super.init() } init(_ value: [UInt16]) { _value = value super.init() } @objc(copyWithZone:) override func copy(with zone: NSZone?) -> Any { // Ensure that copying this string produces a class that CoreFoundation // does not know about. return self } @objc override var length: Int { return _value.count } @objc override func character(at index: Int) -> unichar { return _value[index] } var _value: [UInt16] } let temporaryFileContents = "Lorem ipsum dolor sit amet, consectetur adipisicing elit,\n" + "sed do eiusmod tempor incididunt ut labore et dolore magna\n" + "aliqua.\n" func createNSStringTemporaryFile() -> (existingPath: String, nonExistentPath: String) { let existingPath = createTemporaryFile("NSStringAPIs.", ".txt", temporaryFileContents) let nonExistentPath = existingPath + "-NoNeXiStEnT" return (existingPath, nonExistentPath) } var NSStringAPIs = TestSuite("NSStringAPIs") NSStringAPIs.test("Encodings") { let availableEncodings: [String.Encoding] = String.availableStringEncodings expectNotEqual(0, availableEncodings.count) let defaultCStringEncoding = String.defaultCStringEncoding expectTrue(availableEncodings.contains(defaultCStringEncoding)) expectNotEqual("", String.localizedName(of: .utf8)) } NSStringAPIs.test("NSStringEncoding") { // Make sure NSStringEncoding and its values are type-compatible. var enc: String.Encoding enc = .windowsCP1250 enc = .utf32LittleEndian enc = .utf32BigEndian enc = .ascii enc = .utf8 } NSStringAPIs.test("localizedStringWithFormat(_:...)") { var world: NSString = "world" expectEqual("Hello, world!%42", String.localizedStringWithFormat( "Hello, %@!%%%ld", world, 42)) withOverriddenLocaleCurrentLocale("en_US") { expectEqual("0.5", String.localizedStringWithFormat("%g", 0.5)) } withOverriddenLocaleCurrentLocale("uk") { expectEqual("0,5", String.localizedStringWithFormat("%g", 0.5)) } } NSStringAPIs.test("init(contentsOfFile:encoding:error:)") { let (existingPath, nonExistentPath) = createNSStringTemporaryFile() do { let content = try String( contentsOfFile: existingPath, encoding: .ascii) expectEqual( "Lorem ipsum dolor sit amet, consectetur adipisicing elit,", content._lines[0]) } catch { expectUnreachableCatch(error) } do { let content = try String( contentsOfFile: nonExistentPath, encoding: .ascii) expectUnreachable() } catch { } } NSStringAPIs.test("init(contentsOfFile:usedEncoding:error:)") { let (existingPath, nonExistentPath) = createNSStringTemporaryFile() do { var usedEncoding: String.Encoding = String.Encoding(rawValue: 0) let content = try String( contentsOfFile: existingPath, usedEncoding: &usedEncoding) expectNotEqual(0, usedEncoding.rawValue) expectEqual( "Lorem ipsum dolor sit amet, consectetur adipisicing elit,", content._lines[0]) } catch { expectUnreachableCatch(error) } var usedEncoding: String.Encoding = String.Encoding(rawValue: 0) do { _ = try String(contentsOfFile: nonExistentPath) expectUnreachable() } catch { expectEqual(0, usedEncoding.rawValue) } } NSStringAPIs.test("init(contentsOf:encoding:error:)") { let (existingPath, nonExistentPath) = createNSStringTemporaryFile() let existingURL = URL(string: "file://" + existingPath)! let nonExistentURL = URL(string: "file://" + nonExistentPath)! do { let content = try String( contentsOf: existingURL, encoding: .ascii) expectEqual( "Lorem ipsum dolor sit amet, consectetur adipisicing elit,", content._lines[0]) } catch { expectUnreachableCatch(error) } do { _ = try String(contentsOf: nonExistentURL, encoding: .ascii) expectUnreachable() } catch { } } NSStringAPIs.test("init(contentsOf:usedEncoding:error:)") { let (existingPath, nonExistentPath) = createNSStringTemporaryFile() let existingURL = URL(string: "file://" + existingPath)! let nonExistentURL = URL(string: "file://" + nonExistentPath)! do { var usedEncoding: String.Encoding = String.Encoding(rawValue: 0) let content = try String( contentsOf: existingURL, usedEncoding: &usedEncoding) expectNotEqual(0, usedEncoding.rawValue) expectEqual( "Lorem ipsum dolor sit amet, consectetur adipisicing elit,", content._lines[0]) } catch { expectUnreachableCatch(error) } var usedEncoding: String.Encoding = String.Encoding(rawValue: 0) do { _ = try String(contentsOf: nonExistentURL, usedEncoding: &usedEncoding) expectUnreachable() } catch { expectEqual(0, usedEncoding.rawValue) } } NSStringAPIs.test("init(cString_:encoding:)") { expectOptionalEqual("foo, a basmati bar!", String(cString: "foo, a basmati bar!", encoding: String.defaultCStringEncoding)) } NSStringAPIs.test("init(utf8String:)") { var s = "foo あいう" var up = UnsafeMutablePointer.allocate(capacity: 100) var i = 0 for b in s.utf8 { up[i] = b i += 1 } up[i] = 0 let cstr = UnsafeMutableRawPointer(up) .bindMemory(to: CChar.self, capacity: 100) expectOptionalEqual(s, String(utf8String: cstr)) up.deallocate() } NSStringAPIs.test("canBeConvertedToEncoding(_:)") { expectTrue("foo".canBeConverted(to: .ascii)) expectFalse("あいう".canBeConverted(to: .ascii)) } NSStringAPIs.test("capitalized") { expectEqual("Foo Foo Foo Foo", "foo Foo fOO FOO".capitalized) expectEqual("Жжж", "жжж".capitalized) } NSStringAPIs.test("localizedCapitalized") { if #available(OSX 10.11, iOS 9.0, *) { withOverriddenLocaleCurrentLocale("en") { () -> Void in expectEqual( "Foo Foo Foo Foo", "foo Foo fOO FOO".localizedCapitalized) expectEqual("Жжж", "жжж".localizedCapitalized) return () } // // Special casing. // // U+0069 LATIN SMALL LETTER I // to upper case: // U+0049 LATIN CAPITAL LETTER I withOverriddenLocaleCurrentLocale("en") { expectEqual("Iii Iii", "iii III".localizedCapitalized) } // U+0069 LATIN SMALL LETTER I // to upper case in Turkish locale: // U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE withOverriddenLocaleCurrentLocale("tr") { expectEqual("\u{0130}ii Iıı", "iii III".localizedCapitalized) } } } /// Checks that executing the operation in the locale with the given /// `localeID` (or if `localeID` is `nil`, the current locale) gives /// the expected result, and that executing the operation with a nil /// locale gives the same result as explicitly passing the system /// locale. /// /// - Parameter expected: the expected result when the operation is /// executed in the given localeID func expectLocalizedEquality( _ expected: String, _ op: (_: Locale?) -> String, _ localeID: String? = nil, _ message: @autoclosure () -> String = "", showFrame: Bool = true, stackTrace: SourceLocStack = SourceLocStack(), file: String = #file, line: UInt = #line ) { let trace = stackTrace.pushIf(showFrame, file: file, line: line) let locale = localeID.map { Locale(identifier: $0) } ?? Locale.current expectEqual( expected, op(locale), message(), stackTrace: trace) } NSStringAPIs.test("capitalizedString(with:)") { expectLocalizedEquality( "Foo Foo Foo Foo", { loc in "foo Foo fOO FOO".capitalized(with: loc) }) expectLocalizedEquality("Жжж", { loc in "жжж".capitalized(with: loc) }) expectEqual( "Foo Foo Foo Foo", "foo Foo fOO FOO".capitalized(with: nil)) expectEqual("Жжж", "жжж".capitalized(with: nil)) // // Special casing. // // U+0069 LATIN SMALL LETTER I // to upper case: // U+0049 LATIN CAPITAL LETTER I expectLocalizedEquality( "Iii Iii", { loc in "iii III".capitalized(with: loc) }, "en") // U+0069 LATIN SMALL LETTER I // to upper case in Turkish locale: // U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE expectLocalizedEquality( "İii Iıı", { loc in "iii III".capitalized(with: loc) }, "tr") } NSStringAPIs.test("caseInsensitiveCompare(_:)") { expectEqual(ComparisonResult.orderedSame, "abCD".caseInsensitiveCompare("AbCd")) expectEqual(ComparisonResult.orderedAscending, "abCD".caseInsensitiveCompare("AbCdE")) expectEqual(ComparisonResult.orderedSame, "абвг".caseInsensitiveCompare("АбВг")) expectEqual(ComparisonResult.orderedAscending, "абВГ".caseInsensitiveCompare("АбВгД")) } NSStringAPIs.test("commonPrefix(with:options:)") { expectEqual("ab", "abcd".commonPrefix(with: "abdc", options: [])) expectEqual("abC", "abCd".commonPrefix(with: "abce", options: .caseInsensitive)) expectEqual("аб", "абвг".commonPrefix(with: "абгв", options: [])) expectEqual("абВ", "абВг".commonPrefix(with: "абвд", options: .caseInsensitive)) } NSStringAPIs.test("compare(_:options:range:locale:)") { expectEqual(ComparisonResult.orderedSame, "abc".compare("abc")) expectEqual(ComparisonResult.orderedAscending, "абв".compare("где")) expectEqual(ComparisonResult.orderedSame, "abc".compare("abC", options: .caseInsensitive)) expectEqual(ComparisonResult.orderedSame, "абв".compare("абВ", options: .caseInsensitive)) do { let s = "abcd" let r = s.index(after: s.startIndex).., sentenceRange: Range, stop: inout Bool) in tags.append(tag) tokens.append(String(s[tokenRange])) sentences.append(String(s[sentenceRange])) if tags.count == 3 { stop = true } } expectEqual( [NSLinguisticTagWord, NSLinguisticTagWhitespace, NSLinguisticTagWord], tags) expectEqual(["Глокая", " ", "куздра"], tokens) let sentence = s[startIndex.., enclosingRange: Range, stop: inout Bool) in substrings.append(substring!) expectEqual(substring, String(s[substringRange])) expectEqual(substring, String(s[enclosingRange])) } expectEqual(["\u{304b}\u{3099}", "お", "☺️", "😀"], substrings) } do { var substrings: [String] = [] s.enumerateSubstrings(in: startIndex.., enclosingRange: Range, stop: inout Bool) in expectNil(substring_) let substring = s[substringRange] substrings.append(substring) expectEqual(substring, s[enclosingRange]) } expectEqual(["\u{304b}\u{3099}", "お", "☺️", "😀"], substrings) } } NSStringAPIs.test("fastestEncoding") { let availableEncodings: [String.Encoding] = String.availableStringEncodings expectTrue(availableEncodings.contains("abc".fastestEncoding)) } NSStringAPIs.test("getBytes(_:maxLength:usedLength:encoding:options:range:remaining:)") { let s = "abc абв def где gh жз zzz" let startIndex = s.index(s.startIndex, offsetBy: 8) let endIndex = s.index(s.startIndex, offsetBy: 22) do { // 'maxLength' is limiting. let bufferLength = 100 var expectedStr: [UInt8] = Array("def где ".utf8) while (expectedStr.count != bufferLength) { expectedStr.append(0xff) } var buffer = [UInt8](repeating: 0xff, count: bufferLength) var usedLength = 0 var remainingRange = startIndex..] = [] var tags = s.linguisticTags(in: startIndex.. NFKD normalization as implemented by 'precomposedStringWithCompatibilityMapping:' is not idempotent expectEqual("\u{30c0}クテン", "\u{ff80}\u{ff9e}クテン".precomposedStringWithCompatibilityMapping) */ expectEqual("ffi", "\u{fb03}".precomposedStringWithCompatibilityMapping) } NSStringAPIs.test("propertyList()") { expectEqual(["foo", "bar"], "(\"foo\", \"bar\")".propertyList() as! [String]) } NSStringAPIs.test("propertyListFromStringsFileFormat()") { expectEqual(["foo": "bar", "baz": "baz"], "/* comment */\n\"foo\" = \"bar\";\n\"baz\";" .propertyListFromStringsFileFormat() as Dictionary) } NSStringAPIs.test("rangeOfCharacterFrom(_:options:range:)") { do { let charset = CharacterSet(charactersIn: "абв") do { let s = "Глокая куздра" let r = s.rangeOfCharacter(from: charset)! expectEqual(s.index(s.startIndex, offsetBy: 4), r.lowerBound) expectEqual(s.index(s.startIndex, offsetBy: 5), r.upperBound) } do { expectNil("клмн".rangeOfCharacter(from: charset)) } do { let s = "абвклмнабвклмн" let r = s.rangeOfCharacter(from: charset, options: .backwards)! expectEqual(s.index(s.startIndex, offsetBy: 9), r.lowerBound) expectEqual(s.index(s.startIndex, offsetBy: 10), r.upperBound) } do { let s = "абвклмнабв" let r = s.rangeOfCharacter(from: charset, range: s.index(s.startIndex, offsetBy: 3)..( _ string: S, _ maybeRange: Range? ) -> Range? where S.Index == String.Index, S.IndexDistance == Int { guard let range = maybeRange else { return nil } return string.distance(from: string.startIndex, to: range.lowerBound) ..< string.distance(from: string.startIndex, to: range.upperBound) } NSStringAPIs.test("range(of:options:range:locale:)") { do { let s = "" expectNil(s.range(of: "")) expectNil(s.range(of: "abc")) } do { let s = "abc" expectNil(s.range(of: "")) expectNil(s.range(of: "def")) expectOptionalEqual(0..<3, toIntRange(s, s.range(of: "abc"))) } do { let s = "さ\u{3099}し\u{3099}す\u{3099}せ\u{3099}そ\u{3099}" expectOptionalEqual(2..<3, toIntRange(s, s.range(of: "す\u{3099}"))) expectOptionalEqual(2..<3, toIntRange(s, s.range(of: "\u{305a}"))) expectNil(s.range(of: "\u{3099}す")) expectNil(s.range(of: "す")) // Note: here `rangeOf` API produces indexes that don't point between // grapheme cluster boundaries -- these cannot be created with public // String interface. // // FIXME: why does this search succeed and the above queries fail? There is // no apparent pattern. expectEqual("\u{3099}", s[s.range(of: "\u{3099}")!]) } do { let s = "а\u{0301}б\u{0301}в\u{0301}г\u{0301}" expectOptionalEqual(0..<1, toIntRange(s, s.range(of: "а\u{0301}"))) expectOptionalEqual(1..<2, toIntRange(s, s.range(of: "б\u{0301}"))) expectNil(s.range(of: "б")) expectNil(s.range(of: "\u{0301}б")) // FIXME: Again, indexes that don't correspond to grapheme // cluster boundaries. expectEqual("\u{0301}", s[s.range(of: "\u{0301}")!]) } } NSStringAPIs.test("contains(_:)") { withOverriddenLocaleCurrentLocale("en") { () -> Void in expectFalse("".contains("")) expectFalse("".contains("a")) expectFalse("a".contains("")) expectFalse("a".contains("b")) expectTrue("a".contains("a")) expectFalse("a".contains("A")) expectFalse("A".contains("a")) expectFalse("a".contains("a\u{0301}")) expectTrue("a\u{0301}".contains("a\u{0301}")) expectFalse("a\u{0301}".contains("a")) expectTrue("a\u{0301}".contains("\u{0301}")) expectFalse("a".contains("\u{0301}")) expectFalse("i".contains("I")) expectFalse("I".contains("i")) expectFalse("\u{0130}".contains("i")) expectFalse("i".contains("\u{0130}")) return () } withOverriddenLocaleCurrentLocale("tr") { expectFalse("\u{0130}".contains("ı")) } } NSStringAPIs.test("localizedCaseInsensitiveContains(_:)") { withOverriddenLocaleCurrentLocale("en") { () -> Void in expectFalse("".localizedCaseInsensitiveContains("")) expectFalse("".localizedCaseInsensitiveContains("a")) expectFalse("a".localizedCaseInsensitiveContains("")) expectFalse("a".localizedCaseInsensitiveContains("b")) expectTrue("a".localizedCaseInsensitiveContains("a")) expectTrue("a".localizedCaseInsensitiveContains("A")) expectTrue("A".localizedCaseInsensitiveContains("a")) expectFalse("a".localizedCaseInsensitiveContains("a\u{0301}")) expectTrue("a\u{0301}".localizedCaseInsensitiveContains("a\u{0301}")) expectFalse("a\u{0301}".localizedCaseInsensitiveContains("a")) expectTrue("a\u{0301}".localizedCaseInsensitiveContains("\u{0301}")) expectFalse("a".localizedCaseInsensitiveContains("\u{0301}")) expectTrue("i".localizedCaseInsensitiveContains("I")) expectTrue("I".localizedCaseInsensitiveContains("i")) expectFalse("\u{0130}".localizedCaseInsensitiveContains("i")) expectFalse("i".localizedCaseInsensitiveContains("\u{0130}")) return () } withOverriddenLocaleCurrentLocale("tr") { expectFalse("\u{0130}".localizedCaseInsensitiveContains("ı")) } } NSStringAPIs.test("localizedStandardContains(_:)") { if #available(OSX 10.11, iOS 9.0, *) { withOverriddenLocaleCurrentLocale("en") { () -> Void in expectFalse("".localizedStandardContains("")) expectFalse("".localizedStandardContains("a")) expectFalse("a".localizedStandardContains("")) expectFalse("a".localizedStandardContains("b")) expectTrue("a".localizedStandardContains("a")) expectTrue("a".localizedStandardContains("A")) expectTrue("A".localizedStandardContains("a")) expectTrue("a".localizedStandardContains("a\u{0301}")) expectTrue("a\u{0301}".localizedStandardContains("a\u{0301}")) expectTrue("a\u{0301}".localizedStandardContains("a")) expectTrue("a\u{0301}".localizedStandardContains("\u{0301}")) expectFalse("a".localizedStandardContains("\u{0301}")) expectTrue("i".localizedStandardContains("I")) expectTrue("I".localizedStandardContains("i")) expectTrue("\u{0130}".localizedStandardContains("i")) expectTrue("i".localizedStandardContains("\u{0130}")) return () } withOverriddenLocaleCurrentLocale("tr") { expectTrue("\u{0130}".localizedStandardContains("ı")) } } } NSStringAPIs.test("localizedStandardRange(of:)") { if #available(OSX 10.11, iOS 9.0, *) { func rangeOf(_ string: String, _ substring: String) -> Range? { return toIntRange( string, string.localizedStandardRange(of: substring)) } withOverriddenLocaleCurrentLocale("en") { () -> Void in expectNil(rangeOf("", "")) expectNil(rangeOf("", "a")) expectNil(rangeOf("a", "")) expectNil(rangeOf("a", "b")) expectEqual(0..<1, rangeOf("a", "a")) expectEqual(0..<1, rangeOf("a", "A")) expectEqual(0..<1, rangeOf("A", "a")) expectEqual(0..<1, rangeOf("a", "a\u{0301}")) expectEqual(0..<1, rangeOf("a\u{0301}", "a\u{0301}")) expectEqual(0..<1, rangeOf("a\u{0301}", "a")) do { // FIXME: Indices that don't correspond to grapheme cluster boundaries. let s = "a\u{0301}" expectEqual( "\u{0301}", s[s.localizedStandardRange(of: "\u{0301}")!]) } expectNil(rangeOf("a", "\u{0301}")) expectEqual(0..<1, rangeOf("i", "I")) expectEqual(0..<1, rangeOf("I", "i")) expectEqual(0..<1, rangeOf("\u{0130}", "i")) expectEqual(0..<1, rangeOf("i", "\u{0130}")) return () } withOverriddenLocaleCurrentLocale("tr") { expectEqual(0..<1, rangeOf("\u{0130}", "ı")) } } } NSStringAPIs.test("smallestEncoding") { let availableEncodings: [String.Encoding] = String.availableStringEncodings expectTrue(availableEncodings.contains("abc".smallestEncoding)) } func getHomeDir() -> String { #if os(OSX) return String(cString: getpwuid(getuid()).pointee.pw_dir) #elseif os(iOS) || os(tvOS) || os(watchOS) // getpwuid() returns null in sandboxed apps under iOS simulator. return NSHomeDirectory() #else preconditionFailed("implement") #endif } NSStringAPIs.test("addingPercentEncoding(withAllowedCharacters:)") { expectOptionalEqual( "abcd1234", "abcd1234".addingPercentEncoding(withAllowedCharacters: .alphanumerics)) expectOptionalEqual( "abcd%20%D0%B0%D0%B1%D0%B2%D0%B3", "abcd абвг".addingPercentEncoding(withAllowedCharacters: .alphanumerics)) } NSStringAPIs.test("appendingFormat(_:_:...)") { expectEqual("", "".appendingFormat("")) expectEqual("a", "a".appendingFormat("")) expectEqual( "abc абв \u{0001F60A}", "abc абв \u{0001F60A}".appendingFormat("")) let formatArg: NSString = "привет мир \u{0001F60A}" expectEqual( "abc абв \u{0001F60A}def привет мир \u{0001F60A} 42", "abc абв \u{0001F60A}" .appendingFormat("def %@ %ld", formatArg, 42)) } NSStringAPIs.test("appending(_:)") { expectEqual("", "".appending("")) expectEqual("a", "a".appending("")) expectEqual("a", "".appending("a")) expectEqual("さ\u{3099}", "さ".appending("\u{3099}")) } NSStringAPIs.test("folding(options:locale:)") { func fwo( _ s: String, _ options: String.CompareOptions ) -> (Locale?) -> String { return { loc in s.folding(options: options, locale: loc) } } expectLocalizedEquality("abcd", fwo("abCD", .caseInsensitive), "en") // U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE // to lower case: // U+0069 LATIN SMALL LETTER I // U+0307 COMBINING DOT ABOVE expectLocalizedEquality( "\u{0069}\u{0307}", fwo("\u{0130}", .caseInsensitive), "en") // U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE // to lower case in Turkish locale: // U+0069 LATIN SMALL LETTER I expectLocalizedEquality( "\u{0069}", fwo("\u{0130}", .caseInsensitive), "tr") expectLocalizedEquality( "example123", fwo("example123", .widthInsensitive), "en") } NSStringAPIs.test("padding(toLength:withPad:startingAtIndex:)") { expectEqual( "abc абв \u{0001F60A}", "abc абв \u{0001F60A}".padding( toLength: 10, withPad: "XYZ", startingAt: 0)) expectEqual( "abc абв \u{0001F60A}XYZXY", "abc абв \u{0001F60A}".padding( toLength: 15, withPad: "XYZ", startingAt: 0)) expectEqual( "abc абв \u{0001F60A}YZXYZ", "abc абв \u{0001F60A}".padding( toLength: 15, withPad: "XYZ", startingAt: 1)) } NSStringAPIs.test("replacingCharacters(in:with:)") { do { let empty = "" expectEqual("", empty.replacingCharacters( in: empty.startIndex.. 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.index(donor.startIndex, offsetBy: 1)..