Skip to content

Commit 5df83f7

Browse files
author
Pushkar N Kulkarni
committed
Loopback tests for URLSession
1 parent 2d80441 commit 5df83f7

File tree

4 files changed

+372
-40
lines changed

4 files changed

+372
-40
lines changed

Foundation.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
0383A1751D2E558A0052E5D1 /* TestNSStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0383A1741D2E558A0052E5D1 /* TestNSStream.swift */; };
11+
1520469B1D8AEABE00D02E36 /* HTTPServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1520469A1D8AEABE00D02E36 /* HTTPServer.swift */; };
1112
294E3C1D1CC5E19300E4F44C /* TestNSAttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294E3C1C1CC5E19300E4F44C /* TestNSAttributedString.swift */; };
1213
2EBE67A51C77BF0E006583D5 /* TestNSDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBE67A31C77BF05006583D5 /* TestNSDateFormatter.swift */; };
1314
528776141BF2629700CB0090 /* FoundationErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522C253A1BF16E1600804FC6 /* FoundationErrors.swift */; };
@@ -448,6 +449,7 @@
448449

449450
/* Begin PBXFileReference section */
450451
0383A1741D2E558A0052E5D1 /* TestNSStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSStream.swift; sourceTree = "<group>"; };
452+
1520469A1D8AEABE00D02E36 /* HTTPServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPServer.swift; sourceTree = "<group>"; };
451453
22B9C1E01C165D7A00DECFF9 /* TestNSDate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSDate.swift; sourceTree = "<group>"; };
452454
294E3C1C1CC5E19300E4F44C /* TestNSAttributedString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSAttributedString.swift; sourceTree = "<group>"; };
453455
2EBE67A31C77BF05006583D5 /* TestNSDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSDateFormatter.swift; sourceTree = "<group>"; };
@@ -1268,6 +1270,7 @@
12681270
EA66F6371BF1619600136161 /* TestFoundation */ = {
12691271
isa = PBXGroup;
12701272
children = (
1273+
1520469A1D8AEABE00D02E36 /* HTTPServer.swift */,
12711274
EA66F6381BF1619600136161 /* main.swift */,
12721275
EA66F65A1BF1976100136161 /* Tests */,
12731276
EA66F6391BF1619600136161 /* Resources */,
@@ -2183,6 +2186,7 @@
21832186
files = (
21842187
5FE52C951D147D1C00F7D270 /* TestNSTextCheckingResult.swift in Sources */,
21852188
5B13B3451C582D4C00651CE2 /* TestNSString.swift in Sources */,
2189+
1520469B1D8AEABE00D02E36 /* HTTPServer.swift in Sources */,
21862190
5B13B3471C582D4C00651CE2 /* TestNSThread.swift in Sources */,
21872191
5B13B32E1C582D4C00651CE2 /* TestNSFileManager.swift in Sources */,
21882192
5B13B3381C582D4C00651CE2 /* TestNSNotificationQueue.swift in Sources */,

TestFoundation/HTTPServer.swift

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
// This source file is part of the Swift.org open source project
2+
//
3+
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
4+
// Licensed under Apache License v2.0 with Runtime Library Exception
5+
//
6+
// See http://swift.org/LICENSE.txt for license information
7+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8+
//
9+
10+
11+
//This is a very rudimentary HTTP server written plainly for testing URLSession.
12+
//It is not concurrent. It listens on a port, reads once and writes back only once.
13+
//We can make it better everytime we need more functionality to test different aspects of URLSession.
14+
15+
import Dispatch
16+
17+
#if DEPLOYMENT_RUNTIME_OBJC || os(Linux)
18+
import Foundation
19+
import Glibc
20+
#else
21+
import CoreFoundation
22+
import SwiftFoundation
23+
import Darwin
24+
#endif
25+
26+
struct _HTTPUtils {
27+
static let CRLF = "\r\n"
28+
static let VERSION = "HTTP/1.1"
29+
static let SPACE = " "
30+
static let CRLF2 = CRLF + CRLF
31+
static let EMPTY = ""
32+
}
33+
34+
class _TCPSocket {
35+
36+
private var listenSocket: Int32!
37+
private var socketAddress = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: 1)
38+
private var connectionSocket: Int32!
39+
40+
private func isNotNegative(r: CInt) -> Bool {
41+
return r != -1
42+
}
43+
44+
private func isZero(r: CInt) -> Bool {
45+
return r == 0
46+
}
47+
48+
private func attempt(_ name: String, file: String = #file, line: UInt = #line, valid: (CInt) -> Bool, _ b: @autoclosure () -> CInt) throws -> CInt {
49+
let r = b()
50+
guard valid(r) else { throw ServerError(operation: name, errno: r, file: file, line: line) }
51+
return r
52+
}
53+
54+
init(port: UInt16) throws {
55+
#if os(Linux)
56+
let SOCKSTREAM = Int32(SOCK_STREAM.rawValue)
57+
#else
58+
let SOCKSTREAM = SOCK_STREAM
59+
#endif
60+
listenSocket = try attempt("socket", valid: isNotNegative, socket(AF_INET, SOCKSTREAM, Int32(IPPROTO_TCP)))
61+
var on: Int = 1
62+
_ = try attempt("setsockopt", valid: isZero, setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &on, socklen_t(MemoryLayout<Int>.size)))
63+
let sa = createSockaddr(port)
64+
socketAddress.initialize(to: sa)
65+
try socketAddress.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout<sockaddr>.size, {
66+
let addr = UnsafePointer<sockaddr>($0)
67+
_ = try attempt("bind", valid: isZero, bind(listenSocket, addr, socklen_t(MemoryLayout<sockaddr>.size)))
68+
})
69+
}
70+
71+
private func createSockaddr(_ port: UInt16) -> sockaddr_in {
72+
#if os(Linux)
73+
return sockaddr_in(sin_family: sa_family_t(AF_INET), sin_port: htons(port), sin_addr: in_addr(s_addr: INADDR_ANY), sin_zero: (0,0,0,0,0,0,0,0))
74+
#else
75+
return sockaddr_in(sin_len: 0, sin_family: sa_family_t(AF_INET), sin_port: CFSwapInt16HostToBig(port), sin_addr: in_addr(s_addr: INADDR_ANY), sin_zero: (0,0,0,0,0,0,0,0))
76+
#endif
77+
}
78+
func acceptConnection(notify: DispatchSemaphore) throws {
79+
_ = try attempt("listen", valid: isZero, listen(listenSocket, SOMAXCONN))
80+
try socketAddress.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout<sockaddr>.size, {
81+
let addr = UnsafeMutablePointer<sockaddr>($0)
82+
var sockLen = socklen_t(MemoryLayout<sockaddr>.size)
83+
notify.signal()
84+
connectionSocket = try attempt("accept", valid: isNotNegative, accept(listenSocket, addr, &sockLen))
85+
})
86+
}
87+
88+
func readData() throws -> String {
89+
var buffer = [UInt8](repeating: 0, count: 4096)
90+
_ = try attempt("read", valid: isNotNegative, CInt(read(connectionSocket, &buffer, 4096)))
91+
return String(cString: &buffer)
92+
}
93+
94+
func writeData(data: String) throws {
95+
var bytes = Array(data.utf8)
96+
_ = try attempt("write", valid: isNotNegative, CInt(write(connectionSocket, &bytes, data.utf8.count)))
97+
}
98+
99+
func shutdown() {
100+
close(connectionSocket)
101+
close(listenSocket)
102+
}
103+
}
104+
105+
class _HTTPServer {
106+
107+
let socket: _TCPSocket
108+
109+
init(port: UInt16) throws {
110+
socket = try _TCPSocket(port: port)
111+
}
112+
113+
public class func create(port: UInt16) throws -> _HTTPServer {
114+
return try _HTTPServer(port: port)
115+
}
116+
117+
public func listen(notify: DispatchSemaphore) throws {
118+
try socket.acceptConnection(notify: notify)
119+
}
120+
121+
public func stop() {
122+
socket.shutdown()
123+
}
124+
125+
public func request() throws -> _HTTPRequest {
126+
return _HTTPRequest(request: try socket.readData())
127+
}
128+
129+
public func respond(with response: _HTTPResponse) throws {
130+
try socket.writeData(data: response.description)
131+
}
132+
}
133+
134+
struct _HTTPRequest {
135+
enum Method : String {
136+
case GET
137+
case POST
138+
case PUT
139+
}
140+
let method: Method
141+
let uri: String
142+
let body: String
143+
let headers: [String]
144+
145+
public init(request: String) {
146+
let lines = request.components(separatedBy: _HTTPUtils.CRLF2)[0].components(separatedBy: _HTTPUtils.CRLF)
147+
headers = Array(lines[0...lines.count-2])
148+
method = Method(rawValue: headers[0].components(separatedBy: " ")[0])!
149+
uri = headers[0].components(separatedBy: " ")[1]
150+
body = lines.last!
151+
}
152+
153+
}
154+
155+
struct _HTTPResponse {
156+
enum Response : Int {
157+
case OK = 200
158+
}
159+
private let responseCode: Response
160+
private let headers: String
161+
private let body: String
162+
163+
public init(response: Response, headers: String = _HTTPUtils.EMPTY, body: String) {
164+
self.responseCode = response
165+
self.headers = headers
166+
self.body = body
167+
}
168+
169+
public var description: String {
170+
let statusLine = _HTTPUtils.VERSION + _HTTPUtils.SPACE + "\(responseCode.rawValue)" + _HTTPUtils.SPACE + "\(responseCode)"
171+
return statusLine + (headers != _HTTPUtils.EMPTY ? _HTTPUtils.CRLF + headers : _HTTPUtils.EMPTY) + _HTTPUtils.CRLF2 + body
172+
}
173+
}
174+
175+
public class TestURLSessionServer {
176+
let capitals: [String:String] = ["Nepal":"Kathmandu",
177+
"Peru":"Lima",
178+
"Italy":"Rome",
179+
"USA":"Washington, D.C.",
180+
"country.txt": "A country is a region that is identified as a distinct national entity in political geography"]
181+
let httpServer: _HTTPServer
182+
183+
public init (port: UInt16) throws {
184+
httpServer = try _HTTPServer.create(port: port)
185+
}
186+
public func start(started: DispatchSemaphore) throws {
187+
started.signal()
188+
try httpServer.listen(notify: started)
189+
}
190+
191+
public func readAndRespond() throws {
192+
try httpServer.respond(with: process(request: httpServer.request()))
193+
}
194+
195+
func process(request: _HTTPRequest) -> _HTTPResponse {
196+
if request.method == .GET {
197+
return getResponse(uri: request.uri)
198+
} else {
199+
fatalError("Unsupported method!")
200+
}
201+
}
202+
203+
func getResponse(uri: String) -> _HTTPResponse {
204+
if uri == "/country.txt" {
205+
let text = capitals[String(uri.characters.dropFirst())]!
206+
return _HTTPResponse(response: .OK, headers: "Content-Length: \(text.characters.count)", body: text)
207+
}
208+
return _HTTPResponse(response: .OK, body: capitals[String(uri.characters.dropFirst())]!)
209+
}
210+
211+
func stop() {
212+
httpServer.stop()
213+
}
214+
}
215+
216+
struct ServerError : Error {
217+
let operation: String
218+
let errno: CInt
219+
let file: String
220+
let line: UInt
221+
var _code: Int { return Int(errno) }
222+
var _domain: String { return NSPOSIXErrorDomain }
223+
}
224+
225+
226+
extension ServerError : CustomStringConvertible {
227+
var description: String {
228+
let s = String(validatingUTF8: strerror(errno)) ?? ""
229+
return "\(operation) failed: \(s) (\(_code))"
230+
}
231+
}

0 commit comments

Comments
 (0)