Skip to content

Commit 03a527a

Browse files
authored
Move to Localhost servers for HTTP tests. (#355)
Co-authored-by: Krish <>
1 parent 013c908 commit 03a527a

10 files changed

Lines changed: 224 additions & 34 deletions

File tree

.builder/actions/localhost_test.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import Builder
2+
import subprocess
3+
import socket
4+
import sys
5+
import time
6+
import os
7+
8+
9+
class LocalhostTest(Builder.Action):
10+
11+
def start(self, env):
12+
python = sys.executable
13+
venv_path = os.path.join(env.root_dir,'aws-common-runtime','aws-c-http','tests','mock_server', '.venv')
14+
15+
result = env.shell.exec(python, '-m', 'venv', venv_path)
16+
if result.returncode != 0:
17+
print("Could not start a virtual environment. The localhost integration tests will fail.", file=sys.stderr)
18+
return
19+
20+
python = os.path.join(venv_path, "bin", "python")
21+
22+
result = env.shell.exec(python, '-m', 'pip', 'install', 'h11', 'h2', 'trio')
23+
if result.returncode != 0:
24+
print("Could not install python HTTP dependencies. The localhost integration tests will fail.", file=sys.stderr)
25+
return
26+
27+
server_dir = os.path.join(env.root_dir,'aws-common-runtime','aws-c-http','tests','mock_server')
28+
29+
p1 = subprocess.Popen([python, "h2tls_mock_server.py"], cwd=server_dir)
30+
p2 = subprocess.Popen([python, "h2non_tls_server.py"], cwd=server_dir)
31+
env = os.environ.copy()
32+
env['HTTP_PORT'] = '8091'
33+
env['HTTPS_PORT'] = '8092'
34+
p3 = subprocess.Popen([python, "h11mock_server.py"], cwd=server_dir, env=env)
35+
36+
# Wait for servers to be ready, more information about mock servers on README.md
37+
ports = [3443, 3280, 8092, 8091]
38+
for port in ports:
39+
for attempt in range(30):
40+
try:
41+
with socket.create_connection(("localhost", port), timeout=1):
42+
print(f"Server on port {port} is ready")
43+
break
44+
except (socket.error, ConnectionRefusedError, OSError):
45+
if attempt == 29:
46+
print(f"ERROR: Server on port {port} failed to start", file=sys.stderr)
47+
time.sleep(1)
48+
49+
def run(self, env):
50+
self.start(env)
51+
env.shell.setenv('AWS_CRT_MEMORY_TRACING', '2')
52+
53+
if os.system("env AWS_CRT_LOCALHOST=true swift test --filter 'HTTPTests|HTTP2ClientConnectionTests'"):
54+
# Failed
55+
actions.append("exit 1")
56+
57+
# kill servers
58+
env.shell.exec("pkill", "-f", "h2tls_mock_server.py")
59+
env.shell.exec("pkill", "-f", "h2non_tls_server.py")
60+
env.shell.exec("pkill", "-f", "h11mock_server.py")

.github/workflows/ci.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,37 @@ jobs:
181181
# note: using "@main" because "@${{env.BUILDER_VERSION}}" doesn't work
182182
# https://github.com/actions/runner/issues/480
183183
uses: awslabs/aws-crt-builder/.github/actions/check-submodules@main
184+
185+
localhost-test-linux:
186+
runs-on: ubuntu-24.04 # latest
187+
steps:
188+
- uses: aws-actions/configure-aws-credentials@v4
189+
with:
190+
role-to-assume: ${{ env.CRT_CI_ROLE }}
191+
aws-region: ${{ env.AWS_DEFAULT_REGION }}
192+
- name: Checkout
193+
uses: actions/checkout@v4
194+
with:
195+
submodules: true
196+
- name: Build and test
197+
run: |
198+
python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')"
199+
python builder.pyz build -p ${{ env.PACKAGE_NAME }} --variant=localhost
200+
201+
202+
localhost-test-macos:
203+
runs-on: macos-14 # latest
204+
steps:
205+
- uses: aws-actions/configure-aws-credentials@v4
206+
with:
207+
role-to-assume: ${{ env.CRT_CI_ROLE }}
208+
aws-region: ${{ env.AWS_DEFAULT_REGION }}
209+
- name: Checkout
210+
uses: actions/checkout@v4
211+
with:
212+
submodules: true
213+
- name: Build and test
214+
run: |
215+
python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')"
216+
chmod a+x builder
217+
./builder build -p ${{ env.PACKAGE_NAME }} --variant=localhost

Package.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ awsCCommonPlatformExcludes.append("source/arch/arm")
5757
#if !os(Windows)
5858
awsCCommonPlatformExcludes.append("source/windows")
5959
#endif
60+
#if !os(Linux)
61+
awsCCommonPlatformExcludes.append("source/linux")
62+
#endif
6063
let cSettingsCommon: [CSetting] = [
6164
.headerSearchPath("source/external/libcbor"),
6265
.define("DEBUG_BUILD", .when(configuration: .debug)),

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,41 @@ To format the code:
2525
swift format --in-place --recursive .
2626
```
2727

28+
## Testing
29+
30+
Running Localhost tests:
31+
Localhost tests are run using mock servers from aws-c-http. Use the following instructions from root to run the localhost.
32+
Tests that use localhost within swift: HTTPTests, HTTP2ClientConnectionTests
33+
34+
```sh
35+
cd aws-common-runtime/aws-c-http/tests/mockserver
36+
# install dependencies
37+
python -m pip install h11 h2 trio
38+
# for http/1.1 server
39+
HTTP_PORT=8091 HTTPS_PORT=8092 python h11mock_server.py
40+
# for http/2 non tls server
41+
python h2non_tls_server.py
42+
# for http/2 tls server.
43+
python h2tls_mock_server.py
44+
# enable localhost env variable for tests to detect localhost server.
45+
export AWS_CRT_LOCALHOST=true
46+
```
47+
48+
We use 4 different ports for running mock servers:
49+
- 3280 and 3443 for HTTP/2 (without TLS and with TLS respectively)
50+
- 8091 and 8092 for HTTP/1.1 (without TLS and with TLS respectively)
51+
52+
To use different ports for HTTP/1.1 server, initialize with different values for HTTP_PORT and HTTPS_PORT.
53+
Also, change the following lines in HTTPTests.swift.
54+
```swift
55+
var httpPort: Int {
56+
ProcessInfo.processInfo.environment["AWS_CRT_LOCALHOST"] != nil ? 8091 : 80
57+
}
58+
var httpsPort: Int {
59+
ProcessInfo.processInfo.environment["AWS_CRT_LOCALHOST"] != nil ? 8092 : 443
60+
}
61+
```
62+
2863
### Contributor's Guide
2964
**Required Reading:**
3065
- [Development Guide](docs/dev_guide.md)

Test/AwsCommonRuntimeKitTests/XCBaseTestCase.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ extension XCTestCase {
6969
#endif
7070
}
7171

72+
// Look into README to run localhost server tests and how to startup the local server.
73+
func skipIfLocalhostUnavailable() throws {
74+
guard let _ = ProcessInfo.processInfo.environment["AWS_CRT_LOCALHOST"] else {
75+
throw XCTSkip("Localhost server has not been started.")
76+
}
77+
}
78+
7279
func awaitExpectationResult(_ expectations: [XCTestExpectation], _ timeout: TimeInterval = 5)
7380
async -> XCTWaiter.Result
7481
{
@@ -89,6 +96,7 @@ extension XCTestCase {
8996
wait(for: expectations, timeout: timeout)
9097
#endif
9198
}
99+
92100
func skipIfPlatformDoesntSupportTLS() throws {
93101
// Skipped for secitem support as the unit tests requires enetitlement setup to have acces to
94102
// the data protection keychain.

Test/AwsCommonRuntimeKitTests/http/HTTP2ClientConnectionTests.swift

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,23 @@ import XCTest
77
class HTTP2ClientConnectionTests: XCBaseTestCase {
88

99
let expectedVersion = HTTPVersion.version_2
10-
let host = "postman-echo.com"
10+
let host = "localhost"
11+
let port: Int = 3443
1112

1213
func testGetHTTP2RequestVersion() async throws {
14+
try skipIfLocalhostUnavailable()
15+
try? Logger.initialize(target: LogTarget.standardOutput, level: LogLevel.trace)
1316
let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(
14-
endpoint: host, alpnList: ["h2", "http/1.1"])
17+
endpoint: host, port: port, alpnList: ["h2", "http/1.1"])
1518
let connection = try await connectionManager.acquireConnection()
1619
XCTAssertEqual(connection.httpVersion, HTTPVersion.version_2)
1720
}
1821

1922
// Test that the binding works not the actual functionality. C part has tests for functionality
2023
func testHTTP2UpdateSetting() async throws {
24+
try skipIfLocalhostUnavailable()
2125
let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(
22-
endpoint: host, alpnList: ["h2", "http/1.1"])
26+
endpoint: host, port: port, alpnList: ["h2", "http/1.1"])
2327
let connection = try await connectionManager.acquireConnection()
2428
if let connection = connection as? HTTP2ClientConnection {
2529
try await connection.updateSetting(setting: HTTP2Settings(enablePush: false))
@@ -30,8 +34,9 @@ class HTTP2ClientConnectionTests: XCBaseTestCase {
3034

3135
// Test that the binding works not the actual functionality. C part has tests for functionality
3236
func testHTTP2UpdateSettingEmpty() async throws {
37+
try skipIfLocalhostUnavailable()
3338
let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(
34-
endpoint: host, alpnList: ["h2", "http/1.1"])
39+
endpoint: host, port: port, alpnList: ["h2", "http/1.1"])
3540
let connection = try await connectionManager.acquireConnection()
3641
if let connection = connection as? HTTP2ClientConnection {
3742
try await connection.updateSetting(setting: HTTP2Settings())
@@ -42,8 +47,9 @@ class HTTP2ClientConnectionTests: XCBaseTestCase {
4247

4348
// Test that the binding works not the actual functionality. C part has tests for functionality
4449
func testHTTP2SendPing() async throws {
50+
try skipIfLocalhostUnavailable()
4551
let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(
46-
endpoint: host, alpnList: ["h2", "http/1.1"])
52+
endpoint: host, port: port, alpnList: ["h2", "http/1.1"])
4753
let connection = try await connectionManager.acquireConnection()
4854
if let connection = connection as? HTTP2ClientConnection {
4955
var time = try await connection.sendPing()
@@ -57,8 +63,9 @@ class HTTP2ClientConnectionTests: XCBaseTestCase {
5763

5864
// Test that the binding works not the actual functionality. C part has tests for functionality
5965
func testHTTP2SendGoAway() async throws {
66+
try skipIfLocalhostUnavailable()
6067
let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(
61-
endpoint: host, alpnList: ["h2", "http/1.1"])
68+
endpoint: host, port: port, alpnList: ["h2", "http/1.1"])
6269
let connection = try await connectionManager.acquireConnection()
6370
if let connection = connection as? HTTP2ClientConnection {
6471
connection.sendGoAway(error: .internalError, allowMoreStreams: false)
@@ -68,12 +75,13 @@ class HTTP2ClientConnectionTests: XCBaseTestCase {
6875
}
6976

7077
func testGetHttpsRequest() async throws {
78+
try skipIfLocalhostUnavailable()
7179
let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(
72-
endpoint: host, alpnList: ["h2", "http/1.1"])
80+
endpoint: host, port: port, alpnList: ["h2", "http/1.1"])
7381
let response = try await HTTPClientTestFixture.sendHTTPRequest(
7482
method: "GET",
7583
endpoint: host,
76-
path: "/get",
84+
path: "/echo",
7785
connectionManager: connectionManager,
7886
expectedVersion: expectedVersion,
7987
requestVersion: .version_2)
@@ -91,12 +99,13 @@ class HTTP2ClientConnectionTests: XCBaseTestCase {
9199
}
92100

93101
func testGetHttpsRequestWithHTTP1_1Request() async throws {
102+
try skipIfLocalhostUnavailable()
94103
let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(
95-
endpoint: host, alpnList: ["h2", "http/1.1"])
104+
endpoint: host, port: port, alpnList: ["h2", "http/1.1"])
96105
let response = try await HTTPClientTestFixture.sendHTTPRequest(
97106
method: "GET",
98107
endpoint: host,
99-
path: "/get",
108+
path: "/echo",
100109
connectionManager: connectionManager,
101110
expectedVersion: expectedVersion,
102111
requestVersion: .version_1_1)

Test/AwsCommonRuntimeKitTests/http/HTTPClientTestFixture.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ class HTTPClientTestFixture: XCBaseTestCase {
136136
) async throws -> HTTPClientConnectionManager {
137137
let tlsContextOptions = TLSContextOptions()
138138
tlsContextOptions.setAlpnList(alpnList)
139+
tlsContextOptions.setVerifyPeer(false)
139140
let tlsContext = try TLSContext(options: tlsContextOptions, mode: .client)
140141
var tlsConnectionOptions = TLSConnectionOptions(context: tlsContext)
141142
tlsConnectionOptions.serverName = endpoint

0 commit comments

Comments
 (0)