Skip to content

Commit 5e2e805

Browse files
committed
Adding tests
1 parent dd3cd57 commit 5e2e805

File tree

4 files changed

+282
-2
lines changed

4 files changed

+282
-2
lines changed

Diff for: Sources/AsyncHTTPClient/ConnectionPool/HTTP1.1/HTTP1ConnectionStateMachine.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,12 @@ struct HTTP1ConnectionStateMachine {
200200
preconditionFailure("This event must only happen, if the connection is leased. During startup this is impossible")
201201

202202
case .idle:
203-
self.state = .closing
204-
return .close
203+
if closeConnection {
204+
self.state = .closing
205+
return .close
206+
} else {
207+
return .wait
208+
}
205209

206210
case .inRequest(var requestStateMachine, close: let close):
207211
return self.avoidingStateMachineCoW { state -> Action in
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
//
15+
// HTTP1ConnectionStateMachineTests+XCTest.swift
16+
//
17+
import XCTest
18+
19+
///
20+
/// NOTE: This file was generated by generate_linux_tests.rb
21+
///
22+
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
23+
///
24+
25+
extension HTTP1ConnectionStateMachineTests {
26+
static var allTests: [(String, (HTTP1ConnectionStateMachineTests) -> () throws -> Void)] {
27+
return [
28+
("testPOSTRequestWithWriteAndReadBackpressure", testPOSTRequestWithWriteAndReadBackpressure),
29+
("testResponseReadingWithBackpressure", testResponseReadingWithBackpressure),
30+
("testAConnectionCloseHeaderInTheRequestLeadsToConnectionCloseAfterRequest", testAConnectionCloseHeaderInTheRequestLeadsToConnectionCloseAfterRequest),
31+
("testAConnectionCloseHeaderInTheResponseLeadsToConnectionCloseAfterRequest", testAConnectionCloseHeaderInTheResponseLeadsToConnectionCloseAfterRequest),
32+
("testNIOTriggersChannelActiveTwice", testNIOTriggersChannelActiveTwice),
33+
("testIdleConnectionBecomesInactive", testIdleConnectionBecomesInactive),
34+
("testConnectionGoesAwayWhileInRequest", testConnectionGoesAwayWhileInRequest),
35+
("testRequestWasCancelledWhileUploadingData", testRequestWasCancelledWhileUploadingData),
36+
("testCancelRequestIsIgnoredWhenConnectionIsIdle", testCancelRequestIsIgnoredWhenConnectionIsIdle),
37+
("testReadsAreForwardedIfConnectionIsClosing", testReadsAreForwardedIfConnectionIsClosing),
38+
("testChannelReadsAreIgnoredIfConnectionIsClosing", testChannelReadsAreIgnoredIfConnectionIsClosing),
39+
]
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
@testable import AsyncHTTPClient
16+
import NIO
17+
import NIOHTTP1
18+
import XCTest
19+
20+
class HTTP1ConnectionStateMachineTests: XCTestCase {
21+
func testPOSTRequestWithWriteAndReadBackpressure() {
22+
var state = HTTP1ConnectionStateMachine()
23+
XCTAssertEqual(state.channelActive(isWritable: false), .fireChannelActive)
24+
25+
let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/", headers: HTTPHeaders([("content-length", "4")]))
26+
let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(4))
27+
XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .wait)
28+
XCTAssertEqual(state.writabilityChanged(writable: true), .sendRequestHead(requestHead, startBody: true))
29+
30+
let part0 = IOData.byteBuffer(ByteBuffer(bytes: [0]))
31+
let part1 = IOData.byteBuffer(ByteBuffer(bytes: [1]))
32+
let part2 = IOData.byteBuffer(ByteBuffer(bytes: [2]))
33+
let part3 = IOData.byteBuffer(ByteBuffer(bytes: [3]))
34+
XCTAssertEqual(state.requestStreamPartReceived(part0), .sendBodyPart(part0))
35+
XCTAssertEqual(state.requestStreamPartReceived(part1), .sendBodyPart(part1))
36+
37+
// oh the channel reports... we should slow down producing...
38+
XCTAssertEqual(state.writabilityChanged(writable: false), .pauseRequestBodyStream)
39+
40+
// but we issued a .produceMoreRequestBodyData before... Thus, we must accept more produced
41+
// data
42+
XCTAssertEqual(state.requestStreamPartReceived(part2), .sendBodyPart(part2))
43+
// however when we have put the data on the channel, we should not issue further
44+
// .produceMoreRequestBodyData events
45+
46+
// once we receive a writable event again, we can allow the producer to produce more data
47+
XCTAssertEqual(state.writabilityChanged(writable: true), .resumeRequestBodyStream)
48+
XCTAssertEqual(state.requestStreamPartReceived(part3), .sendBodyPart(part3))
49+
XCTAssertEqual(state.requestStreamFinished(), .sendRequestEnd)
50+
51+
let responseHead = HTTPResponseHead(version: .http1_1, status: .ok)
52+
XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false))
53+
let responseBody = ByteBuffer(bytes: [1, 2, 3, 4])
54+
XCTAssertEqual(state.channelRead(.body(responseBody)), .wait)
55+
XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.informConnectionIsIdle, .init([responseBody])))
56+
XCTAssertEqual(state.channelReadComplete(), .wait)
57+
}
58+
59+
func testResponseReadingWithBackpressure() {
60+
var state = HTTP1ConnectionStateMachine()
61+
XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive)
62+
63+
let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/")
64+
let metadata = RequestFramingMetadata(connectionClose: false, body: .none)
65+
XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false))
66+
67+
let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: HTTPHeaders([("content-length", "12")]))
68+
XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false))
69+
let part0 = ByteBuffer(bytes: 0...3)
70+
let part1 = ByteBuffer(bytes: 4...7)
71+
let part2 = ByteBuffer(bytes: 8...11)
72+
XCTAssertEqual(state.channelRead(.body(part0)), .wait)
73+
XCTAssertEqual(state.channelRead(.body(part1)), .wait)
74+
XCTAssertEqual(state.channelReadComplete(), .forwardResponseBodyParts(.init([part0, part1])))
75+
XCTAssertEqual(state.read(), .wait)
76+
XCTAssertEqual(state.read(), .wait, "Expected to be able to consume a second read event")
77+
XCTAssertEqual(state.demandMoreResponseBodyParts(), .read)
78+
XCTAssertEqual(state.channelRead(.body(part2)), .wait)
79+
XCTAssertEqual(state.channelReadComplete(), .forwardResponseBodyParts(.init([part2])))
80+
XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait)
81+
XCTAssertEqual(state.read(), .read)
82+
XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.informConnectionIsIdle, .init()))
83+
XCTAssertEqual(state.channelReadComplete(), .wait)
84+
XCTAssertEqual(state.read(), .read)
85+
}
86+
87+
func testAConnectionCloseHeaderInTheRequestLeadsToConnectionCloseAfterRequest() {
88+
var state = HTTP1ConnectionStateMachine()
89+
XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive)
90+
let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/", headers: ["connection": "close"])
91+
let metadata = RequestFramingMetadata(connectionClose: true, body: .none)
92+
XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false))
93+
94+
let responseHead = HTTPResponseHead(version: .http1_1, status: .ok)
95+
XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false))
96+
let responseBody = ByteBuffer(bytes: [1, 2, 3, 4])
97+
XCTAssertEqual(state.channelRead(.body(responseBody)), .wait)
98+
XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.close, .init([responseBody])))
99+
}
100+
101+
func testAConnectionCloseHeaderInTheResponseLeadsToConnectionCloseAfterRequest() {
102+
var state = HTTP1ConnectionStateMachine()
103+
XCTAssertEqual(state.channelActive(isWritable: false), .fireChannelActive)
104+
XCTAssertEqual(state.writabilityChanged(writable: true), .wait)
105+
let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/")
106+
let metadata = RequestFramingMetadata(connectionClose: false, body: .none)
107+
XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false))
108+
109+
let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: ["connection": "close"])
110+
XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false))
111+
let responseBody = ByteBuffer(bytes: [1, 2, 3, 4])
112+
XCTAssertEqual(state.channelRead(.body(responseBody)), .wait)
113+
XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.close, .init([responseBody])))
114+
}
115+
116+
func testNIOTriggersChannelActiveTwice() {
117+
var state = HTTP1ConnectionStateMachine()
118+
XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive)
119+
XCTAssertEqual(state.channelActive(isWritable: true), .wait)
120+
}
121+
122+
func testIdleConnectionBecomesInactive() {
123+
var state = HTTP1ConnectionStateMachine()
124+
XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive)
125+
XCTAssertEqual(state.channelInactive(), .fireChannelInactive)
126+
XCTAssertEqual(state.channelInactive(), .wait)
127+
}
128+
129+
func testConnectionGoesAwayWhileInRequest() {
130+
var state = HTTP1ConnectionStateMachine()
131+
XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive)
132+
133+
let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/")
134+
let metadata = RequestFramingMetadata(connectionClose: false, body: .none)
135+
XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false))
136+
137+
XCTAssertEqual(state.channelInactive(), .failRequest(HTTPClientError.remoteConnectionClosed, .none))
138+
}
139+
140+
func testRequestWasCancelledWhileUploadingData() {
141+
var state = HTTP1ConnectionStateMachine()
142+
XCTAssertEqual(state.channelActive(isWritable: false), .fireChannelActive)
143+
144+
let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/", headers: HTTPHeaders([("content-length", "4")]))
145+
let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(4))
146+
XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .wait)
147+
XCTAssertEqual(state.writabilityChanged(writable: true), .sendRequestHead(requestHead, startBody: true))
148+
149+
let part0 = IOData.byteBuffer(ByteBuffer(bytes: [0]))
150+
let part1 = IOData.byteBuffer(ByteBuffer(bytes: [1]))
151+
XCTAssertEqual(state.requestStreamPartReceived(part0), .sendBodyPart(part0))
152+
XCTAssertEqual(state.requestStreamPartReceived(part1), .sendBodyPart(part1))
153+
XCTAssertEqual(state.requestCancelled(closeConnection: false), .failRequest(HTTPClientError.cancelled, .close))
154+
}
155+
156+
func testCancelRequestIsIgnoredWhenConnectionIsIdle() {
157+
var state = HTTP1ConnectionStateMachine()
158+
XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive)
159+
XCTAssertEqual(state.requestCancelled(closeConnection: false), .wait, "Should be ignored.")
160+
XCTAssertEqual(state.requestCancelled(closeConnection: true), .close, "Should lead to connection closure.")
161+
XCTAssertEqual(state.requestCancelled(closeConnection: true), .wait, "Should be ignored. Connection is already closing")
162+
XCTAssertEqual(state.channelInactive(), .fireChannelInactive)
163+
XCTAssertEqual(state.requestCancelled(closeConnection: true), .wait, "Should be ignored. Connection is already closed")
164+
}
165+
166+
func testReadsAreForwardedIfConnectionIsClosing() {
167+
var state = HTTP1ConnectionStateMachine()
168+
XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive)
169+
XCTAssertEqual(state.requestCancelled(closeConnection: true), .close)
170+
XCTAssertEqual(state.read(), .read)
171+
XCTAssertEqual(state.channelInactive(), .fireChannelInactive)
172+
XCTAssertEqual(state.read(), .read)
173+
}
174+
175+
func testChannelReadsAreIgnoredIfConnectionIsClosing() {
176+
var state = HTTP1ConnectionStateMachine()
177+
XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive)
178+
XCTAssertEqual(state.requestCancelled(closeConnection: true), .close)
179+
XCTAssertEqual(state.channelRead(.end(nil)), .wait)
180+
XCTAssertEqual(state.channelReadComplete(), .wait)
181+
XCTAssertEqual(state.channelInactive(), .fireChannelInactive)
182+
XCTAssertEqual(state.channelRead(.end(nil)), .wait)
183+
}
184+
}
185+
186+
extension HTTP1ConnectionStateMachine.Action: Equatable {
187+
public static func == (lhs: Self, rhs: Self) -> Bool {
188+
switch (lhs, rhs) {
189+
case (.fireChannelActive, .fireChannelActive):
190+
return true
191+
192+
case (.fireChannelInactive, .fireChannelInactive):
193+
return true
194+
195+
case (.sendRequestHead(let lhsHead, let lhsStartBody), .sendRequestHead(let rhsHead, let rhsStartBody)):
196+
return lhsHead == rhsHead && lhsStartBody == rhsStartBody
197+
198+
case (.sendBodyPart(let lhsData), .sendBodyPart(let rhsData)):
199+
return lhsData == rhsData
200+
201+
case (.sendRequestEnd, .sendRequestEnd):
202+
return true
203+
204+
case (.pauseRequestBodyStream, .pauseRequestBodyStream):
205+
return true
206+
case (.resumeRequestBodyStream, .resumeRequestBodyStream):
207+
return true
208+
209+
case (.forwardResponseHead(let lhsHead, let lhsPauseRequestBodyStream), .forwardResponseHead(let rhsHead, let rhsPauseRequestBodyStream)):
210+
return lhsHead == rhsHead && lhsPauseRequestBodyStream == rhsPauseRequestBodyStream
211+
212+
case (.forwardResponseBodyParts(let lhsData), .forwardResponseBodyParts(let rhsData)):
213+
return lhsData == rhsData
214+
215+
case (.succeedRequest(let lhsFinalAction, let lhsFinalBuffer), .succeedRequest(let rhsFinalAction, let rhsFinalBuffer)):
216+
return lhsFinalAction == rhsFinalAction && lhsFinalBuffer == rhsFinalBuffer
217+
218+
case (.failRequest(_, let lhsFinalAction), .failRequest(_, let rhsFinalAction)):
219+
return lhsFinalAction == rhsFinalAction
220+
221+
case (.read, .read):
222+
return true
223+
224+
case (.close, .close):
225+
return true
226+
227+
case (.wait, .wait):
228+
return true
229+
230+
default:
231+
return false
232+
}
233+
}
234+
}

Diff for: Tests/LinuxMain.swift

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import XCTest
2828
XCTMain([
2929
testCase(ConnectionPoolTests.allTests),
3030
testCase(ConnectionTests.allTests),
31+
testCase(HTTP1ConnectionStateMachineTests.allTests),
3132
testCase(HTTP1ProxyConnectHandlerTests.allTests),
3233
testCase(HTTPClientCookieTests.allTests),
3334
testCase(HTTPClientInternalTests.allTests),

0 commit comments

Comments
 (0)