Skip to content

Commit

Permalink
Add a basic implementation of XCTIssue.
Browse files Browse the repository at this point in the history
This PR adds a basic implementation of `XCTIssue` and `record(_:)`. The implementation is not as complex as the one in the XCTest framework that ships in Xcode. The interface is equivalent.

Resolves #348.
  • Loading branch information
grynspan committed Oct 6, 2023
1 parent a61b37e commit 82d6aa2
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 0 deletions.
86 changes: 86 additions & 0 deletions Sources/XCTest/Public/XCTIssue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//

/// Encapsulates all data concerning a test failure or other issue.
public struct XCTIssue: Sendable {
/// Types of failures and other issues that can be reported for tests.
public enum IssueType: Int, Sendable {
/// Issue raised by a failed XCTAssert or related API.
case assertionFailure = 0

/// Issue raised by the test throwing an error in Swift. This could also occur if an Objective C test is implemented in the form `- (BOOL)testFoo:(NSError **)outError` and returns NO with a non-nil out error.
case thrownError = 1

/// Code in the test throws and does not catch an exception, Objective C, C++, or other.
case uncaughtException = 2

/// One of the XCTestCase(measure:) family of APIs detected a performance regression.
case performanceRegression = 3

/// One of the framework APIs failed internally. For example, XCUIApplication was unable to launch or terminate an app or XCUIElementQuery was unable to complete a query.
case system = 4

/// Issue raised when XCTExpectFailure is used but no matching issue is recorded.
case unmatchedExpectedFailure = 5

/// A short human-readable description of this issue type.
fileprivate var stringRepresentation: String {
switch self {
case .assertionFailure:
"Assertion Failure"
case .thrownError:
"Thrown Error"
case .uncaughtException:
"Uncaught Exception"
case .performanceRegression:
"Performance Regression"
case .system:
"System Error"
case .warning:
"Warning"
case .unmatchedExpectedFailure:
"Unmatched Expected Failure"
}
}
}

/// The type of the issue.
public var type: IssueType

/// A concise description of the issue, expected to be free of transient data and suitable for use in test run
/// summaries and for aggregation of results across multiple test runs.
public var compactDescription: String

/// A detailed description of the issue designed to help diagnose the issue. May include transient data such as
/// numbers, object identifiers, timestamps, etc.
public var detailedDescription: String?

/// Error associated with the issue.
public var associatedError: (any Error)?

public init(
type: IssueType,
compactDescription: String,
detailedDescription: String? = nil,
associatedError: (any Error)? = nil
) {
self.type = type
self.compactDescription = compactDescription
self.detailedDescription = detailedDescription
self.associatedError = associatedError
}
}

extension XCTIssue: Equatable, Hashable {}

extension XCTIssue: CustomStringConvertible {
public var description: String {
"\(type.stringRepresentation): \(compactDescription)"
}
}
17 changes: 17 additions & 0 deletions Sources/XCTest/Public/XCTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,23 @@ open class XCTestCase: XCTest {
expected: false)
}

/// Records a failure or other issue in the execution of the test and is used by all test assertions.
///
/// - Parameters:
/// - issue: A value with all details related to the issue.
///
/// Overrides of this method should call `super` unless they wish to suppress the issue.
/// `super` can be invoked with a different `issue` object.
open func record(_ issue: XCTIssue, file: StaticString = #file, line: Int = #line) {
let isExpected = issue.type == .assertionFailure || issue.type == .performanceRegression
recordFailure(
withDescription: issue.compactDescription,
inFile: file,
atLine: line,
expected: isExpected
)
}

/// Setup method called before the invocation of any test method in the
/// class.
open class func setUp() {}
Expand Down
15 changes: 15 additions & 0 deletions Tests/Functional/ErrorHandling/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class ErrorHandling: XCTestCase {
("test_shouldReportCorrectTypeOnUnwrapFailure", test_shouldReportCorrectTypeOnUnwrapFailure),
("test_shouldReportCustomFileLineLocation", test_shouldReportCustomFileLineLocation),
("test_shouldReportFailureNotOnMainThread", test_shouldReportFailureNotOnMainThread),
("test_canRecordIssue", test_canRecordIssue),
]
}()

Expand Down Expand Up @@ -290,6 +291,20 @@ class ErrorHandling: XCTestCase {

semaphore.wait()
}

// CHECK: Test Case 'ErrorHandling.test_canRecordIssue' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+10]]: error: ErrorHandling.test_canRecordIssue : Performance Regression: ABC 123
// CHECK: Test Case 'ErrorHandling.test_canRecordIssue' failed \(\d+\.\d+ seconds\)
func test_canRecordIssue() {
struct MyError: Error {}
let issue = XCTIssue(
type: .performanceRegression,
compactDescription: "ABC 123",
detailedDescription: "DEF 987",
associatedError: MyError()
)
record(issue)
}
}

// CHECK: Test Suite 'ErrorHandling' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
Expand Down
4 changes: 4 additions & 0 deletions XCTest.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
DA9D441B1D920A3500108768 /* XCTestCase+Asynchronous.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9D44161D920A3500108768 /* XCTestCase+Asynchronous.swift */; };
DA9D441C1D920A3500108768 /* XCTestExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9D44171D920A3500108768 /* XCTestExpectation.swift */; };
E1495C80224276A600CDEB7D /* IgnoredErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1495C7F224276A600CDEB7D /* IgnoredErrors.swift */; };
FCF8F7132AD069A80074FC0F /* XCTIssue.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCF8F7122AD069A80074FC0F /* XCTIssue.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -91,6 +92,7 @@
DA9D44171D920A3500108768 /* XCTestExpectation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestExpectation.swift; sourceTree = "<group>"; };
E1495C7F224276A600CDEB7D /* IgnoredErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IgnoredErrors.swift; sourceTree = "<group>"; };
EA3E74BB1BF2B6D500635A73 /* build_script.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = build_script.py; sourceTree = "<group>"; };
FCF8F7122AD069A80074FC0F /* XCTIssue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTIssue.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -152,6 +154,7 @@
DA9D44131D920A3500108768 /* Asynchronous */,
AE2FE0EA1CFE86DB003EF0D7 /* XCAbstractTest.swift */,
AE2FE0ED1CFE86DB003EF0D7 /* XCTAssert.swift */,
FCF8F7122AD069A80074FC0F /* XCTIssue.swift */,
1748FE3623AFD2D80014DB87 /* XCTSkip.swift */,
AE2FE0EE1CFE86DB003EF0D7 /* XCTestCase.swift */,
AE2FE0F01CFE86DB003EF0D7 /* XCTestCase+Performance.swift */,
Expand Down Expand Up @@ -361,6 +364,7 @@
172FF8A72117B74D0059CBC5 /* XCTNSNotificationExpectation.swift in Sources */,
AE2FE11A1CFE86E6003EF0D7 /* WallClockTimeMetric.swift in Sources */,
AE2FE1191CFE86E6003EF0D7 /* TestFiltering.swift in Sources */,
FCF8F7132AD069A80074FC0F /* XCTIssue.swift in Sources */,
AE2FE1031CFE86DB003EF0D7 /* XCTestErrors.swift in Sources */,
AE2FE1091CFE86DB003EF0D7 /* XCTestSuite.swift in Sources */,
AE2FE11C1CFE86E6003EF0D7 /* XCTestCaseSuite.swift in Sources */,
Expand Down

0 comments on commit 82d6aa2

Please sign in to comment.