Improve documentation & test coverage (#29)

This commit is contained in:
Moritz Lang 2020-08-31 15:03:56 +02:00 committed by GitHub
parent 613b53e0a3
commit 9be28a8450
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 325 additions and 71 deletions

View File

@ -4,22 +4,23 @@ import PackageDescription
let package = Package(
name: "swift-baggage-context",
products: [
.library(name: "Baggage",
.library(
name: "Baggage",
targets: [
"Baggage"
"Baggage",
]
),
.library(name: "BaggageLogging",
.library(
name: "BaggageLogging",
targets: [
"BaggageLogging"
"BaggageLogging",
]
),
],
dependencies: [
.package(url: "https://github.com/apple/swift-log.git", from: "1.3.0")
.package(url: "https://github.com/apple/swift-log.git", from: "1.3.0"),
],
targets: [
.target(
name: "Baggage",
dependencies: []
@ -39,7 +40,7 @@ let package = Package(
.testTarget(
name: "BaggageTests",
dependencies: [
"Baggage"
"Baggage",
]
),
@ -47,7 +48,7 @@ let package = Package(
name: "BaggageLoggingTests",
dependencies: [
"Baggage",
"BaggageLogging"
"BaggageLogging",
]
),
@ -60,7 +61,7 @@ let package = Package(
"Baggage",
"BaggageLogging",
"BaggageBenchmarkTools",
]
]
),
.target(
name: "BaggageBenchmarkTools",

148
README.md
View File

@ -1,16 +1,24 @@
# Baggage Context
[![Swift 5.2](https://img.shields.io/badge/Swift-5.2-ED523F.svg?style=flat)](https://swift.org/download/)
[![Swift 5.1](https://img.shields.io/badge/Swift-5.1-ED523F.svg?style=flat)](https://swift.org/download/)
[![Swift 5.0](https://img.shields.io/badge/Swift-5.0-ED523F.svg?style=flat)](https://swift.org/download/)
[![CI](https://github.com/slashmo/gsoc-swift-baggage-context/workflows/CI/badge.svg)](https://github.com/slashmo/gsoc-swift-baggage-context/actions?query=workflow%3ACI)
`BaggageContext` is a minimal (zero-dependency) "context" library meant to "carry" baggage (metadata) for cross-cutting tools such as tracers.
It is purposefully not tied to any specific use-case (in the spirit of the [Tracing Plane paper](https://cs.brown.edu/~jcmace/papers/mace18universal.pdf)'s BaggageContext), however it should enable a vast majority of use cases cross-cutting tools need to support. Unlike mentioned in the paper, our `BaggageContext` does not implement its own serialization scheme (today).
`BaggageContext` is a minimal (zero-dependency) "context" library meant to "carry" baggage (metadata) for cross-cutting
tools such as tracers. It is purposefully not tied to any specific use-case (in the spirit of the
[Tracing Plane paper](https://cs.brown.edu/~jcmace/papers/mace18universal.pdf)'s BaggageContext). However, it should
enable a vast majority of use cases cross-cutting tools need to support. Unlike mentioned in the paper, our
`BaggageContext` does not implement its own serialization scheme (today).
See https://github.com/slashmo/gsoc-swift-tracing for actual instrument types and implementations which can be used to deploy various cross-cutting instruments all reusing the same baggage type. More information can be found in the [SSWG meeting notes](https://gist.github.com/ktoso/4d160232407e4d5835b5ba700c73de37#swift-baggage-context--distributed-tracing).
See https://github.com/slashmo/gsoc-swift-tracing for actual instrument types and implementations which can be used to
deploy various cross-cutting instruments all reusing the same baggage type. More information can be found in the
[SSWG meeting notes](https://gist.github.com/ktoso/4d160232407e4d5835b5ba700c73de37#swift-baggage-context--distributed-tracing).
## Installation
You can install the `BaggageContext` library through the Swift Package Manager. The library itself is called `Baggage`, so that's what you'd import in your Swift files.
You can install the `BaggageContext` library through the Swift Package Manager. The library itself is called `Baggage`,
so that's what you'd import in your Swift files.
```swift
dependencies: [
@ -22,14 +30,92 @@ dependencies: [
]
```
## Usage
`BaggageContext` is intended to be used in conjunction with the instrumentation of distributed systems. To make this
instrumentation work, all parties involved operate on the same `BaggageContext` type. These are the three common
parties, in no specific order, and guidance on how to use `BaggageContext`:
### End Users - explicit context passing
You'll likely interact with some API that takes a context. In most cases you already have a context at hand so you
should pass that along. If you're certain you don't have a context at hand, pass along an empty one after thinking about
why that's the case.
**TODO**: Document the reasoning behind `.background` & `.TODO` once merged ([#26](#26))
While this might seem like a burden to take on, this will allow you to immediately add instrumentation (e.g. tracing)
once your application grows. Let's say your profiling some troublesome performance regressions. You won't have the time
to go through the entire system to start passing contexts around.
> TL;DR: You should always pass around `BaggageContext`, so that you're ready for when you need it.
Once you are ready to instrument your application, you already have everything in place to get going. Instead of each
instrument operating on its own context type they'll be using the same `BaggageContext` that you're already passing
around to the various instrumentable libraries & frameworks you make use of, so you're free to mix & match any
compatible instrument(s) 🙌 Check out the [swift-tracing](https://github.com/slashmo/gsoc-swift-tracing) repository for
instructions on how to get up & running.
### Library & Framework Authors - passing context and instrumenting libraries
Developers creating frameworks/libraries (e.g. NIO, gRPC, AsyncHTTPClient, ...) which benefit from being instrumented
should adopt `BaggageContext` as part of their public API. AsyncHTTPClient for example might accept a context like this:
```swift
let context = BaggageContext()
client.get(url: "https://swift.org", context: context)
```
For more information on where to place this argument and how to name it, take a look at the
[Context-Passing Guidelines](#Context-Passing-Guidelines).
Generally speaking, frameworks and libraries should treat baggage as an _opaque container_ and simply thread it along
all asynchronous boundaries a call may have to go through. Libraries and frameworks should not attempt to reuse context
as a means of passing values that they need for "normal" operation.
At cross-cutting boundaries, e.g. right before sending an HTTP
request, they inject the `BaggageContext` into the HTTP headers, allowing context propagation. On the receiving side, an
HTTP server should extract the request headers into a `BaggageContext`. Injecting/extracting is part of the
`swift-tracing` libraries [and documented in its own repository](https://github.com/slashmo/gsoc-swift-tracing).
### Instrumentation Authors - defining, injecting and extracting baggage
When implementing instrumentation for cross-cutting tools, `BaggageContext` becomes the way you propagate metadata such
as trace ids. Because each instrument knows what values might be added to the `BaggageContext` they are the ones
creating `BaggageContextKey` types dictating the type of value associated with each key added to the context. To make
accessing values a bit more convenient, we encourage you to add computed properties to `BaggageContextProtocol`:
```swift
private enum TraceIDKey: BaggageContextKey {
typealias Value = String
}
extension BaggageContextProtocol {
var traceID: String? {
get {
return self[TraceIDKey.self]
}
set {
self[TraceIDKey.self] = newValue
}
}
}
var context = BaggageContext()
context.traceID = "4bf92f3577b34da6a3ce929d0e0e4736"
print(context.traceID ?? "new trace id")
```
## Context-Passing Guidelines
In order for context-passing to feel consistent and Swifty among all server-side (and not only) libraries and frameworks
aiming to adopt `BaggageContext` (or any of its uses, such as Distributed Tracing), we suggest the following set of guidelines:
For context-passing to feel consistent and Swifty among all server-side (and not only) libraries and frameworks
aiming to adopt `BaggageContext` (or any of its uses, such as Distributed Tracing), we suggest the following set of
guidelines:
### Argument naming/positioning
In order to propagate baggage through function calls (and asynchronous-boundaries it may often be necessary to pass it explicitly (unless wrapper APIs are provided which handle the propagation automatically).
Propagating baggage context through your system is to be done explicitly, meaning as a parameter in function calls,
following the "flow" of execution.
When passing baggage context explicitly we strongly suggest sticking to the following style guideline:
@ -38,16 +124,20 @@ When passing baggage context explicitly we strongly suggest sticking to the foll
2. Defaulted non-function parameters (e.g. `(mode: Mode = .default)`),
3. Required function parameters, including required trailing closures (e.g. `(onNext elementHandler: (Value) -> ())`),
4. Defaulted function parameters, including optional trailing closures (e.g. `(onComplete completionHandler: (Reason) -> ()) = { _ in }`).
- Baggage Context should be passed as: **the last parameter in the required non-function parameters group in a function declaration**.
- Baggage Context should be passed as **the last parameter in the required non-function parameters group in a function declaration**.
This way when reading the call side, users of these APIs can learn to "ignore" or "skim over" the context parameter and the method signature remains human-readable and “Swifty”.
This way when reading the call side, users of these APIs can learn to "ignore" or "skim over" the context parameter and
the method signature remains human-readable and “Swifty”.
Examples:
- `func request(_ url: URL,` **`context: BaggageContext`** `)`, which may be called as `httpClient.request(url, context: context)`
- `func handle(_ request: RequestObject,` **`context: BaggageContextCarrier`** `)`
- if a "framework context" exists and _carries_ the baggage context already, it is permitted to pass that context together with the baggage;
- it is _strongly recommended_ to store the baggage context as `baggage` property of `FrameworkContext` in such cases, in order to avoid the confusing spelling of `context.context`, and favoring the self-explanatory `context.baggage` spelling when the baggage is contained in a framework context object.
- if a "framework context" exists and _carries_ the baggage context already, it is permitted to pass that context
together with the baggage;
- it is _strongly recommended_ to store the baggage context as `baggage` property of `FrameworkContext` in such cases,
in order to avoid the confusing spelling of `context.context`, and favoring the self-explanatory `context.baggage`
spelling when the baggage is contained in a framework context object.
- `func receiveMessage(_ message: Message, context: FrameworkContext)`
- `func handle(element: Element,` **`context: BaggageContextCarrier`** `, settings: Settings? = nil)`
- before any defaulted non-function parameters
@ -58,29 +148,45 @@ Examples:
In case there are _multiple_ "framework-ish" parameters, such as passing a NIO `EventLoop` or similar, we suggest:
- `func perform(_ work: Work, for user: User,` _`frameworkThing: Thing, eventLoop: NIO.EventLoop,`_ **`context: BaggageContext`** `)`
- pass the baggage as **last** of such non-domain specific parameters as it will be _by far more_ omnipresent than any specific framework parameter - as it is expected that any framework should be accepting a context if it is able to do so. While not all libraries are necessarily going to be implemented using the same frameworks.
- pass the baggage as **last** of such non-domain specific parameters as it will be _by far more_ omnipresent than any
specific framework parameter - as it is expected that any framework should be accepting a context if it can do so.
While not all libraries are necessarily going to be implemented using the same frameworks.
We feel it is important to preserve Swift's human-readable nature of function definitions. In other words, we intend to keep the read-out-loud phrasing of methods to remain _"request that url (ignore reading out loud the context parameter)"_ rather than _"request (ignore this context parameter when reading) that url"_.
We feel it is important to preserve Swift's human-readable nature of function definitions. In other words, we intend to
keep the read-out-loud phrasing of methods to remain _"request that URL (ignore reading out loud the context parameter)"_
rather than _"request (ignore this context parameter when reading) that URL"_.
#### When to use what context type?
This library defines the following context (carrier) types:
- `struct BaggageContext` - which is the actual context object,
- `protocol BaggageContextCarrier` - which should be used whenever a library implements an API and does not necessarily care where it gets a `context` value from
- this pattern enables other frameworks to pass their `FrameworkContext`, like so: `get(context: MyFrameworkContext())` if they already have such context in scope (e.g. Vapor's `Request` object is a good example, or Lambda Runtime's `Lambda.Context`
- `protocol BaggageContextCarrier` - which should be used whenever a library implements an API and does not necessarily
care where it gets a `context` value from
- this pattern enables other frameworks to pass their `FrameworkContext`, like so:
`get(context: MyFrameworkContext())` if they already have such context in scope (e.g. Vapor's `Request` object is a
good example, or Lambda Runtime's `Lambda.Context`
- `protocol LoggingBaggageContextCarrier` - which in addition exposes a logger bound to the passed context
Finally, some frameworks will have APIs which accept the specific `MyFrameworkContext`, withing frameworks specifically a lot more frequently than libraries one would hope. It is important when designing APIs to keep in mind -- can this API work with any context, or is it always going to require _my framework context_, and erring on accepting the most general type possible.
Finally, some frameworks will have APIs which accept the specific `MyFrameworkContext`, withing frameworks specifically
a lot more frequently than libraries one would hope. It is important when designing APIs to keep in mind -- can this API
work with any context, or is it always going to require _my framework context_, and erring on accepting the most
general type possible.
#### Existing context argument
When adapting an existing library/framework to support `BaggageContext` and it already has a "framework context" which is expected to be passed through "everywhere", we suggest to follow these guidelines to adopting BaggageContext:
When adapting an existing library/framework to support `BaggageContext` and it already has a "framework context" which
is expected to be passed through "everywhere", we suggest to follow these guidelines for adopting BaggageContext:
1. Add a `BaggageContext` as a property called `baggage` to your own `context` type, so that the call side for your users becomes `context.baggage` (rather than the confusing `context.context`)
2. If you cannot or it would not make sense to carry baggage inside your framework's context object, pass (and accept (!)) the `BaggageContext` in your framework functions like follows:
- if they take no framework context, accept a `context: BaggageContext` which is the same guideline as for all other cases
- if they already _must_ take a context object and you are out of words (or your API already accepts your framework context as "context"), pass the baggage as **last** parameter (see above) yet call the parameter `baggage` to disambiguate your `context` object from the `baggage` context object.
1. Add a `BaggageContext` as a property called `baggage` to your own `context` type, so that the call side for your
users becomes `context.baggage` (rather than the confusing `context.context`)
2. If you cannot or it would not make sense to carry baggage inside your framework's context object,
pass (and accept (!)) the `BaggageContext` in your framework functions like follows:
- if they take no framework context, accept a `context: BaggageContext` which is the same guideline as for all other
cases
- if they already _must_ take a context object and you are out of words (or your API already accepts your framework
context as "context"), pass the baggage as **last** parameter (see above) yet call the parameter `baggage` to
disambiguate your `context` object from the `baggage` context object.
Examples:

View File

@ -22,13 +22,13 @@
/// typealias Value = String
/// }
///
/// var baggage = BaggageContext()
/// var context = BaggageContext()
/// // set a new value
/// baggage[TestIDKey.self] = "abc"
/// context[TestIDKey.self] = "abc"
/// // retrieve a stored value
/// baggage[TestIDKey.self] ?? "default"
/// context[TestIDKey.self] ?? "default"
/// // remove a stored value
/// baggage[TestIDKey.self] = nil
/// context[TestIDKey.self] = nil
///
/// ## Convenience extensions
///
@ -36,7 +36,7 @@
/// using the following pattern:
///
/// extension BaggageContextProtocol {
/// var testID: TestIDKey.Value {
/// var testID: TestIDKey.Value? {
/// get {
/// self[TestIDKey.self]
/// } set {
@ -45,37 +45,34 @@
/// }
/// }
public struct BaggageContext: BaggageContextProtocol {
private var _storage = [AnyBaggageContextKey: ValueContainer]()
private var _storage = [AnyBaggageContextKey: Any]()
/// Create an empty `BaggageContext`.
public init() {}
public subscript<Key: BaggageContextKey>(_ key: Key.Type) -> Key.Value? {
get {
return self._storage[AnyBaggageContextKey(key)]?.forceUnwrap(key)
guard let value = self._storage[AnyBaggageContextKey(key)] else { return nil }
// safe to force-cast as this subscript is the only way to set a value.
return (value as! Key.Value)
} set {
self._storage[AnyBaggageContextKey(key)] = newValue.map {
ValueContainer(value: $0)
}
self._storage[AnyBaggageContextKey(key)] = newValue
}
}
public func forEach(_ callback: (AnyBaggageContextKey, Any) -> Void) {
self._storage.forEach { key, container in
callback(key, container.value)
}
}
private struct ValueContainer {
let value: Any
func forceUnwrap<Key: BaggageContextKey>(_ key: Key.Type) -> Key.Value {
return self.value as! Key.Value
public func forEach(_ body: (AnyBaggageContextKey, Any) throws -> Void) rethrows {
try self._storage.forEach { key, value in
try body(key, value)
}
}
}
extension BaggageContext: CustomStringConvertible {
/// A context's description prints only keys of the contained values.
/// This is in order to prevent spilling a lot of detailed information of carried values accidentally.
///
/// `BaggageContext`s are not intended to be printed "raw" but rather inter-operate with tracing, logging and other systems,
/// which can use the `forEach` function providing access to its underlying values.
public var description: String {
return "\(type(of: self).self)(keys: \(self._storage.map { $0.key.name }))"
}
@ -88,7 +85,7 @@ public protocol BaggageContextProtocol {
/// using the following pattern:
///
/// extension BaggageContextProtocol {
/// var testID: TestIDKey.Value {
/// var testID: TestIDKey.Value? {
/// get {
/// self[TestIDKey.self]
/// } set {
@ -98,17 +95,30 @@ public protocol BaggageContextProtocol {
/// }
subscript<Key: BaggageContextKey>(_ key: Key.Type) -> Key.Value? { get set }
/// Iterates over the baggage context's contents invoking the callback one-by one.
/// Calls the given closure on each key/value pair in the `BaggageContext`.
///
/// - Parameter callback: invoked with the type erased key and value stored for the key in this baggage.
func forEach(_ callback: (AnyBaggageContextKey, Any) -> Void)
/// - Parameter body: A closure invoked with the type erased key and value stored for the key in this baggage.
func forEach(_ body: (AnyBaggageContextKey, Any) throws -> Void) rethrows
}
// ==== ------------------------------------------------------------------------
// MARK: Baggage keys
/// `BaggageContextKey`s are used as keys in a `BaggageContext`. Their associated type `Value` gurantees type-safety.
/// `BaggageContextKey`s are used as keys in a `BaggageContext`. Their associated type `Value` guarantees type-safety.
/// To give your `BaggageContextKey` an explicit name you may override the `name` property.
///
/// In general, `BaggageContextKey`s should be `internal` to the part of a system using it. It is strongly recommended to do
/// convenience extensions on `BaggageContextProtocol`, using the keys directly is considered an anti-pattern.
///
/// extension BaggageContextProtocol {
/// var testID: TestIDKey.Value? {
/// get {
/// self[TestIDKey.self]
/// } set {
/// self[TestIDKey.self] = newValue
/// }
/// }
/// }
public protocol BaggageContextKey {
/// The type of `Value` uniquely identified by this key.
associatedtype Value
@ -121,7 +131,9 @@ extension BaggageContextKey {
public static var name: String? { return nil }
}
/// A type-erased `BaggageContextKey` used when iterating through the `BaggageContext` using its `forEach` method.
public struct AnyBaggageContextKey {
/// The key's type represented erased to an `Any.Type`.
public let keyType: Any.Type
private let _name: String?
@ -132,7 +144,7 @@ public struct AnyBaggageContextKey {
return self._name ?? String(describing: self.keyType.self)
}
public init<Key>(_ keyType: Key.Type) where Key: BaggageContextKey {
init<Key>(_ keyType: Key.Type) where Key: BaggageContextKey {
self.keyType = keyType
self._name = keyType.name
}

View File

@ -36,8 +36,8 @@ extension BaggageContextCarrier {
}
}
public func forEach(_ callback: (AnyBaggageContextKey, Any) -> Void) {
self.baggage.forEach(callback)
public func forEach(_ body: (AnyBaggageContextKey, Any) throws -> Void) rethrows {
try self.baggage.forEach(body)
}
}

View File

@ -219,7 +219,7 @@ class ArgumentParser<U> {
) {
self.arguments.append(
Argument(name: name, help: help)
{ try self.parseArgument(name, property, defaultValue, parser) }
{ try self.parseArgument(name, property, defaultValue, parser) }
)
}

View File

@ -18,9 +18,12 @@ import Logging
// MARK: BaggageContext (as additional Logger.Metadata) LogHandler
/// Proxying log handler which adds `BaggageContext` as metadata when log events are to be emitted.
///
/// The values stored in the `BaggageContext` are merged with the existing metadata on the logger. If both contain values for the same key,
/// the `BaggageContext` values are preferred.
public struct BaggageMetadataLogHandler: LogHandler {
var underlying: Logger
let context: BaggageContext
private var underlying: Logger
private let context: BaggageContext
public init(logger underlying: Logger, context: BaggageContext) {
self.underlying = underlying

View File

@ -14,10 +14,10 @@
import Baggage
import Logging
/// A `BaggageContextLogging` purpose is to be adopted by frameworks which already provide a "FrameworkContext",
/// A `LoggingBaggageContextCarrier` purpose is to be adopted by frameworks which already provide a "FrameworkContext",
/// and to such frameworks to pass their context as `BaggageContextCarrier`.
public protocol LoggingBaggageContextCarrier: BaggageContextCarrier {
/// The logger associated with this carrier context.
/// The logger associated with this context carrier.
///
/// It should automatically populate the loggers metadata based on the `BaggageContext` associated with this context object.
///
@ -27,7 +27,18 @@ public protocol LoggingBaggageContextCarrier: BaggageContextCarrier {
/// SHOULD implement this logger by wrapping the "raw" logger associated with this context with the `logger.with(BaggageContext:)` function,
/// which efficiently handles the bridging of baggage to logging metadata values.
///
/// ### Example implementation
///
/// Writes to the `logger` metadata SHOULD NOT be reflected in the `baggage`,
/// however writes to the underlying `baggage` SHOULD be reflected in the `logger`.
///
/// struct MyFrameworkContext: LoggingBaggageContextCarrier {
/// var baggage = BaggageContext()
/// private let _logger: Logger
///
/// var logger: Logger {
/// return self._logger.with(context: self.baggage)
/// }
/// }
var logger: Logger { get }
}

View File

@ -27,6 +27,7 @@ extension LoggingBaggageContextCarrierTests {
return [
("test_ContextWithLogger_dumpBaggage", test_ContextWithLogger_dumpBaggage),
("test_ContextWithLogger_log_withBaggage", test_ContextWithLogger_log_withBaggage),
("test_ContextWithLogger_log_prefersBaggageContextOverExistingLoggerMetadata", test_ContextWithLogger_log_prefersBaggageContextOverExistingLoggerMetadata),
]
}
}

View File

@ -64,17 +64,34 @@ final class LoggingBaggageContextCarrierTests: XCTestCase {
"secondIDExplicitlyNamed": "value",
])
}
func test_ContextWithLogger_log_prefersBaggageContextOverExistingLoggerMetadata() {
let baggage = BaggageContext()
let logging = TestLogging()
var logger = Logger(label: "TheLogger", factory: { label in logging.make(label: label) })
logger[metadataKey: "secondIDExplicitlyNamed"] = "set on logger"
var context: LoggingBaggageContextCarrier = ExampleFrameworkContext(context: baggage, logger: logger)
context.secondTestID = "set on baggage"
context.logger.info("Hello")
logging.history.assertExist(level: .info, message: "Hello", metadata: [
"secondIDExplicitlyNamed": "set on baggage",
])
}
}
public struct ExampleFrameworkContext: LoggingBaggageContextCarrier {
public var baggage: BaggageContext
struct ExampleFrameworkContext: LoggingBaggageContextCarrier {
var baggage: BaggageContext
private var _logger: Logger
public var logger: Logger {
var logger: Logger {
return self._logger.with(context: self.baggage)
}
public init(context baggage: BaggageContext, logger: Logger) {
init(context baggage: BaggageContext, logger: Logger) {
self.baggage = baggage
self._logger = logger
}

View File

@ -194,7 +194,8 @@ extension History {
metadata: Logger.Metadata? = nil,
source: String? = nil,
file: StaticString = #file,
line: UInt = #line) {
line: UInt = #line)
{
let source = source ?? Logger.currentModule(filePath: "\(file)")
let entry = self.find(level: level, message: message, metadata: metadata, source: source)
XCTAssertNotNil(
@ -214,7 +215,8 @@ extension History {
metadata: Logger.Metadata? = nil,
source: String? = nil,
file: StaticString = #file,
line: UInt = #line) {
line: UInt = #line)
{
let source = source ?? Logger.currentModule(filePath: "\(file)")
let entry = self.find(level: level, message: message, metadata: metadata, source: source)
XCTAssertNil(

View File

@ -0,0 +1,34 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Baggage Context open source project
//
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// BaggageContextCarrierTests+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension BaggageContextCarrierTests {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (BaggageContextCarrierTests) -> () throws -> Void)] {
return [
("testBaggageContextSubscript", testBaggageContextSubscript),
("testBaggageContextForEach", testBaggageContextForEach),
("testBaggageContextCarriesItself", testBaggageContextCarriesItself),
]
}
}

View File

@ -0,0 +1,67 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Baggage Context open source project
//
// Copyright (c) 2020 Moritz Lang and the Swift Baggage Context project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
@testable import Baggage
import XCTest
final class BaggageContextCarrierTests: XCTestCase {
func testBaggageContextSubscript() {
var carrier = TestFrameworkContext()
// mutate baggage context through carrier
carrier[TestKey.self] = 42
XCTAssertEqual(carrier[TestKey.self], 42)
XCTAssertEqual(carrier.baggage[TestKey.self], 42)
// mutate baggage context directly
carrier.baggage[OtherKey.self] = "test"
XCTAssertEqual(carrier.baggage[OtherKey.self], "test")
XCTAssertEqual(carrier[OtherKey.self], "test")
}
func testBaggageContextForEach() {
var contents = [AnyBaggageContextKey: Any]()
var carrier = TestFrameworkContext()
carrier[TestKey.self] = 42
carrier[OtherKey.self] = "test"
carrier.forEach { key, value in
contents[key] = value
}
XCTAssertNotNil(contents[AnyBaggageContextKey(TestKey.self)])
XCTAssertEqual(contents[AnyBaggageContextKey(TestKey.self)] as? Int, 42)
XCTAssertNotNil(contents[AnyBaggageContextKey(OtherKey.self)])
XCTAssertEqual(contents[AnyBaggageContextKey(OtherKey.self)] as? String, "test")
}
func testBaggageContextCarriesItself() {
var context: BaggageContextCarrier = BaggageContext()
context.baggage[TestKey.self] = 42
XCTAssertEqual(context.baggage[TestKey.self], 42)
}
}
private struct TestFrameworkContext: BaggageContextCarrier {
var baggage = BaggageContext()
}
private enum TestKey: BaggageContextKey {
typealias Value = Int
}
private enum OtherKey: BaggageContextKey {
typealias Value = String
}

View File

@ -62,7 +62,6 @@ final class BaggageContextTests: XCTestCase {
// use contains instead of `XCTAssertEqual` because the order is non-predictable (Dictionary)
XCTAssert(description.contains("TestIDKey"))
XCTAssert(description.contains("ExplicitKeyName"))
print(description.reversed().starts(with: "])"))
}
}

View File

@ -33,6 +33,7 @@ class LinuxMainRunnerImpl: LinuxMainRunner {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
func run() {
XCTMain([
testCase(BaggageContextCarrierTests.allTests),
testCase(BaggageContextTests.allTests),
testCase(LoggingBaggageContextCarrierTests.allTests),
])