From f10598ed348caa722e70f2d6579216145fb49eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 3 Dec 2024 12:20:00 +0100 Subject: [PATCH] Add a no-op shim for os.OSSignpost --- .../NoOpSignposterShim.swift | 135 ++++++++++++++++++ .../Utility/NoOpSignposterShimTests.swift | 55 +++++++ 2 files changed, 190 insertions(+) create mode 100644 Sources/SwiftDocC/Utility/FoundationExtensions/NoOpSignposterShim.swift create mode 100644 Tests/SwiftDocCTests/Utility/NoOpSignposterShimTests.swift diff --git a/Sources/SwiftDocC/Utility/FoundationExtensions/NoOpSignposterShim.swift b/Sources/SwiftDocC/Utility/FoundationExtensions/NoOpSignposterShim.swift new file mode 100644 index 0000000000..ce02bcb917 --- /dev/null +++ b/Sources/SwiftDocC/Utility/FoundationExtensions/NoOpSignposterShim.swift @@ -0,0 +1,135 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2024 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +/// A shim for `OSSignposter` that does nothing, except for running the passed interval task. +/// +/// This type allows calling code to avoid using `#if canImport(os)` throughout the implementation. +package struct NoOpSignposterShim : @unchecked Sendable { + package init() {} + + package var isEnabled: Bool { false } + + package struct ID { + static var exclusive = ID() + } + package func makeSignpostID() -> ID { ID() } + + package struct IntervalState {} + + // Without messages + + package func beginInterval(_ name: StaticString, id: ID = .exclusive) -> IntervalState { + IntervalState() + } + package func endInterval(_ name: StaticString, _ state: IntervalState) {} + + package func withIntervalSignpost(_ name: StaticString, id: ID = .exclusive, around task: () throws -> T) rethrows -> T { + try task() + } + + package func emitEvent(_ name: StaticString, id: ID = .exclusive) {} + + // With messages + + package func beginInterval(_ name: StaticString, id: ID = .exclusive, _ message: NoOpLogMessage) -> IntervalState { + self.beginInterval(name, id: id) + } + package func endInterval(_ name: StaticString, _ state: IntervalState, _ message: NoOpLogMessage) {} + + package func withIntervalSignpost(_ name: StaticString, id: ID = .exclusive, _ message: NoOpLogMessage, around task: () throws -> T) rethrows -> T { + try self.withIntervalSignpost(name, id: id, around: task) + } + + package func emitEvent(_ name: StaticString, id: ID = .exclusive, _ message: NoOpLogMessage) {} +} + +// MARK: Message + +package struct NoOpLogMessage: ExpressibleByStringInterpolation, ExpressibleByStringLiteral { + package let interpolation: Interpolation + + package init(stringInterpolation: Interpolation) { + interpolation = stringInterpolation + } + package init(stringLiteral value: String) { + self.init(stringInterpolation: .init(literalCapacity: 0, interpolationCount: 0)) + } + + package struct Interpolation: StringInterpolationProtocol { + package init(literalCapacity: Int, interpolationCount: Int) {} + + package mutating func appendLiteral(_ literal: String) {} + + // Append string + package mutating func appendInterpolation(_ argumentString: @autoclosure @escaping () -> String, align: NoOpLogStringAlignment = .none, privacy: NoOpLogPrivacy = .auto) {} + package mutating func appendInterpolation(_ value: @autoclosure @escaping () -> some CustomStringConvertible, align: NoOpLogStringAlignment = .none, privacy: NoOpLogPrivacy = .auto) {} + + // Append booleans + package mutating func appendInterpolation(_ boolean: @autoclosure @escaping () -> Bool, format: NoOpLogBoolFormat = .truth, privacy: NoOpLogPrivacy = .auto) {} + + // Append integers + package mutating func appendInterpolation(_ number: @autoclosure @escaping () -> some FixedWidthInteger, format: NoOpLogIntegerFormatting = .decimal, align: NoOpLogStringAlignment = .none, privacy: NoOpLogPrivacy = .auto) {} + + // Append float/double + package mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Float, format: NoOpLogFloatFormatting = .fixed, align: NoOpLogStringAlignment = .none, privacy: NoOpLogPrivacy = .auto) {} + package mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Double, format: NoOpLogFloatFormatting = .fixed, align: NoOpLogStringAlignment = .none, privacy: NoOpLogPrivacy = .auto) {} + + // Add more interpolations here as needed + } + + package struct NoOpLogStringAlignment { + package static var none: Self { .init() } + package static func right(columns: @autoclosure @escaping () -> Int) -> Self { .init() } + package static func left(columns: @autoclosure @escaping () -> Int) -> Self { .init() } + } + + package struct NoOpLogPrivacy { + package enum Mask { + case hash, none + } + package static var `public`: Self { .init() } + package static var `private`: Self { .init() } + package static var sensitive: Self { .init() } + package static var auto: Self { .init() } + package static func `private`(mask: Mask) -> Self { .init() } + package static func sensitive(mask: Mask) -> Self { .init() } + package static func auto(mask: Mask) -> Self { .init() } + } + + package enum NoOpLogBoolFormat { + case truth, answer + } + + public struct NoOpLogIntegerFormatting { + package static var decimal: Self { .init() } + package static var hex: Self { .init() } + package static var octal: Self { .init() } + package static func decimal(explicitPositiveSign: Bool = false) -> Self { .init() } + package static func decimal(explicitPositiveSign: Bool = false, minDigits: @autoclosure @escaping () -> Int) -> Self { .init() } + package static func hex(explicitPositiveSign: Bool = false, includePrefix: Bool = false, uppercase: Bool = false) -> Self { .init() } + package static func hex(explicitPositiveSign: Bool = false, includePrefix: Bool = false, uppercase: Bool = false, minDigits: @autoclosure @escaping () -> Int) -> Self { .init() } + package static func octal(explicitPositiveSign: Bool = false, includePrefix: Bool = false, uppercase: Bool = false) -> Self { .init() } + package static func octal(explicitPositiveSign: Bool = false, includePrefix: Bool = false, uppercase: Bool = false, minDigits: @autoclosure @escaping () -> Int) -> Self { .init() } + } + + package struct NoOpLogFloatFormatting { + package static var fixed: Self { .init() } + package static var hex: Self { .init() } + package static var exponential: Self { .init() } + package static var hybrid: Self { .init() } + package static func fixed(precision: @autoclosure @escaping () -> Int, explicitPositiveSign: Bool = false, uppercase: Bool = false) -> Self { .init() } + package static func fixed(explicitPositiveSign: Bool = false, uppercase: Bool = false) -> Self { .init() } + package static func hex(explicitPositiveSign: Bool = false, uppercase: Bool = false) -> Self { .init() } + package static func exponential(precision: @autoclosure @escaping () -> Int, explicitPositiveSign: Bool = false, uppercase: Bool = false) -> Self { .init() } + package static func exponential(explicitPositiveSign: Bool = false, uppercase: Bool = false) -> Self { .init() } + package static func hybrid(precision: @autoclosure @escaping () -> Int, explicitPositiveSign: Bool = false, uppercase: Bool = false) -> Self { .init() } + package static func hybrid(explicitPositiveSign: Bool = false, uppercase: Bool = false) -> Self { .init() } + } +} diff --git a/Tests/SwiftDocCTests/Utility/NoOpSignposterShimTests.swift b/Tests/SwiftDocCTests/Utility/NoOpSignposterShimTests.swift new file mode 100644 index 0000000000..facb69e04e --- /dev/null +++ b/Tests/SwiftDocCTests/Utility/NoOpSignposterShimTests.swift @@ -0,0 +1,55 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2024 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import Foundation +import SwiftDocC +import XCTest + +final class NoOpSignposterShimTests: XCTestCase { + func testRunsIntervalVoidWork() { + let signposter = NoOpSignposterShim() + + let didPerformWork = expectation(description: "Did perform work") + signposter.withIntervalSignpost("Something") { + didPerformWork.fulfill() + } + + wait(for: [didPerformWork], timeout: 10.0) + } + + func testReturnsIntervalWorkResult() { + let signposter = NoOpSignposterShim() + + let didPerformWork = expectation(description: "Did perform work") + let number = signposter.withIntervalSignpost("Something") { + didPerformWork.fulfill() + return 7 + } + XCTAssertEqual(number, 7) + + wait(for: [didPerformWork], timeout: 10.0) + } + + func testCanAcceptMessageInputs() { + let signposter = NoOpSignposterShim() + + let handle = signposter.beginInterval("Some interval", "Some message") + signposter.endInterval("Some interval", handle, "Another message") + + signposter.emitEvent("Some event", id: signposter.makeSignpostID(), "Some static string") + signposter.emitEvent("Some event", "Some formatted bool \(true, format: .answer)") + signposter.emitEvent("Some event", "Some formatted integer \(12, format: .decimal)") + signposter.emitEvent("Some event", "Some formatted float \(7.0, format: .exponential)") + signposter.emitEvent("Some event", "Some sensitive string \("my secret", privacy: .sensitive(mask: .hash))") + signposter.emitEvent("Some event", "Some non-secret string \("my secret", privacy: .public)") + + signposter.emitEvent("Some event", "Some aligned values \(12, align: .right(columns: 5)) \("some text", align: .left(columns: 10))") + } +}