init
This commit is contained in:
commit
b6ff6e900c
|
@ -0,0 +1,23 @@
|
||||||
|
# This workflow will build a Swift project
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift
|
||||||
|
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build
|
||||||
|
run: swift build -v
|
||||||
|
- name: Run tests
|
||||||
|
run: swift test -v
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
name: docc
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
concurrency:
|
||||||
|
group: "pages"
|
||||||
|
cancel-in-progress: true
|
||||||
|
jobs:
|
||||||
|
pages:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: macos-12
|
||||||
|
steps:
|
||||||
|
- name: git checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: docbuild
|
||||||
|
run: |
|
||||||
|
xcodebuild docbuild -scheme Test \
|
||||||
|
-derivedDataPath /tmp/docbuild \
|
||||||
|
-destination 'generic/platform=iOS';
|
||||||
|
$(xcrun --find docc) process-archive \
|
||||||
|
transform-for-static-hosting /tmp/docbuild/Build/Products/Debug-iphoneos/Test.doccarchive \
|
||||||
|
--hosting-base-path Test \
|
||||||
|
--output-path docs;
|
||||||
|
echo "<script>window.location.href += \"/documentation/test\"</script>" > docs/index.html;
|
||||||
|
- name: artifacts
|
||||||
|
uses: actions/upload-pages-artifact@v1
|
||||||
|
with:
|
||||||
|
path: 'docs'
|
||||||
|
- name: deploy
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v1
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
/*.xcodeproj
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm/config/registries.json
|
||||||
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
|
.netrc
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "fork",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/0xLeif/Fork",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "7682617694979adcf1f8b08d1507c865cad81ae4",
|
||||||
|
"version" : "1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "o",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/0xOpenBytes/o",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "3e83362434c82f318a8d72e0d3b0786ffb3ba640",
|
||||||
|
"version" : "2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "plugin",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/0xLeif/Plugin",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "bd5449fc40720589881bacb4ed7373ddfcb2d214",
|
||||||
|
"version" : "2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "scribe",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/0xLeif/Scribe",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "79881803f6942346941421de516fefe690ea4db3",
|
||||||
|
"version" : "1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-log",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-log",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "32e8d724467f8fe623624570367e3d50c5638e46",
|
||||||
|
"version" : "1.5.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "t",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/0xOpenBytes/t",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "c111675ac4d84af23d2d9b65bffaf1829c376986",
|
||||||
|
"version" : "1.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 2
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
// swift-tools-version: 5.7
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "Test",
|
||||||
|
platforms: [
|
||||||
|
.iOS(.v14),
|
||||||
|
.macOS(.v11),
|
||||||
|
.watchOS(.v7),
|
||||||
|
.tvOS(.v14)
|
||||||
|
],
|
||||||
|
products: [
|
||||||
|
.library(
|
||||||
|
name: "Test",
|
||||||
|
targets: ["Test"]),
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
.package(url: "https://github.com/0xOpenBytes/t", from: "1.0.0"),
|
||||||
|
.package(url: "https://github.com/0xLeif/Scribe", from: "1.3.0"),
|
||||||
|
.package(url: "https://github.com/0xLeif/Plugin", from: "2.0.0")
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.target(
|
||||||
|
name: "Test",
|
||||||
|
dependencies: [
|
||||||
|
"t",
|
||||||
|
"Scribe",
|
||||||
|
"Plugin"
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "TestTests",
|
||||||
|
dependencies: ["Test"]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Test
|
||||||
|
|
||||||
|
*Expect and assert*
|
||||||
|
|
||||||
|
## What is `Test`?
|
||||||
|
|
||||||
|
`Test` is a simple testing function that allows you to create test suites with multiple steps, expectations, and assertions. You can specify a name for the test suite, an instance of the `Tester` class to be used for running the tests, and a closure that contains the test steps.
|
||||||
|
|
||||||
|
## Where can `Test` be used?
|
||||||
|
|
||||||
|
`Test` can be used anywhere! `Test` can be used to test quickly inside a function to make sure something is working as expected. It is especially useful when you want to test a complex piece of code with multiple steps and assertions. `Test` can even be used for your unit tests!
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Simple test with assertions
|
||||||
|
|
||||||
|
```swift
|
||||||
|
try await Test(named: "Test someMethod()") { tester in
|
||||||
|
try tester.assert(SomeClass.someMethod())
|
||||||
|
try await tester.assertNoThrows(try await SomeClass.someOtherMethod())
|
||||||
|
try tester.assert(SomeClass.someBooleanValue, isEqualTo: false)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test with expectations
|
||||||
|
|
||||||
|
```swift
|
||||||
|
try Test(named: "Test someMethod() with expectations") { tester in
|
||||||
|
try Expect("First step should succeed") {
|
||||||
|
try SomeClass.someMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
tester.logInfo("Just finished first step")
|
||||||
|
|
||||||
|
try Expect("Second step should succeed") {
|
||||||
|
try SomeClass.someOtherMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
tester.logWarning("Something unexpected happened during the second step")
|
||||||
|
|
||||||
|
try Expect("Final assertion should be true") {
|
||||||
|
try tester.assert(SomeClass.someBooleanValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
tester.logSuccess("All steps and assertions passed!")
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,29 @@
|
||||||
|
import t
|
||||||
|
|
||||||
|
/**
|
||||||
|
The `Expect` function is used to define a test expectation within a `Test` function. The expectation closure contains the code that should succeed and produce the expected result.
|
||||||
|
|
||||||
|
- Parameters:
|
||||||
|
- description: A string describing the expectation.
|
||||||
|
- expectation: A closure that contains the code to test the expectation.
|
||||||
|
*/
|
||||||
|
public func Expect(
|
||||||
|
_ description: String? = nil,
|
||||||
|
expectation: @escaping () throws -> Void
|
||||||
|
) throws {
|
||||||
|
try t.expect(description, expectation: expectation)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The `Expect` function is used to define a test expectation within a `Test` function. The expectation closure contains the code that should succeed and produce the expected result.
|
||||||
|
|
||||||
|
- Parameters:
|
||||||
|
- description: A string describing the expectation.
|
||||||
|
- expectation: A closure that contains the code to test the expectation.
|
||||||
|
*/
|
||||||
|
public func Expect(
|
||||||
|
_ description: String? = nil,
|
||||||
|
expectation: @escaping () async throws -> Void
|
||||||
|
) async throws {
|
||||||
|
try await t.expect(description, expectation: expectation)
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
import t
|
||||||
|
import Scribe
|
||||||
|
|
||||||
|
/**
|
||||||
|
The `TestConfiguration` enum provides a global configuration for tests.
|
||||||
|
|
||||||
|
Use this enum to customize the logger and scribe for the test framework.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
TestConfiguration.logger = { print($0) }
|
||||||
|
TestConfiguration.scribe = Scribe(label: "Custom Scribe")
|
||||||
|
|
||||||
|
*/
|
||||||
|
public enum TestConfiguration {
|
||||||
|
/**
|
||||||
|
A closure that receives a string and logs it. This is used by the framework to log messages when tests run.
|
||||||
|
|
||||||
|
By default, the logger is set to `t.logger` from the `t` library.
|
||||||
|
|
||||||
|
- Note: To change the logger, set `TestConfiguration.logger`.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
TestConfiguration.logger = { print($0) }
|
||||||
|
*/
|
||||||
|
public static var logger: (String) -> Void {
|
||||||
|
get { t.logger }
|
||||||
|
set { t.logger = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The scribe used by the test framework.
|
||||||
|
|
||||||
|
By default, the scribe is set to a new instance of `Scribe` with a label of "Test.Scribe".
|
||||||
|
|
||||||
|
- Note: To change the scribe, set `TestConfiguration.scribe`.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
TestConfiguration.scribe = Scribe(label: "Custom Scribe")
|
||||||
|
|
||||||
|
*/
|
||||||
|
public static var scribe: Scribe = Scribe(label: "Test.Scribe")
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Plugin
|
||||||
|
|
||||||
|
/**
|
||||||
|
The `TestPlugin` protocol defines a contract for plugins that can be used to extend the behavior of the `Tester` class.
|
||||||
|
|
||||||
|
`TestPlugin` extends the `ImmutablePlugin` protocol, which allows the plugin to be treated as an immutable object.
|
||||||
|
|
||||||
|
`TestPlugin` is used for conveince when creating a `Plugin` for a `Tester`.
|
||||||
|
*/
|
||||||
|
public protocol TestPlugin: ImmutablePlugin { }
|
|
@ -0,0 +1,105 @@
|
||||||
|
import t
|
||||||
|
|
||||||
|
/**
|
||||||
|
The `Test` function is used to create a test suite with multiple steps, expectations, and assertions.
|
||||||
|
|
||||||
|
- Parameters:
|
||||||
|
- named: An optional name for the test suite.
|
||||||
|
- tester: An instance of the `Tester` class to be used for running the test suite.
|
||||||
|
- operation: A closure that contains the test steps, expectations, and assertions.
|
||||||
|
- lineNumber: The line number where the `Test` function is called. Defaults to the line number where the function is called.
|
||||||
|
- functionName: The name of the function where the `Test` function is called. Defaults to the name of the function where the function is called.
|
||||||
|
- fileName: The name of the file where the `Test` function is called. Defaults to the name of the file where the function is called.
|
||||||
|
|
||||||
|
- Throws: A `TestError` if the test suite fails.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
try Test(named: "My Test Suite") { tester in
|
||||||
|
try Expect("First step should succeed") {
|
||||||
|
try SomeClass.someMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
try Expect("Second step should succeed") {
|
||||||
|
try SomeClass.someOtherMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
try Expect("Final assertion should be true") {
|
||||||
|
try tester.assert(SomeClass.someBooleanValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
public func Test(
|
||||||
|
named name: String? = nil,
|
||||||
|
tester: Tester = Tester(),
|
||||||
|
operation: (Tester) throws -> Void,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) throws {
|
||||||
|
let result = t.suite(named: name) {
|
||||||
|
try operation(tester)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard result == true else {
|
||||||
|
let testName = name.map { "\($0) Test" } ?? "Test"
|
||||||
|
throw TestError(
|
||||||
|
description: "\(testName) failed.",
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The `Test` function is used to create a test suite with multiple steps, expectations, and assertions.
|
||||||
|
|
||||||
|
- Parameters:
|
||||||
|
- named: An optional name for the test suite.
|
||||||
|
- tester: An instance of the `Tester` class to be used for running the test suite.
|
||||||
|
- operation: A closure that contains the test steps, expectations, and assertions.
|
||||||
|
- lineNumber: The line number where the `Test` function is called. Defaults to the line number where the function is called.
|
||||||
|
- functionName: The name of the function where the `Test` function is called. Defaults to the name of the function where the function is called.
|
||||||
|
- fileName: The name of the file where the `Test` function is called. Defaults to the name of the file where the function is called.
|
||||||
|
|
||||||
|
- Throws: A `TestError` if the test suite fails.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
try await Test(named: "My Test Suite") { tester in
|
||||||
|
try Expect("First step should succeed") {
|
||||||
|
try SomeClass.someMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
try await Expect("Second step should succeed") {
|
||||||
|
try await SomeClass.someOtherMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
try Expect("Final assertion should be true") {
|
||||||
|
try tester.assert(SomeClass.someBooleanValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
public func Test(
|
||||||
|
named name: String? = nil,
|
||||||
|
tester: Tester = Tester(),
|
||||||
|
operation: (Tester) async throws -> Void,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) async throws {
|
||||||
|
let result = await t.suite(named: name) {
|
||||||
|
try await operation(tester)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard result == true else {
|
||||||
|
let testName = name.map { "\($0) Test" } ?? "Test"
|
||||||
|
throw TestError(
|
||||||
|
description: "\(testName) failed.",
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import t
|
||||||
|
|
||||||
|
/**
|
||||||
|
The `TestError` function creates an instance of a test error with a given description and location information.
|
||||||
|
|
||||||
|
- Parameters:
|
||||||
|
- description: A string describing the error.
|
||||||
|
- lineNumber: The line number where the error occurred. Defaults to the line number where the function is called.
|
||||||
|
- functionName: The name of the function where the error occurred. Defaults to the name of the function where the function is called.
|
||||||
|
- fileName: The name of the file where the error occurred. Defaults to the name of the file where the function is called.
|
||||||
|
|
||||||
|
- Returns: An instance of `Error` representing the test error.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
throw TestError(description: "Test failed.")
|
||||||
|
*/
|
||||||
|
public func TestError(
|
||||||
|
description: String,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) -> Error {
|
||||||
|
t.error(
|
||||||
|
description: description,
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
import t
|
||||||
|
|
||||||
|
/**
|
||||||
|
The Tested function is used to test a single operation and return its output.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
description: A description of the operation being tested.
|
||||||
|
operation: A closure that contains the operation to be tested.
|
||||||
|
|
||||||
|
Returns: The output of the tested operation.
|
||||||
|
|
||||||
|
Throws: Any errors that occur during the operation.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
let result = try Tested("Tested operation") {
|
||||||
|
return SomeClass.someMethod()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
public func Tested<Output>(
|
||||||
|
_ description: String,
|
||||||
|
operation: @escaping () throws -> Output
|
||||||
|
) throws -> Output {
|
||||||
|
try t.tested(description, operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The Tested function is used to test a single operation and return its output.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
operation: A closure that contains the operation to be tested.
|
||||||
|
|
||||||
|
Returns: The output of the tested operation.
|
||||||
|
|
||||||
|
Throws: Any errors that occur during the operation.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
let result = try Tested("Tested operation") {
|
||||||
|
return SomeClass.someMethod()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
public func Tested<Output>(
|
||||||
|
operation: @escaping () throws -> Output
|
||||||
|
) throws -> Output {
|
||||||
|
try t.tested(operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The Tested function is used to test a single operation and return its output.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
description: A description of the operation being tested.
|
||||||
|
operation: A closure that contains the operation to be tested.
|
||||||
|
|
||||||
|
Returns: The output of the tested operation.
|
||||||
|
|
||||||
|
Throws: Any errors that occur during the operation.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
let result = try await Tested("Tested operation") {
|
||||||
|
try await SomeClass.someMethod()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
public func Tested<Output>(
|
||||||
|
_ description: String,
|
||||||
|
operation: @escaping () async throws -> Output
|
||||||
|
) async throws -> Output {
|
||||||
|
try await t.tested(description, operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The Tested function is used to test a single operation and return its output.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
operation: A closure that contains the operation to be tested.
|
||||||
|
|
||||||
|
Returns: The output of the tested operation.
|
||||||
|
|
||||||
|
Throws: Any errors that occur during the operation.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
let result = try await Tested("Tested operation") {
|
||||||
|
try await SomeClass.someMethod()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
public func Tested<Output>(
|
||||||
|
operation: @escaping () async throws -> Output
|
||||||
|
) async throws -> Output {
|
||||||
|
try await t.tested(operation)
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
import Scribe
|
||||||
|
|
||||||
|
extension Tester {
|
||||||
|
public typealias PluginPayload = Scribe.PluginPayload
|
||||||
|
public typealias Message = Scribe.Message
|
||||||
|
public typealias Level = Scribe.Level
|
||||||
|
public typealias Metadata = Scribe.Metadata
|
||||||
|
|
||||||
|
/// Logs a message with the trace log level.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: The message to be logged.
|
||||||
|
/// - metadata: Optional metadata to be included with the log message.
|
||||||
|
/// - source: Optional source information to be included with the log message.
|
||||||
|
/// - Returns: A task representing the log operation.
|
||||||
|
@discardableResult
|
||||||
|
public func logTrace(
|
||||||
|
_ message: Message,
|
||||||
|
metadata: Metadata? = nil,
|
||||||
|
source: String? = nil
|
||||||
|
) -> Task<Void, Error> {
|
||||||
|
scribe.trace(
|
||||||
|
message,
|
||||||
|
metadata: metadata,
|
||||||
|
source: source
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs a message with the debug log level.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: The message to be logged.
|
||||||
|
/// - metadata: Optional metadata to be included with the log message.
|
||||||
|
/// - source: Optional source information to be included with the log message.
|
||||||
|
/// - Returns: A task representing the log operation.
|
||||||
|
@discardableResult
|
||||||
|
public func logDebug(
|
||||||
|
_ message: Message,
|
||||||
|
metadata: Metadata? = nil,
|
||||||
|
source: String? = nil
|
||||||
|
) -> Task<Void, Error> {
|
||||||
|
scribe.debug(
|
||||||
|
message,
|
||||||
|
metadata: metadata,
|
||||||
|
source: source
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs a message with the info log level.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: The message to be logged.
|
||||||
|
/// - metadata: Optional metadata to be included with the log message.
|
||||||
|
/// - source: Optional source information to be included with the log message.
|
||||||
|
/// - Returns: A task representing the log operation.
|
||||||
|
@discardableResult
|
||||||
|
public func logInfo(
|
||||||
|
_ message: Message,
|
||||||
|
metadata: Metadata? = nil,
|
||||||
|
source: String? = nil
|
||||||
|
) -> Task<Void, Error> {
|
||||||
|
scribe.info(
|
||||||
|
message,
|
||||||
|
metadata: metadata,
|
||||||
|
source: source
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs a message with the notice log level.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: The message to be logged.
|
||||||
|
/// - metadata: Optional metadata to be included with the log message.
|
||||||
|
/// - source: Optional source information to be included with the log message.
|
||||||
|
/// - Returns: A task representing the log operation.
|
||||||
|
@discardableResult
|
||||||
|
public func logNotice(
|
||||||
|
_ message: Message,
|
||||||
|
metadata: Metadata? = nil,
|
||||||
|
source: String? = nil
|
||||||
|
) -> Task<Void, Error> {
|
||||||
|
scribe.notice(
|
||||||
|
message,
|
||||||
|
metadata: metadata,
|
||||||
|
source: source
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs a message with the warning log level.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: The message to be logged.
|
||||||
|
/// - metadata: Optional metadata to be included with the log message.
|
||||||
|
/// - source: Optional source information to be included with the log message.
|
||||||
|
/// - Returns: A task representing the log operation.
|
||||||
|
@discardableResult
|
||||||
|
public func logWarning(
|
||||||
|
_ message: Message,
|
||||||
|
metadata: Metadata? = nil,
|
||||||
|
source: String? = nil
|
||||||
|
) -> Task<Void, Error> {
|
||||||
|
scribe.warning(
|
||||||
|
message,
|
||||||
|
metadata: metadata,
|
||||||
|
source: source
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs a message with the error log level.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: The message to be logged.
|
||||||
|
/// - metadata: Optional metadata to be included with the log message.
|
||||||
|
/// - source: Optional source information to be included with the log message.
|
||||||
|
/// - Returns: A task representing the log operation.
|
||||||
|
@discardableResult
|
||||||
|
public func logError(
|
||||||
|
_ message: Message,
|
||||||
|
metadata: Metadata? = nil,
|
||||||
|
source: String? = nil
|
||||||
|
) -> Task<Void, Error> {
|
||||||
|
scribe.error(
|
||||||
|
message,
|
||||||
|
metadata: metadata,
|
||||||
|
source: source
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs a message with the critical log level.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: The message to be logged.
|
||||||
|
/// - metadata: Optional metadata to be included with the log message.
|
||||||
|
/// - source: Optional source information to be included with the log message.
|
||||||
|
/// - Returns: A task representing the log operation.
|
||||||
|
@discardableResult
|
||||||
|
public func logCritical(
|
||||||
|
_ message: Message,
|
||||||
|
metadata: Metadata? = nil,
|
||||||
|
source: String? = nil
|
||||||
|
) -> Task<Void, Error> {
|
||||||
|
scribe.critical(
|
||||||
|
message,
|
||||||
|
metadata: metadata,
|
||||||
|
source: source
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
import t
|
||||||
|
|
||||||
|
extension Tester {
|
||||||
|
/// Assert that the condition is true.
|
||||||
|
public func assert(
|
||||||
|
_ condition: Bool,
|
||||||
|
_ message: String? = nil,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) throws {
|
||||||
|
try t.assert(
|
||||||
|
condition,
|
||||||
|
message,
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that the condition is false.
|
||||||
|
public func assert(
|
||||||
|
notTrue condition: Bool,
|
||||||
|
_ message: String? = nil,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) throws {
|
||||||
|
try t.assert(
|
||||||
|
notTrue: condition,
|
||||||
|
message,
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that the first value is equal to the second.
|
||||||
|
public func assert<Value: Equatable>(
|
||||||
|
_ firstValue: Value,
|
||||||
|
isEqualTo secondValue: Value,
|
||||||
|
_ message: String? = nil,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) throws {
|
||||||
|
try t.assert(
|
||||||
|
firstValue,
|
||||||
|
isEqualTo: secondValue,
|
||||||
|
message,
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that the first value is not equal to the second.
|
||||||
|
public func assert<Value: Equatable>(
|
||||||
|
_ firstValue: Value,
|
||||||
|
isNotEqualTo secondValue: Value,
|
||||||
|
_ message: String? = nil,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) throws {
|
||||||
|
try t.assert(
|
||||||
|
firstValue,
|
||||||
|
isNotEqualTo: secondValue,
|
||||||
|
message,
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that the value is not nil.
|
||||||
|
public func assert<Value>(
|
||||||
|
isNotNil value: Value?,
|
||||||
|
_ message: String? = nil,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) throws {
|
||||||
|
try t.assert(
|
||||||
|
isNotNil: value,
|
||||||
|
message,
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that the value is nil.
|
||||||
|
public func assert<Value>(
|
||||||
|
isNil value: Value?,
|
||||||
|
_ message: String? = nil,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) throws {
|
||||||
|
try t.assert(
|
||||||
|
isNil: value,
|
||||||
|
message,
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that the closure should throw
|
||||||
|
public func assertThrows<Value>(
|
||||||
|
_ message: String? = nil,
|
||||||
|
operation: @escaping @autoclosure () throws -> Value,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) throws {
|
||||||
|
try t.assertThrows(
|
||||||
|
operation,
|
||||||
|
message: message,
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that the closure should not throw
|
||||||
|
public func assertNoThrows<Value>(
|
||||||
|
_ message: String? = nil,
|
||||||
|
operation: @escaping @autoclosure () throws -> Value,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) throws {
|
||||||
|
try t.assertNoThrows(
|
||||||
|
operation,
|
||||||
|
message: message,
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@discardableResult
|
||||||
|
public func unwrap<Value>(
|
||||||
|
_ value: Value?,
|
||||||
|
message: String? = nil,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) throws -> Value {
|
||||||
|
try t.unwrap(
|
||||||
|
value,
|
||||||
|
message: message,
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
|
||||||
|
extension Tester {
|
||||||
|
/// Assert that the closure should throw
|
||||||
|
public func assertThrows<Value>(
|
||||||
|
_ message: String? = nil,
|
||||||
|
operation: @escaping @autoclosure () async throws -> Value,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) async throws {
|
||||||
|
try await t.assertThrows(
|
||||||
|
operation,
|
||||||
|
message: message,
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that the closure should not throw
|
||||||
|
public func assertNoThrows<Value>(
|
||||||
|
_ message: String? = nil,
|
||||||
|
operation: @escaping @autoclosure () async throws -> Value,
|
||||||
|
lineNumber: Int = #line,
|
||||||
|
functionName: String = #function,
|
||||||
|
fileName: String = #file
|
||||||
|
) async throws {
|
||||||
|
try await t.assertNoThrows(
|
||||||
|
operation,
|
||||||
|
message: message,
|
||||||
|
lineNumber: lineNumber,
|
||||||
|
functionName: functionName,
|
||||||
|
fileName: fileName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,29 @@
|
||||||
|
import t
|
||||||
|
import Scribe
|
||||||
|
import Plugin
|
||||||
|
|
||||||
|
/**
|
||||||
|
The `Tester` class is responsible for managing the execution of a test suite.
|
||||||
|
|
||||||
|
A `Tester` instance contains a `Scribe` object, which is used to log test results and a collection of plugins that are used to perform custom operations during the test run.
|
||||||
|
*/
|
||||||
|
open class Tester: ImmutablePluginable {
|
||||||
|
/// The `Scribe` object used to log test results.
|
||||||
|
open var scribe: Scribe
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializes a new `Tester` object.
|
||||||
|
|
||||||
|
- Parameters:
|
||||||
|
- scribe: The `Scribe` object used to log test results. Defaults to `TestConfiguration.scribe`.
|
||||||
|
- plugins: An array of plugins to use during the test run. Defaults to an empty array.
|
||||||
|
*/
|
||||||
|
public init(
|
||||||
|
scribe: Scribe = TestConfiguration.scribe,
|
||||||
|
plugins: [any Plugin] = []
|
||||||
|
) {
|
||||||
|
self.scribe = scribe
|
||||||
|
|
||||||
|
super.init(plugins: plugins)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import XCTest
|
||||||
|
@testable import Test
|
||||||
|
|
||||||
|
final class TestTests: XCTestCase {
|
||||||
|
override class func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
|
||||||
|
TestConfiguration.logger = { _ in }
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExample() async throws {
|
||||||
|
struct ExampleTestPlugin: TestPlugin {
|
||||||
|
func handle(value: Bool) async throws {
|
||||||
|
guard value else {
|
||||||
|
throw TestError(description: "False value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try await Test(
|
||||||
|
named: "Development",
|
||||||
|
tester: Tester(
|
||||||
|
plugins: [
|
||||||
|
ExampleTestPlugin()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
operation: { tester in
|
||||||
|
try await Expect(description) {
|
||||||
|
tester.logInfo("Info!")
|
||||||
|
|
||||||
|
try tester.assert(0, isEqualTo: 0)
|
||||||
|
|
||||||
|
try await tester.handle(value: true)
|
||||||
|
|
||||||
|
tester.logError("There should have been an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue