| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- /*
- * Copyright 2019, gRPC Authors All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- import Foundation
- import GRPC
- import NIO
- import NIOSSL
- import GRPCInteroperabilityTests
- import Commander
- enum InteroperabilityTestError: LocalizedError {
- case testNotFound(String)
- case testFailed(Error)
- var errorDescription: String? {
- switch self {
- case .testNotFound(let name):
- return "No test named '\(name)' was found"
- case .testFailed(let error):
- return "Test failed with error: \(error)"
- }
- }
- }
- /// Runs the test instance using the given connection.
- ///
- /// Success or failure is indicated by the lack or presence of thrown errors, respectively.
- ///
- /// - Parameters:
- /// - instance: `InteroperabilityTest` instance to run.
- /// - name: the name of the test, use for logging only.
- /// - connection: client connection to use for running the test.
- /// - Throws: `InteroperabilityTestError` if the test fails.
- func runTest(_ instance: InteroperabilityTest, name: String, connection: ClientConnection) throws {
- do {
- print("Running '\(name)' ... ", terminator: "")
- try instance.run(using: connection)
- print("PASSED")
- } catch {
- print("FAILED")
- throw InteroperabilityTestError.testFailed(error)
- }
- }
- /// Creates a new `InteroperabilityTest` instance with the given name, or throws an
- /// `InteroperabilityTestError` if no test matches the given name. Implemented test names can be
- /// found by running the `list_tests` target.
- func makeRunnableTest(name: String) throws -> InteroperabilityTest {
- guard let testCase = InteroperabilityTestCase(rawValue: name) else {
- throw InteroperabilityTestError.testNotFound(name)
- }
- return testCase.makeTest()
- }
- /// Runs the given block and exits with code 1 if the block throws an error.
- ///
- /// The "Commander" CLI elides thrown errors in favour of its own. This function is intended purely
- /// to work around this limitation by printing any errors before exiting.
- func exitOnThrow<T>(block: () throws -> T) -> T {
- do {
- return try block()
- } catch {
- print(error)
- exit(1)
- }
- }
- // MARK: - Command line options and "main".
- let serverHostOption = Option(
- "server_host",
- default: "localhost",
- description: "The server host to connect to.")
- let serverPortOption = Option(
- "server_port",
- default: 8080,
- description: "The server port to connect to.")
- let testCaseOption = Option(
- "test_case",
- default: InteroperabilityTestCase.emptyUnary.name,
- description: "The name of the test case to execute.")
- /// The spec requires a string (as opposed to having a flag) to indicate whether TLS is enabled or
- /// disabled.
- let useTLSOption = Option(
- "use_tls",
- default: "false",
- description: "Whether to use an encrypted or plaintext connection (true|false).") { value in
- let lowercased = value.lowercased()
- switch lowercased {
- case "true", "false":
- return lowercased
- default:
- throw ArgumentError.invalidType(value: value, type: "boolean", argument: "use_tls")
- }
- }
- let portOption = Option(
- "port",
- default: 8080,
- description: "The port to listen on.")
- let group = Group { group in
- group.command(
- "run_test",
- serverHostOption,
- serverPortOption,
- useTLSOption,
- testCaseOption,
- description: "Run a single test. See 'list_tests' for available test names."
- ) { host, port, useTLS, testCaseName in
- let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
- defer {
- try? eventLoopGroup.syncShutdownGracefully()
- }
- exitOnThrow {
- let instance = try makeRunnableTest(name: testCaseName)
- let connection = try makeInteroperabilityTestClientConnection(
- host: host,
- port: port,
- eventLoopGroup: eventLoopGroup,
- useTLS: useTLS == "true")
- try runTest(instance, name: testCaseName, connection: connection)
- }
- }
- group.command(
- "start_server",
- portOption,
- useTLSOption,
- description: "Starts the test server."
- ) { port, useTls in
- let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
- defer {
- try? eventLoopGroup.syncShutdownGracefully()
- }
- let server = exitOnThrow {
- return try makeInteroperabilityTestServer(
- host: "localhost",
- port: port,
- eventLoopGroup: eventLoopGroup,
- useTLS: useTls == "true")
- }
- server.map { $0.channel.localAddress?.port }.whenSuccess {
- print("Server started on port \($0!)")
- }
- // We never call close; run until we get killed.
- try server.flatMap { $0.onClose }.wait()
- }
- group.command(
- "list_tests",
- description: "List available test case names."
- ) {
- InteroperabilityTestCase.allCases.forEach {
- print($0.name)
- }
- }
- }
- group.run()
|