/* * 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 GRPC import Logging import XCTest /// This should be used instead of `XCTestCase`. class GRPCTestCase: XCTestCase { /// Unless `GRPC_ALWAYS_LOG` is set, logs will only be printed if a test case fails. private static let alwaysLog = Bool( fromTruthLike: ProcessInfo.processInfo.environment["GRPC_ALWAYS_LOG"], defaultingTo: false ) private static let runTimeSensitiveTests = Bool( fromTruthLike: ProcessInfo.processInfo.environment["ENABLE_TIMING_TESTS"], defaultingTo: true ) override func setUp() { super.setUp() self.logFactory = CapturingLogHandlerFactory(printWhenCaptured: GRPCTestCase.alwaysLog) } override func tearDown() { let logs = self.capturedLogs() // The default source emitted by swift-log is the directory containing the '#file' in which the // log was emitted. It's meant to represent the system which emitted the log, typically the // module name. In most cases it's right but in a few places, i.e. where the source lives in // child directories below 'GRPC', it isn't. We'll use this as a sanity check. // // See also: https://github.com/apple/swift-log/issues/145 for log in logs { XCTAssertEqual(log.source, "GRPC", "Incorrect log source in \(log.file) on line \(log.line)") } // Only print logs when there's a failure and we're *not* always logging (when we are always // logging, logs will be printed as they're caught). if !GRPCTestCase.alwaysLog, (self.testRun.map { $0.totalFailureCount > 0 } ?? false) { self.printCapturedLogs(logs) } super.tearDown() } func runTimeSensitiveTests() -> Bool { let shouldRun = GRPCTestCase.runTimeSensitiveTests if !shouldRun { print("Skipping '\(self.name)' as ENABLE_TIMING_TESTS=false") } return shouldRun } private(set) var logFactory: CapturingLogHandlerFactory! /// A general-use logger. var logger: Logger { return Logger(label: "grpc", factory: self.logFactory.make) } /// A logger for clients to use. var clientLogger: Logger { // Label is ignored; we already have a handler. return Logger(label: "client", factory: self.logFactory.make) } /// A logger for servers to use. var serverLogger: Logger { // Label is ignored; we already have a handler. return Logger(label: "server", factory: self.logFactory.make) } /// The default client call options using `self.clientLogger`. var callOptionsWithLogger: CallOptions { return CallOptions(logger: self.clientLogger) } /// Returns all captured logs sorted by date. private func capturedLogs() -> [CapturedLog] { assert(self.logFactory != nil, "Missing call to super.setUp()") var logs = self.logFactory.clearCapturedLogs() logs.sort(by: { $0.date < $1.date }) return logs } /// Prints all captured logs. private func printCapturedLogs(_ logs: [CapturedLog]) { print("Test Case '\(self.name)' logs started") // The logs are already sorted by date. let formatter = CapturedLogFormatter() for log in logs { print(formatter.string(for: log)) } print("Test Case '\(self.name)' logs finished") } } extension Bool { fileprivate init(fromTruthLike value: String?, defaultingTo defaultValue: Bool) { switch value?.lowercased() { case "0", "false", "no": self = false case "1", "true", "yes": self = true default: self = defaultValue } } }