Merge pull request #1045 from norio-nomura/change-rsync-option-on-Install
diff --git a/Foundation/Data.swift b/Foundation/Data.swift
index ccfd16f..b4d22c1 100644
--- a/Foundation/Data.swift
+++ b/Foundation/Data.swift
@@ -11,7 +11,7 @@
//===----------------------------------------------------------------------===//
#if DEPLOYMENT_RUNTIME_SWIFT
-
+
#if os(OSX) || os(iOS)
import Darwin
#elseif os(Linux)
@@ -19,24 +19,24 @@
#endif
import CoreFoundation
-
+
internal func __NSDataInvokeDeallocatorUnmap(_ mem: UnsafeMutableRawPointer, _ length: Int) {
munmap(mem, length)
}
-
+
internal func __NSDataInvokeDeallocatorFree(_ mem: UnsafeMutableRawPointer, _ length: Int) {
free(mem)
}
-
+
#else
-
+
@_exported import Foundation // Clang module
import _SwiftFoundationOverlayShims
import _SwiftCoreFoundationOverlayShims
-
+
@_silgen_name("__NSDataWriteToURL")
internal func __NSDataWriteToURL(_ data: NSData, _ url: NSURL, _ options: UInt, _ error: NSErrorPointer) -> Bool
-
+
#endif
public final class _DataStorage {
@@ -189,7 +189,7 @@
d.enumerateBytes { (ptr, range, stop) in
var stopv = false
let bytePtr = ptr.bindMemory(to: UInt8.self, capacity: range.length)
- block(UnsafeBufferPointer(start: bytePtr, count: range.length), range.length, &stopv)
+ block(UnsafeBufferPointer(start: bytePtr, count: range.length), range.location, &stopv)
if stopv {
stop.pointee = true
}
@@ -198,7 +198,7 @@
d.enumerateBytes { (ptr, range, stop) in
var stopv = false
let bytePtr = ptr.bindMemory(to: UInt8.self, capacity: range.length)
- block(UnsafeBufferPointer(start: bytePtr, count: range.length), range.length, &stopv)
+ block(UnsafeBufferPointer(start: bytePtr, count: range.length), range.location, &stopv)
if stopv {
stop.pointee = true
}
@@ -341,7 +341,7 @@
case .customReference(let d):
let data = d.mutableCopy() as! NSMutableData
data.append(bytes, length: length)
- _backing = .customReference(data)
+ _backing = .customMutableReference(data)
case .customMutableReference(let d):
d.append(bytes, length: length)
}
@@ -421,7 +421,7 @@
if _length < range.location + range.length {
let newLength = range.location + range.length
if _capacity < newLength {
-
+
_grow(newLength, false)
}
_length = newLength
@@ -471,7 +471,7 @@
memset(mutableBytes! + start, 0, replacementLength)
}
}
-
+
if resultingLength < currentLength {
setLength(resultingLength)
}
@@ -703,50 +703,64 @@
}
public func withInteriorPointerReference<T>(_ range: Range<Int>, _ work: (NSData) throws -> T) rethrows -> T {
+ if range.count == 0 {
+ return try work(NSData()) // zero length data can be optimized as a singleton
+ }
+
switch _backing {
case .swift:
- let d = _bytes == nil ? NSData() : NSData(bytesNoCopy: _bytes!.advanced(by: range.lowerBound), length: range.count, freeWhenDone: false)
- return try work(d)
+ return try work(NSData(bytesNoCopy: _bytes!.advanced(by: range.lowerBound), length: range.count, freeWhenDone: false))
case .immutable(let d):
- if range.lowerBound == 0 && range.upperBound == _length {
- return try work(d)
- } else {
- return try work(d.subdata(with: NSRange(location: range.lowerBound, length: range.count))._bridgeToObjectiveC())
+ guard range.lowerBound == 0 && range.upperBound == _length else {
+ return try work(NSData(bytesNoCopy: _bytes!.advanced(by: range.lowerBound), length: range.count, freeWhenDone: false))
}
+ return try work(d)
case .mutable(let d):
- if range.lowerBound == 0 && range.upperBound == _length {
- return try work(d)
- } else {
- return try work(d.subdata(with: NSRange(location: range.lowerBound, length: range.count))._bridgeToObjectiveC())
+ guard range.lowerBound == 0 && range.upperBound == _length else {
+ return try work(NSData(bytesNoCopy: _bytes!.advanced(by: range.lowerBound), length: range.count, freeWhenDone: false))
}
+ return try work(d)
case .customReference(let d):
- if range.lowerBound == 0 && range.upperBound == _length {
- return try work(d)
- } else {
- return try work(d.subdata(with: NSRange(location: range.lowerBound, length: range.count))._bridgeToObjectiveC())
+ guard range.lowerBound == 0 && range.upperBound == _length else {
+
+ return try work(NSData(bytesNoCopy: UnsafeMutableRawPointer(mutating: d.bytes.advanced(by: range.lowerBound)), length: range.count, freeWhenDone: false))
}
+ return try work(d)
case .customMutableReference(let d):
- if range.lowerBound == 0 && range.upperBound == _length {
- return try work(d)
- } else {
- return try work(d.subdata(with: NSRange(location: range.lowerBound, length: range.count))._bridgeToObjectiveC())
+ guard range.lowerBound == 0 && range.upperBound == _length else {
+ return try work(NSData(bytesNoCopy: UnsafeMutableRawPointer(mutating: d.bytes.advanced(by: range.lowerBound)), length: range.count, freeWhenDone: false))
}
+ return try work(d)
}
}
- public func bridgedReference() -> NSData {
+ public func bridgedReference(_ range: Range<Int>) -> NSData {
+ if range.count == 0 {
+ return NSData() // zero length data can be optimized as a singleton
+ }
+
switch _backing {
case .swift:
- return _NSSwiftData(backing: self)
+ return _NSSwiftData(backing: self, range: range)
case .immutable(let d):
+ guard range.lowerBound == 0 && range.upperBound == _length else {
+ return _NSSwiftData(backing: self, range: range)
+ }
return d
case .mutable(let d):
+ guard range.lowerBound == 0 && range.upperBound == _length else {
+ return _NSSwiftData(backing: self, range: range)
+ }
return d
case .customReference(let d):
+ guard range.lowerBound == 0 && range.upperBound == d.length else {
+ return _NSSwiftData(backing: self, range: range)
+ }
return d
case .customMutableReference(let d):
- // Because this is returning an object that may be mutated in the future it needs to create a copy to prevent
- // any further mutations out from under the receiver
+ guard range.lowerBound == 0 && range.upperBound == d.length else {
+ return d.subdata(with: NSRange(location: range.lowerBound, length: range.count))._bridgeToObjectiveC()
+ }
return d.copy() as! NSData
}
}
@@ -777,14 +791,15 @@
internal class _NSSwiftData : NSData {
var _backing: _DataStorage!
+ var _range: Range<Data.Index>!
- convenience init(backing: _DataStorage) {
+ convenience init(backing: _DataStorage, range: Range<Data.Index>) {
self.init()
_backing = backing
+ _range = range
}
-
override var length: Int {
- return _backing.length
+ return _range.count
}
override var bytes: UnsafeRawPointer {
@@ -795,20 +810,28 @@
// return value here is not needed to be an allocated value. This is specifically
// needed to live like this to be source compatible with Swift3. Beyond that point
// this API may be subject to correction.
- return _backing.bytes ?? UnsafeRawPointer(bitPattern: 0xBAD0)!
+ guard let bytes = _backing.bytes else {
+ return UnsafeRawPointer(bitPattern: 0xBAD0)!
+ }
+
+ return bytes.advanced(by: _range.lowerBound)
}
override func copy(with zone: NSZone? = nil) -> Any {
- return NSData(bytes: _backing.bytes, length: _backing.length)
+ return self
}
-
+
+ override func mutableCopy(with zone: NSZone? = nil) -> Any {
+ return NSMutableData(bytes: bytes, length: length)
+ }
+
#if !DEPLOYMENT_RUNTIME_SWIFT
@objc
func _isCompact() -> Bool {
return true
}
#endif
-
+
#if DEPLOYMENT_RUNTIME_SWIFT
override func _providesConcreteBacking() -> Bool {
return true
@@ -942,7 +965,7 @@
}
_sliceRange = 0..<count
}
-
+
/// Initialize a `Data` with a repeating byte pattern
///
/// - parameter repeatedValue: A byte to initialize the pattern
@@ -1055,7 +1078,7 @@
_backing = _DataStorage(customReference: reference.copy() as! NSData)
_sliceRange = 0..<reference.length
}
-
+
}
@_versioned
@@ -1174,7 +1197,7 @@
#if !DEPLOYMENT_RUNTIME_SWIFT
@inline(__always)
private func _shouldUseNonAtomicWriteReimplementation(options: Data.WritingOptions = []) -> Bool {
-
+
// Avoid a crash that happens on OS X 10.11.x and iOS 9.x or before when writing a bridged Data non-atomically with Foundation's standard write() implementation.
if !options.contains(.atomic) {
#if os(OSX)
@@ -1336,6 +1359,21 @@
_sliceRange = _sliceRange.lowerBound..<(_sliceRange.lowerBound + resultingLength)
}
+ @inline(__always)
+ public mutating func replaceSubrange(_ subrange: CountableRange<Index>, with data: Data) {
+ let nsRange = NSMakeRange(subrange.lowerBound, subrange.upperBound - subrange.lowerBound)
+ let cnt = data.count
+ if !isKnownUniquelyReferenced(&_backing) {
+ _backing = _backing.mutableCopy(_sliceRange)
+ }
+ let resultingLength = data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Int in
+ let currentLength = _backing.length
+ _backing.replaceBytes(in: nsRange, with: bytes, length: cnt)
+ return currentLength - nsRange.length + cnt
+ }
+ _sliceRange = _sliceRange.lowerBound..<(_sliceRange.lowerBound + resultingLength)
+ }
+
/// Replace a region of bytes in the data with new bytes from a buffer.
///
/// This will resize the data if required, to fit the entire contents of `buffer`.
@@ -1387,7 +1425,7 @@
// In the future, if we keep the malloced pointer and count inside this struct/ref instead of deferring to NSData, we may be able to do this more efficiently.
self.count = resultCount
}
-
+
let shift = resultCount - currentCount
let start = subrange.lowerBound
@@ -1417,7 +1455,7 @@
let resultingLength = currentLength - nsRange.length + cnt
_sliceRange = _sliceRange.lowerBound..<(_sliceRange.lowerBound + resultingLength)
}
-
+
/// Return a new copy of the data in a specified range.
///
/// - parameter range: The range to copy.
@@ -1480,7 +1518,7 @@
public subscript(index: Index) -> UInt8 {
@inline(__always)
get {
- return _backing.bytes!.advanced(by: _sliceRange.lowerBound + index).assumingMemoryBound(to: UInt8.self).pointee
+ return _backing.bytes!.advanced(by: index).assumingMemoryBound(to: UInt8.self).pointee
}
@inline(__always)
set {
@@ -1502,6 +1540,24 @@
}
}
+ public subscript<R: RangeExpression>(_ rangeExpression: R) -> Data
+ where R.Bound: FixedWidthInteger, R.Bound.Stride : SignedInteger {
+ @inline(__always)
+ get {
+ let range = rangeExpression.relative(to: 0..<R.Bound.max)
+ let start: Int = numericCast(range.lowerBound)
+ let end: Int = numericCast(range.upperBound)
+ return Data(backing: _backing, range: start..<end)
+ }
+ @inline(__always)
+ set {
+ let range = rangeExpression.relative(to: 0..<R.Bound.max)
+ let start: Int = numericCast(range.lowerBound)
+ let end: Int = numericCast(range.upperBound)
+ replaceSubrange(start..<end, with: newValue)
+ }
+
+ }
/// The start `Index` in the data.
public var startIndex: Index {
@@ -1598,14 +1654,18 @@
let backing1 = d1._backing
let backing2 = d2._backing
if backing1 === backing2 {
- return true
+ if d1._sliceRange == d2._sliceRange {
+ return true
+ }
}
- let length1 = backing1.length
- if length1 != backing2.length {
+ let length1 = d1.count
+ if length1 != d2.count {
return false
}
if backing1.bytes == backing2.bytes {
- return true
+ if d1._sliceRange == d2._sliceRange {
+ return true
+ }
}
if length1 > 0 {
return d1.withUnsafeBytes { (b1) in
@@ -1668,7 +1728,7 @@
extension Data : DataBridgeType {
@_semantics("convertToObjectiveC")
public func _bridgeToObjectiveC() -> NSData {
- return _backing.bridgedReference()
+ return _backing.bridgedReference(_sliceRange)
}
public static func _forceBridgeFromObjectiveC(_ input: NSData, result: inout Data?) {
@@ -1683,9 +1743,8 @@
}
public static func _unconditionallyBridgeFromObjectiveC(_ source: NSData?) -> Data {
- var result: Data?
- _forceBridgeFromObjectiveC(source!, result: &result)
- return result!
+ guard let src = source else { return Data() }
+ return Data(referencing: src)
}
}
@@ -1696,3 +1755,48 @@
return AnyHashable(Data._unconditionallyBridgeFromObjectiveC(self))
}
}
+
+extension Data : Codable {
+ public init(from decoder: Decoder) throws {
+ var container = try decoder.unkeyedContainer()
+
+ // It's more efficient to pre-allocate the buffer if we can.
+ if let count = container.count {
+ self.init(count: count)
+
+ // Loop only until count, not while !container.isAtEnd, in case count is underestimated (this is misbehavior) and we haven't allocated enough space.
+ // We don't want to write past the end of what we allocated.
+ for i in 0 ..< count {
+ let byte = try container.decode(UInt8.self)
+ self[i] = byte
+ }
+ } else {
+ self.init()
+ }
+
+ while !container.isAtEnd {
+ var byte = try container.decode(UInt8.self)
+ self.append(&byte, count: 1)
+ }
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.unkeyedContainer()
+
+ // Since enumerateBytes does not rethrow, we need to catch the error, stow it away, and rethrow if we stopped.
+ var caughtError: Error? = nil
+ self.enumerateBytes { (buffer: UnsafeBufferPointer<UInt8>, byteIndex: Data.Index, stop: inout Bool) in
+ do {
+ try container.encode(contentsOf: buffer)
+ } catch {
+ caughtError = error
+ stop = true
+ }
+ }
+
+ if let error = caughtError {
+ throw error
+ }
+ }
+}
+
diff --git a/Foundation/NSCache.swift b/Foundation/NSCache.swift
index cce3797..0128f02 100644
--- a/Foundation/NSCache.swift
+++ b/Foundation/NSCache.swift
@@ -33,7 +33,7 @@
switch self.value {
case let nsObject as NSObject:
return nsObject.hashValue
- case let hashable as Hashable:
+ case let hashable as AnyHashable:
return hashable.hashValue
default: return 0
}
diff --git a/Foundation/NSURLSession/http/HTTPURLProtocol.swift b/Foundation/NSURLSession/http/HTTPURLProtocol.swift
index 081b464..017eaae 100644
--- a/Foundation/NSURLSession/http/HTTPURLProtocol.swift
+++ b/Foundation/NSURLSession/http/HTTPURLProtocol.swift
@@ -776,10 +776,15 @@
// For now, we'll notify the delegate, but won't pause the transfer,
// and we'll disregard the completion handler:
guard let s = session as? URLSession else { fatalError() }
- s.delegateQueue.addOperation {
- delegate.urlSession(s, dataTask: dt, didReceive: response, completionHandler: { _ in
- URLSession.printDebug("warning: Ignoring disposition from completion handler.")
- })
+ switch response.statusCode {
+ case 301, 302, 303, 307:
+ break
+ default:
+ s.delegateQueue.addOperation {
+ delegate.urlSession(s, dataTask: dt, didReceive: response, completionHandler: { _ in
+ URLSession.printDebug("warning: Ignoring disposition from completion handler.")
+ })
+ }
}
case .taskDelegate:
break
@@ -859,7 +864,7 @@
//TODO: Do we ever want to redirect for HEAD requests?
func methodAndURL() -> (String, URL)? {
guard
- let location = response.value(forHeaderField: .location),
+ let location = response.value(forHeaderField: .location, response: response),
let targetURL = URL(string: location)
else {
// Can't redirect when there's no location to redirect to.
@@ -882,8 +887,24 @@
guard let (method, targetURL) = methodAndURL() else { return nil }
var request = fromRequest
request.httpMethod = method
- request.url = targetURL
- return request
+
+ // If targetURL has only relative path of url, create a new valid url with relative path
+ // Otherwise, return request with targetURL ie.url from location field
+ guard targetURL.scheme == nil || targetURL.host == nil else {
+ request.url = targetURL
+ return request
+ }
+
+ let scheme = request.url?.scheme
+ let host = request.url?.host
+
+ var components = URLComponents()
+ components.scheme = scheme
+ components.host = host
+ components.path = targetURL.relativeString
+ guard let urlString = components.string else { fatalError("Invalid URL") }
+ request.url = URL(string: urlString)
+ return request
}
}
@@ -894,7 +915,12 @@
/// - SeeAlso: RFC 2616 section 14.30 <https://tools.ietf.org/html/rfc2616#section-14.30>
case location = "Location"
}
- func value(forHeaderField field: _Field) -> String? {
- return field.rawValue
+ func value(forHeaderField field: _Field, response: HTTPURLResponse?) -> String? {
+ let value = field.rawValue
+ guard let response = response else { fatalError("Response is nil") }
+ if let location = response.allHeaderFields[value] as? String {
+ return location
+ }
+ return nil
}
}
diff --git a/TestFoundation/HTTPServer.swift b/TestFoundation/HTTPServer.swift
index 8eb1239..bec7079 100644
--- a/TestFoundation/HTTPServer.swift
+++ b/TestFoundation/HTTPServer.swift
@@ -207,6 +207,7 @@
struct _HTTPResponse {
enum Response : Int {
case OK = 200
+ case REDIRECT = 302
}
private let responseCode: Response
private let headers: String
@@ -229,6 +230,7 @@
"Peru":"Lima",
"Italy":"Rome",
"USA":"Washington, D.C.",
+ "UnitedStates": "USA",
"country.txt": "A country is a region that is identified as a distinct national entity in political geography"]
let httpServer: _HTTPServer
let startDelay: TimeInterval?
@@ -269,6 +271,18 @@
let text = request.getCommaSeparatedHeaders()
return _HTTPResponse(response: .OK, headers: "Content-Length: \(text.characters.count)", body: text)
}
+
+ if uri == "/UnitedStates" {
+ let value = capitals[String(uri.characters.dropFirst())]!
+ let text = request.getCommaSeparatedHeaders()
+ let host = request.headers[1].components(separatedBy: " ")[1]
+ let ip = host.components(separatedBy: ":")[0]
+ let port = host.components(separatedBy: ":")[1]
+ let newPort = Int(port)! + 1
+ let newHost = ip + ":" + String(newPort)
+ let httpResponse = _HTTPResponse(response: .REDIRECT, headers: "Location: http://\(newHost + "/" + value)", body: text)
+ return httpResponse
+ }
return _HTTPResponse(response: .OK, body: capitals[String(uri.characters.dropFirst())]!)
}
diff --git a/TestFoundation/TestNSCache.swift b/TestFoundation/TestNSCache.swift
index 1dde6d5..9ab9ed8 100644
--- a/TestFoundation/TestNSCache.swift
+++ b/TestFoundation/TestNSCache.swift
@@ -23,6 +23,8 @@
("test_setWithMutableKeys", test_setWithMutableKeys),
("test_costLimit", test_costLimit),
("test_countLimit", test_countLimit),
+ ("test_hashableKey", test_hashableKey),
+ ("test_nonHashableKey", test_nonHashableKey)
]
}
@@ -106,4 +108,66 @@
XCTAssertNil(cache.object(forKey: key1), "should be nil")
}
+
+
+ class TestHashableCacheKey: Hashable {
+ let string: String
+ var hashValue: Int { return string.hashValue }
+
+ init(string: String) {
+ self.string = string
+ }
+
+ static func ==(lhs: TestHashableCacheKey,
+ rhs:TestHashableCacheKey) -> Bool {
+ return lhs.string == rhs.string
+ }
+ }
+
+ // Test when NSCacheKey.value is AnyHashable
+ func test_hashableKey() {
+ let cache = NSCache<TestHashableCacheKey, NSString>()
+ cache.countLimit = 2
+
+ let key1 = TestHashableCacheKey(string: "key1")
+ let key2 = TestHashableCacheKey(string: "key2")
+ let key3 = TestHashableCacheKey(string: "key3")
+ let value = NSString(string: "value")
+
+ cache.setObject(value, forKey: key1)
+ cache.setObject(value, forKey: key2)
+ cache.setObject(value, forKey: key3)
+
+ XCTAssertEqual(cache.object(forKey: key2), value, "should be equal to \(value)")
+ XCTAssertEqual(cache.object(forKey: key3), value, "should be equal to \(value)")
+ XCTAssertNil(cache.object(forKey: key1), "should be nil")
+ }
+
+
+ class TestCacheKey {
+ let string: String
+
+ init(string: String) {
+ self.string = string
+ }
+ }
+
+ // Test when NSCacheKey.value is neither NSObject or AnyHashable
+ func test_nonHashableKey() {
+ let cache = NSCache<TestCacheKey, NSString>()
+ cache.countLimit = 2
+
+ let key1 = TestCacheKey(string: "key1")
+ let key2 = TestCacheKey(string: "key2")
+ let key3 = TestCacheKey(string: "key3")
+ let value = NSString(string: "value")
+
+ cache.setObject(value, forKey: key1)
+ cache.setObject(value, forKey: key2)
+ cache.setObject(value, forKey: key3)
+
+ XCTAssertEqual(cache.object(forKey: key2), value, "should be equal to \(value)")
+ XCTAssertEqual(cache.object(forKey: key3), value, "should be equal to \(value)")
+ XCTAssertNil(cache.object(forKey: key1), "should be nil")
+ }
}
diff --git a/TestFoundation/TestNSURLSession.swift b/TestFoundation/TestNSURLSession.swift
index c89f5ae..3bb16f6 100644
--- a/TestFoundation/TestNSURLSession.swift
+++ b/TestFoundation/TestNSURLSession.swift
@@ -39,6 +39,7 @@
("test_verifyHttpAdditionalHeaders", test_verifyHttpAdditionalHeaders),
("test_timeoutInterval", test_timeoutInterval),
("test_customProtocol", test_customProtocol),
+ ("test_httpRedirection", test_httpRedirection),
]
}
@@ -467,6 +468,24 @@
task.resume()
waitForExpectations(timeout: 12)
}
+
+ func test_httpRedirection() {
+ let serverReady = ServerSemaphore()
+ globalDispatchQueue.async {
+ do {
+ try self.runServer(with: serverReady)
+ } catch {
+ XCTAssertTrue(true)
+ return
+ }
+ }
+ serverReady.wait()
+ let urlString = "http://127.0.0.1:\(serverPort)/UnitedStates"
+ let url = URL(string: urlString)!
+ let d = HTTPRedirectionDataTask(with: expectation(description: "data task"))
+ d.run(with: url)
+ waitForExpectations(timeout: 12)
+ }
}
class SessionDelegate: NSObject, URLSessionDelegate {
@@ -516,14 +535,13 @@
extension DataTask : URLSessionDataDelegate {
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
capital = String(data: data, encoding: String.Encoding.utf8)!
- dataTaskExpectation.fulfill()
}
}
extension DataTask : URLSessionTaskDelegate {
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
- guard (error as? URLError) != nil else { return }
dataTaskExpectation.fulfill()
+ guard (error as? URLError) != nil else { return }
if let cancellation = cancelExpectation {
cancellation.fulfill()
}
@@ -608,3 +626,61 @@
return
}
}
+
+class HTTPRedirectionDataTask : NSObject {
+ let dataTaskExpectation: XCTestExpectation!
+ var session: URLSession! = nil
+ var task: URLSessionDataTask! = nil
+ var cancelExpectation: XCTestExpectation?
+
+ public var error = false
+
+ init(with expectation: XCTestExpectation) {
+ dataTaskExpectation = expectation
+ }
+
+ func run(with request: URLRequest) {
+ let config = URLSessionConfiguration.default
+ config.timeoutIntervalForRequest = 8
+ session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
+ task = session.dataTask(with: request)
+ task.resume()
+ }
+
+ func run(with url: URL) {
+ let config = URLSessionConfiguration.default
+ config.timeoutIntervalForRequest = 8
+ session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
+ task = session.dataTask(with: url)
+ task.resume()
+ }
+
+ func cancel() {
+ task.cancel()
+ }
+}
+
+extension HTTPRedirectionDataTask : URLSessionDataDelegate {
+ public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
+ guard let httpresponse = response as? HTTPURLResponse else { fatalError() }
+ XCTAssertNotNil(response)
+ XCTAssertEqual(200, httpresponse.statusCode, "HTTP response code is not 200")
+ }
+}
+
+extension HTTPRedirectionDataTask : URLSessionTaskDelegate {
+ public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
+ dataTaskExpectation.fulfill()
+ guard (error as? URLError) != nil else { return }
+ if let cancellation = cancelExpectation {
+ cancellation.fulfill()
+ }
+ self.error = true
+ }
+
+ public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
+ XCTAssertNotNil(response)
+ XCTAssertEqual(302, response.statusCode, "HTTP response code is not 302")
+ completionHandler(request)
+ }
+}