// RUN: %target-run-stdlib-swift(-enable-experimental-feature LifetimeDependence) %S/Inputs/ // REQUIRES: swift_feature_LifetimeDependence // REQUIRES: executable_test import Swift import StdlibUnittest var suite = TestSuite("UTF8SpanIterator") defer { runAllTests() } @available(SwiftStdlib 6.2, *) extension Array { func withSpan(_ f: (Span) throws -> R) rethrows -> R { try self.withUnsafeBufferPointer { try f(Span(_unsafeElements: $0)) } } } @available(SwiftStdlib 6.2, *) extension UTF8Span { func withSpan(_ f: (Span) throws -> R) rethrows -> R { try self._withUnsafeBufferPointer { try f(Span(_unsafeElements: $0)) } } } // @available(SwiftStdlib 6.2, *) struct ContentEquivalenceTestCase { var str: String var loc: SourceLocStack } @available(SwiftStdlib 6.2, *) extension ContentEquivalenceTestCase { func expectStart( _ scalars: inout UTF8Span.UnicodeScalarIterator ) { let firstScalar = str.unicodeScalars.first expectEqual(0, scalars.currentCodeUnitOffset, stackTrace: loc) expectNil(scalars.previous(), stackTrace: loc) expectEqual(firstScalar, scalars.next(), stackTrace: loc) expectEqual(firstScalar, scalars.previous(), stackTrace: loc) expectNil(scalars.previous(), stackTrace: loc) } func expectEnd( _ scalars: inout UTF8Span.UnicodeScalarIterator ) { let lastScalar = str.unicodeScalars.last expectEqual(scalars.currentCodeUnitOffset, scalars.codeUnits.count, stackTrace: loc) expectNil(scalars.next(), stackTrace: loc) expectEqual(lastScalar, scalars.previous(), stackTrace: loc) expectEqual(lastScalar, scalars.next(), stackTrace: loc) expectNil(scalars.next(), stackTrace: loc) } func expectStart( _ chars: inout UTF8Span.CharacterIterator ) { let firstChar = str.first expectEqual(0, chars.currentCodeUnitOffset, stackTrace: loc) expectNil(chars.previous(), stackTrace: loc) expectEqual(firstChar, chars.next(), stackTrace: loc) expectEqual(firstChar, chars.previous(), stackTrace: loc) expectNil(chars.previous(), stackTrace: loc) } func expectEnd( _ chars: inout UTF8Span.CharacterIterator ) { let lastChar = str.last expectEqual(chars.currentCodeUnitOffset, chars.codeUnits.count, stackTrace: loc) expectNil(chars.next(), stackTrace: loc) expectEqual(lastChar, chars.previous(), stackTrace: loc) expectEqual(lastChar, chars.next(), stackTrace: loc) expectNil(chars.next(), stackTrace: loc) } func withUTF8Span(_ f: (UTF8Span) throws -> R) rethrows -> R { try Array(str.utf8).withSpan { span in try f(try! UTF8Span(validating: span)) } } } @available(SwiftStdlib 6.2, *) extension ContentEquivalenceTestCase { func testBytes() { let otherBytes = Array((str+"abc").utf8) withUTF8Span { utf8Span in utf8Span._withUnsafeBufferPointer { expectEqualSequence(str.utf8, $0, stackTrace: loc) } } // NOTE: There's a slight jarring due to not having the same // iterators for code units } func testScalars() { withUTF8Span { utf8Span in // Test forwards var utf8SpanIter = utf8Span.makeUnicodeScalarIterator() var stringIter = str.unicodeScalars.makeIterator() while let scalar = utf8SpanIter.next() { expectEqual(scalar, stringIter.next(), stackTrace: loc) } expectNil(stringIter.next(), stackTrace: loc) expectEnd(&utf8SpanIter) // Test backwards var stringRevIter = str.unicodeScalars.reversed().makeIterator() while let scalar = utf8SpanIter.previous() { expectEqual(scalar, stringRevIter.next(), stackTrace: loc) } expectNil(stringRevIter.next(), stackTrace: loc) expectStart(&utf8SpanIter) let numElements = str.unicodeScalars.count let lastElement = str.unicodeScalars.last let firstElement = str.unicodeScalars.first expectEqual(numElements, utf8SpanIter.skipForward(by: Int.max)) expectEnd(&utf8SpanIter) expectEqual(numElements, utf8SpanIter.skipBack(by: Int.max)) expectStart(&utf8SpanIter) expectEqual(numElements, utf8SpanIter.skipForward(by: numElements)) expectEnd(&utf8SpanIter) expectEqual(numElements, utf8SpanIter.skipBack(by: numElements)) expectStart(&utf8SpanIter) if numElements > 0 { expectStart(&utf8SpanIter) expectEqual(numElements-1, utf8SpanIter.skipForward(by: numElements-1)) expectEqual(lastElement, utf8SpanIter.next()) expectEnd(&utf8SpanIter) expectEqual(numElements-1, utf8SpanIter.skipBack(by: numElements-1)) expectEqual(firstElement, utf8SpanIter.previous()) expectStart(&utf8SpanIter) } // TODO: test reset variants // TODO: test prefix/suffix } } func testCharacters() { withUTF8Span { utf8Span in // Test forwards var utf8SpanIter = utf8Span.makeCharacterIterator() var stringIter = str.makeIterator() while let char = utf8SpanIter.next() { expectEqual(char, stringIter.next(), stackTrace: loc) } expectNil(stringIter.next(), stackTrace: loc) expectEnd(&utf8SpanIter) // Test backwards var stringRevIter = str.reversed().makeIterator() while let char = utf8SpanIter.previous() { expectEqual(char, stringRevIter.next(), stackTrace: loc) } expectNil(stringRevIter.next(), stackTrace: loc) expectStart(&utf8SpanIter) let numElements = str.count let lastElement = str.last let firstElement = str.first expectEqual(numElements, utf8SpanIter.skipForward(by: Int.max)) expectEnd(&utf8SpanIter) expectEqual(numElements, utf8SpanIter.skipBack(by: Int.max)) expectStart(&utf8SpanIter) expectEqual(numElements, utf8SpanIter.skipForward(by: numElements)) expectEnd(&utf8SpanIter) expectEqual(numElements, utf8SpanIter.skipBack(by: numElements)) expectStart(&utf8SpanIter) if numElements > 0 { expectStart(&utf8SpanIter) expectEqual(numElements-1, utf8SpanIter.skipForward(by: numElements-1)) expectEqual(lastElement, utf8SpanIter.next()) expectEnd(&utf8SpanIter) expectEqual(numElements-1, utf8SpanIter.skipBack(by: numElements-1)) expectEqual(firstElement, utf8SpanIter.previous()) expectStart(&utf8SpanIter) } // TODO: test reset variants // TODO: test prefix/suffix } } func run() { testBytes() testScalars() testCharacters() // TODO: test grapheme break iterator } } if #available(SwiftStdlib 6.2, *) { suite.test("UTF8Span/iterators") { func test( _ s: String, file: String = #file, line: UInt = #line ) { // print("testing: \(s)") let t = ContentEquivalenceTestCase( str: s, loc: .init(SourceLoc(file, line))) t.run() } test("") test("a") test("รก") test("a\u{301}") test("๐ŸงŸโ€โ™€๏ธ") test("abc") test("abcde\u{301}") test("abรฉร๐“€€") test("012345678901234567890") test("abรฉร012345678901234567890๐“€€") test("๐Ÿ˜€๐Ÿ˜ƒ๐Ÿคข๐Ÿคฎ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽค๐Ÿง›๐Ÿปโ€โ™‚๏ธ๐Ÿง›๐Ÿปโ€โ™‚๏ธ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ") test("defghijklmnopqrstuvwxyz") test("ab๐ŸงŸโ€โ™€๏ธde\u{301}bytรฉs") test("ab๐ŸงŸโ€โ™€๏ธde\u{301}๐ŸงŸโ€โ™€๏ธ") test("ab๐ŸงŸโ€โ™€๏ธde๐ŸงŸโ€โ™€๏ธ\u{301}") } } // @available(SwiftStdlib 6.2, *) // extension UTF8Span { // func splitOffASCIIPrefix() -> (UTF8Span, UTF8Span) { // if isKnownASCII { // return (self, .init()) // } // var splitPoint = 0 // while splitPoint < codeUnits.count && codeUnits[unchecked: split] < 0x80 { // splitPoint += 1 // } // } // } if #available(SwiftStdlib 6.2, *) { suite.test("UTF8Span/whatever") { // var badURLBytes: [UInt8] = [] // badURLBytes.append(contentsOf: "http://servername/scripts/..".utf8) // // Invalid overlong encoding of "/" // badURLBytes.append(contentsOf: [0xC0, 0xAF]) // badURLBytes.append(contentsOf: "../winnt/system32/cmd.exe".utf8) // // try! UTF8Span(validating: badURLBytes.span) // badURLBytes.withSpan { // try! UTF8Span(validating: $0) // } } }