diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 0000000..d0184b8 --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,37 @@ +name: Actions + +on: + pull_request: + branches: + - main + +jobs: + + bb_checks: + name: BB Checks + uses: BinaryBirds/github-workflows/.github/workflows/extra_soundness.yml@main + with: + local_swift_dependencies_check_enabled : true + + swiftlang_checks: + name: Swiftlang Checks + uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main + with: + license_header_check_project_name: "Testify" + format_check_enabled : true + broken_symlink_check_enabled : true + unacceptable_language_check_enabled : true + api_breakage_check_enabled : false + docs_check_enabled : false + license_header_check_enabled : false + shell_check_enabled : false + yamllint_check_enabled : false + python_lint_check_enabled : false + + swiftlang_tests: + name: Swiftlang Tests + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + with: + enable_windows_checks : false + linux_build_command: "swift test --parallel" + linux_exclude_swift_versions: "[{\"swift_version\": \"5.8\"}, {\"swift_version\": \"5.10\"}, {\"swift_version\": \"nightly\"}, {\"swift_version\": \"nightly-main\"}, {\"swift_version\": \"nightly-6.0\"}]" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 40b1b4f..dd3cae7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /.build /Packages /*.xcodeproj +.vscode/launch.json diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..58d21f0 --- /dev/null +++ b/.swift-format @@ -0,0 +1,64 @@ +{ + "version": 1, + "lineLength": 80, + "maximumBlankLines": 1, + "fileScopedDeclarationPrivacy": { + "accessLevel": "private" + }, + "tabWidth": 4, + "indentation": { + "spaces": 4 + }, + "indentConditionalCompilationBlocks": false, + "indentSwitchCaseLabels": false, + "lineBreakAroundMultilineExpressionChainComponents": true, + "lineBreakBeforeControlFlowKeywords": true, + "lineBreakBeforeEachArgument": true, + "lineBreakBeforeEachGenericRequirement": true, + "prioritizeKeepingFunctionOutputTogether": false, + "respectsExistingLineBreaks": true, + "spacesAroundRangeFormationOperators": false, + "multiElementCollectionTrailingCommas": true, + "rules": { + "AllPublicDeclarationsHaveDocumentation": false, + "AlwaysUseLiteralForEmptyCollectionInit": true, + "AlwaysUseLowerCamelCase": true, + "AmbiguousTrailingClosureOverload": true, + "BeginDocumentationCommentWithOneLineSummary": true, + "DoNotUseSemicolons": true, + "DontRepeatTypeInStaticProperties": true, + "FileScopedDeclarationPrivacy": true, + "FullyIndirectEnum": true, + "GroupNumericLiterals": true, + "IdentifiersMustBeASCII": true, + "NeverForceUnwrap": false, + "NeverUseForceTry": true, + "NeverUseImplicitlyUnwrappedOptionals": true, + "NoAccessLevelOnExtensionDeclaration": true, + "NoAssignmentInExpressions": true, + "NoBlockComments": true, + "NoCasesWithOnlyFallthrough": true, + "NoEmptyTrailingClosureParentheses": true, + "NoLabelsInCasePatterns": true, + "NoLeadingUnderscores": true, + "NoParensAroundConditions": true, + "NoPlaygroundLiterals": true, + "NoVoidReturnOnFunctionSignature": true, + "OmitExplicitReturns": true, + "OneCasePerLine": true, + "OneVariableDeclarationPerLine": true, + "OnlyOneTrailingClosureArgument": true, + "OrderedImports": true, + "ReplaceForEachWithForLoop": true, + "ReturnVoidInsteadOfEmptyTuple": true, + "TypeNamesShouldBeCapitalized": true, + "UseEarlyExits": true, + "UseLetInEveryBoundCaseVariable": true, + "UseShorthandTypeNames": true, + "UseSingleLinePropertyGetter": true, + "UseSynthesizedInitializer": true, + "UseTripleSlashForDocumentationComments": true, + "UseWhereClausesInForLoops": true, + "ValidateDocumentationComments": true + } +} diff --git a/.swiftformatignore b/.swiftformatignore new file mode 100644 index 0000000..90955ec --- /dev/null +++ b/.swiftformatignore @@ -0,0 +1,2 @@ +Package.swift +Package@swift-5.9.swift \ No newline at end of file diff --git a/.unacceptablelanguageignore b/.unacceptablelanguageignore new file mode 100644 index 0000000..045b5ba --- /dev/null +++ b/.unacceptablelanguageignore @@ -0,0 +1,10 @@ +Tests/TestifySDKTests/Resources/Assets/json/Kitura.json +Tests/TestifySDKTests/Resources/Assets/json/Kitura-coverage.json +Tests/TestifySDKTests/Resources/Assets/json/PromiseFailure.json +Tests/TestifySDKTests/Resources/Assets/json/Shell-coverage.json +Tests/TestifySDKTests/Resources/Assets/json/ShellOutFailure.json +Tests/TestifySDKTests/Resources/Assets/tests/Kitura-coverage.tests +Tests/TestifySDKTests/Resources/Assets/tests/PromiseFailure.tests +Tests/TestifySDKTests/Resources/Assets/tests/ShellOutFailure.tests +Tests/TestifySDKTests/Resources/Assets/xml/Kitura.xml +Tests/TestifySDKTests/Resources/Assets/tests/Kitura.tests \ No newline at end of file diff --git a/Makefile b/Makefile index 00749f2..356a5ae 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,19 @@ +SHELL=/bin/bash + +baseUrl = https://raw.githubusercontent.com/BinaryBirds/github-workflows/refs/heads/main/scripts + +test: + swift test --parallel + +language: + curl -s $(baseUrl)/check-unacceptable-language.sh | bash + +lint: + curl -s $(baseUrl)/run-swift-format.sh | bash + +format: + curl -s $(baseUrl)/run-swift-format.sh | bash -s -- --fix + release: swift package update && swift build -c release diff --git a/README.md b/README.md index 7603e3a..c4fa7b7 100755 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ In your project folder run: You can just use the [Swift Package Manager](https://theswiftdev.com/2017/11/09/swift-package-manager-tutorial/) as usual: ```swift -.package(url: "https://github.com/binarybirds/testify", from: "1.1.2"), +.package(url: "https://github.com/binarybirds/testify", from: "1.2.0"), ``` ⚠️ Don't forget to add "Testify" to your target as a dependency! diff --git a/Sources/TestifySDK/Codable/RawTestResultDecoder.swift b/Sources/TestifySDK/Codable/RawTestResultDecoder.swift index e89d338..3d654b4 100644 --- a/Sources/TestifySDK/Codable/RawTestResultDecoder.swift +++ b/Sources/TestifySDK/Codable/RawTestResultDecoder.swift @@ -1,5 +1,5 @@ // -// RawTestResultParser.swift +// RawTestResultDecoder.swift // Testify // // Created by Tibor Bodecs on 2023. 02. 12.. @@ -7,46 +7,10 @@ import Foundation -private extension String { - - func match(_ pattern: String) -> String? { - guard let regex = try? NSRegularExpression(pattern: pattern) - else { return nil } - let matches = regex.matches( - in: self, - range: .init(location: 0, length: count) - ) - guard let match = matches.first, - let range = Range(match.range, in: self) - else { return nil } - return String(self[range]) - } - - var matchedTestName: String? { - guard let match = match("(\\'.+\\')") else { - return nil - } - return String(match.dropFirst().dropLast()) - } - - var matchedDate: String? { - match("(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}\\:\\d{2}\\:\\d{2}\\.\\d{3})") - } - - var matchedSeconds: String? { - match("(\\d+\\.\\d+)") - } - - var matchedUnexpected: String? { - guard let dropFirst = match("\\((\\d+)")?.dropFirst() else { return nil } - return String(dropFirst) - } -} - public struct RawTestResultDecoder { - + private let dateFormatter: DateFormatter - + public init() { self.dateFormatter = .init() self.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" @@ -57,47 +21,32 @@ public struct RawTestResultDecoder { var currentCaseName: String? var testCaseOutput: String = "" var gatherTestCaseOutput = false - + let lines = input.split(separator: "\n").map({ String($0) }) for (index, line) in lines.enumerated() { // start or end test suite if line.contains("Test Suite") { - if line.contains("started") { - guard let name = line.matchedTestName else { continue } - - guard let matchedDate = line.matchedDate, - let date = dateFormatter.date(from: matchedDate) - else { continue } - - suites.append( - TestSuite( - name: name, - startDate: date, - endDate: date, - unexpected: 0, - outcome: .failure - ) - ) - continue; - } - else { + guard line.contains("started") else { guard var suite = suites.last else { continue } suites = Array(suites.dropLast()) - - suite.outcome = line.contains("passed") ? .success : .failure + + suite.outcome = + line.contains("passed") ? .success : .failure if let matchedDate = line.matchedDate, - let date = dateFormatter.date(from: matchedDate) { + let date = dateFormatter.date(from: matchedDate) + { suite.endDate = date } - if index+1 < lines.count { - let nextLine = lines[index+1] + if index + 1 < lines.count { + let nextLine = lines[index + 1] if nextLine.contains("Executed"), - let matchedUnexpected = nextLine.matchedUnexpected, - let unexpected = UInt(matchedUnexpected) { + let matchedUnexpected = nextLine.matchedUnexpected, + let unexpected = UInt(matchedUnexpected) + { suite.unexpected = unexpected } } - + if suites.isEmpty { suites.append(suite) } @@ -108,39 +57,64 @@ public struct RawTestResultDecoder { suites.append(parentSuite) } } - continue; + continue } + guard let name = line.matchedTestName else { continue } + + guard let matchedDate = line.matchedDate, + let date = dateFormatter.date(from: matchedDate) + else { continue } + + suites.append( + TestSuite( + name: name, + startDate: date, + endDate: date, + unexpected: 0, + outcome: .failure + ) + ) + continue } if line.contains("Test Case") { - if line.contains("started") { - testCaseOutput = "" - gatherTestCaseOutput = true - currentCaseName = line.matchedTestName - continue; - } - else { + guard line.contains("started") else { gatherTestCaseOutput = false guard var suite = suites.last else { continue } suites = Array(suites.dropLast()) - let outcome: Outcome = line.contains("passed") || line.contains("measured") ? .success : .failure - + let outcome: Outcome = + line.contains("passed") || line.contains("measured") + ? .success : .failure + var failureInfo: FailureInfo? = nil if outcome == .failure, !testCaseOutput.isEmpty { let outputSplit = testCaseOutput.split(separator: ":") let file = String(outputSplit[0]) - if outputSplit.count >= 2, let line = Int(outputSplit[1]) { - let reason = String(outputSplit.dropFirst(4) - .joined(separator: ":") - .trimmingCharacters(in: CharacterSet(charactersIn: "-").union(.whitespaces))) - failureInfo = FailureInfo(file: file, line: line, reason: reason) + if outputSplit.count >= 2, + let line = Int(outputSplit[1]) + { + let reason = String( + outputSplit.dropFirst(4) + .joined(separator: ":") + .trimmingCharacters( + in: CharacterSet(charactersIn: "-") + .union(.whitespaces) + ) + ) + failureInfo = FailureInfo( + file: file, + line: line, + reason: reason + ) } } - - if let _currentCaseName = currentCaseName, + + if let notNullcurrentCaseName = currentCaseName, let matchedSeconds = line.matchedSeconds, - let duration = TimeInterval(matchedSeconds) { - - let caseName = _currentCaseName.dropFirst(2).dropLast() + let duration = TimeInterval(matchedSeconds) + { + + let caseName = notNullcurrentCaseName.dropFirst(2) + .dropLast() let firstSplit = caseName.split(separator: ".") let secondSplit = firstSplit[1].split(separator: " ") let testCase = TestCase( @@ -155,8 +129,12 @@ public struct RawTestResultDecoder { } suites.append(suite) currentCaseName = nil - continue; + continue } + testCaseOutput = "" + gatherTestCaseOutput = true + currentCaseName = line.matchedTestName + continue } if gatherTestCaseOutput { testCaseOutput += line @@ -165,3 +143,41 @@ public struct RawTestResultDecoder { return suites.first } } + +extension String { + + fileprivate func match(_ pattern: String) -> String? { + guard let regex = try? NSRegularExpression(pattern: pattern) + else { return nil } + let matches = regex.matches( + in: self, + range: .init(location: 0, length: count) + ) + guard let match = matches.first, + let range = Range(match.range, in: self) + else { return nil } + return String(self[range]) + } + + fileprivate var matchedTestName: String? { + guard let match = match("(\\'.+\\')") else { + return nil + } + return String(match.dropFirst().dropLast()) + } + + fileprivate var matchedDate: String? { + match("(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}\\:\\d{2}\\:\\d{2}\\.\\d{3})") + } + + fileprivate var matchedSeconds: String? { + match("(\\d+\\.\\d+)") + } + + fileprivate var matchedUnexpected: String? { + guard let dropFirst = match("\\((\\d+)")?.dropFirst() else { + return nil + } + return String(dropFirst) + } +} diff --git a/Sources/TestifySDK/Codable/TestResultGitHubFlavoredMarkdownEncoder.swift b/Sources/TestifySDK/Codable/TestResultGitHubFlavoredMarkdownEncoder.swift index 5558e7b..25140fc 100644 --- a/Sources/TestifySDK/Codable/TestResultGitHubFlavoredMarkdownEncoder.swift +++ b/Sources/TestifySDK/Codable/TestResultGitHubFlavoredMarkdownEncoder.swift @@ -1,11 +1,18 @@ +// +// TestResultGitHubFlavoredMarkdownEncoder.swift +// Testify +// +// Created by kanstantsin-bucha +// + import Foundation public struct TestResultGitHubFlavoredMarkdownEncoder: TestResultEncoder { - + public init() { - + } - + public func encode(_ input: TestSuite) throws -> String { var totalTestsCount = 0 var totalTime: Double = 0 @@ -19,39 +26,46 @@ public struct TestResultGitHubFlavoredMarkdownEncoder: TestResultEncoder { let name = suite.name let count = suite.cases.count let time = suite.cases.reduce(0) { $0 + $1.duration } - let successCount = suite.cases.reduce(0) { $0 + ($1.outcome == .success ? 1 : 0) } - let failureCount = suite.cases.reduce(0) { $0 + ($1.outcome == .failure ? 1 : 0) } - + let successCount = suite.cases.reduce(0) { + $0 + ($1.outcome == .success ? 1 : 0) + } + let failureCount = suite.cases.reduce(0) { + $0 + ($1.outcome == .failure ? 1 : 0) + } + totalTestsCount += count totalTime += time totalSucceedCount += successCount totalFailedCount += totalFailedCount - + result += "
\n" let suiteResult = count == successCount ? "✅" : "❌" - result += " \(suiteResult) \(name): \(count) tests were completed in \(timeString(time)) with \(successCount) passed, \(failureCount) failed.\n" - + result += + " \(suiteResult) \(name): \(count) tests were completed in \(timeString(time)) with \(successCount) passed, \(failureCount) failed.\n" + for testCase in suite.cases { let name = testCase.testName let testResult = testCase.outcome == .success ? "✅" : "❌" let time = timeString(testCase.duration) result += "| \(testResult) \(time) | \(name)
\n" } - + result += "
\n" result += "
\n" } - + let testsRunResult = totalTestsCount == totalSucceedCount ? "✅" : "❌" - var testsResult = "# \(testsRunResult) \(totalTestsCount) tests were completed in \(timeString(totalTime))" + var testsResult = + "# \(testsRunResult) \(totalTestsCount) tests were completed in \(timeString(totalTime))" testsResult += "\n \n" - testsResult += "\(totalSucceedCount) tests passed, \(totalFailedCount) test failed.\n" + testsResult += + "\(totalSucceedCount) tests passed, \(totalFailedCount) test failed.\n" testsResult += "\n----\n" testsResult += result - + return testsResult } - + private func timeString(_ sec: Double) -> String { "\(String(format: "%.2f", sec))s" } diff --git a/Sources/TestifySDK/Codable/TestResultJSONEncoder.swift b/Sources/TestifySDK/Codable/TestResultJSONEncoder.swift index f15f88a..36ea6bb 100644 --- a/Sources/TestifySDK/Codable/TestResultJSONEncoder.swift +++ b/Sources/TestifySDK/Codable/TestResultJSONEncoder.swift @@ -1,6 +1,6 @@ // -// File.swift -// +// TestResultJSONEncoder.swift +// Testify // // Created by Tibor Bodecs on 2023. 02. 12.. // @@ -8,7 +8,7 @@ import Foundation struct TestResultJSONEncoder: TestResultEncoder { - + func encode(_ suite: TestSuite) throws -> String { let encoder = JSONEncoder() let data = try encoder.encode(suite) diff --git a/Sources/TestifySDK/Codable/TestResultJunitEncoder.swift b/Sources/TestifySDK/Codable/TestResultJunitEncoder.swift index 4362c73..7af6a27 100644 --- a/Sources/TestifySDK/Codable/TestResultJunitEncoder.swift +++ b/Sources/TestifySDK/Codable/TestResultJunitEncoder.swift @@ -1,6 +1,6 @@ // -// File.swift -// +// TestResultJunitEncoder.swift +// Testify // // Created by Lengyel Gábor on 2023. 02. 15.. // @@ -8,29 +8,32 @@ import Foundation public struct TestResultJunitEncoder: TestResultEncoder { - + public init() { - + } - + public func encode(_ input: TestSuite) throws -> String { var restOfResult = "" var allTests = 0 var allTimes = 0.0 var allFails = 0 - + let suites: [TestSuite] = input.children.reduce([]) { $0 + $1.children } for suite in suites { let start = suite.startDate let name = suite.name let tests = suite.cases.count let time = suite.cases.reduce(0) { $0 + $1.duration } - let failureCount = suite.cases.reduce(0) { $0 + ($1.outcome == .failure ? 1 : 0) } + let failureCount = suite.cases.reduce(0) { + $0 + ($1.outcome == .failure ? 1 : 0) + } allTests += tests allTimes += time allFails += failureCount - restOfResult += "\n" - + restOfResult += + "\n" + for testCase in suite.cases { let name = testCase.testName let className = testCase.className @@ -38,18 +41,22 @@ public struct TestResultJunitEncoder: TestResultEncoder { let time = testCase.duration let failLine = testCase.failureInfo?.line let failReason = testCase.failureInfo?.reason - - restOfResult += "\n" + restOfResult += + "\n" restOfResult += "\(failReason ?? "")\n" restOfResult += "\n" restOfResult += "\n" - } else { + } + else { restOfResult += "/>\n" } } @@ -57,12 +64,13 @@ public struct TestResultJunitEncoder: TestResultEncoder { restOfResult += "\n" } restOfResult += "\n" - + let formatter = DateFormatter() formatter.dateFormat = "yyyyMMdd_HHmmss" var startResult = "\n\n" - startResult += "\n" - + startResult += + "\n" + return startResult + restOfResult } } diff --git a/Sources/TestifySDK/Codable/TestResultMarkdownEncoder.swift b/Sources/TestifySDK/Codable/TestResultMarkdownEncoder.swift index 5713464..7db0d90 100644 --- a/Sources/TestifySDK/Codable/TestResultMarkdownEncoder.swift +++ b/Sources/TestifySDK/Codable/TestResultMarkdownEncoder.swift @@ -1,6 +1,6 @@ // -// File.swift -// +// TestResultMarkdownEncoder.swift +// Testify // // Created by Tibor Bodecs on 2023. 02. 12.. // @@ -8,11 +8,11 @@ import Foundation public struct TestResultMarkdownEncoder: TestResultEncoder { - + public init() { - + } - + public func encode(_ input: TestSuite) throws -> String { var result = "" @@ -22,20 +22,25 @@ public struct TestResultMarkdownEncoder: TestResultEncoder { let name = suite.name let count = suite.cases.count let time = suite.cases.reduce(0) { $0 + $1.duration } - let successCount = suite.cases.reduce(0) { $0 + ($1.outcome == .success ? 1 : 0) } - let failureCount = suite.cases.reduce(0) { $0 + ($1.outcome == .failure ? 1 : 0) } - - result += "\(name): \(count) tests were completed in \(time) with \(successCount) passed, \(failureCount) failed.\n\n" + let successCount = suite.cases.reduce(0) { + $0 + ($1.outcome == .success ? 1 : 0) + } + let failureCount = suite.cases.reduce(0) { + $0 + ($1.outcome == .failure ? 1 : 0) + } + + result += + "\(name): \(count) tests were completed in \(time) with \(successCount) passed, \(failureCount) failed.\n\n" result += "| Test case | Result | Time |\n" result += "| :--- | ---: | ---: |\n" - + for testCase in suite.cases { let name = testCase.testName let testResult = testCase.outcome == .success ? "✅" : "❌" let time = "\(testCase.duration)s ⌛️" result += "| \(name) | \(testResult) | \(time) |\n" } - + } return result diff --git a/Sources/TestifySDK/Models/FailureInfo.swift b/Sources/TestifySDK/Models/FailureInfo.swift index d0eab1d..3fc00b0 100644 --- a/Sources/TestifySDK/Models/FailureInfo.swift +++ b/Sources/TestifySDK/Models/FailureInfo.swift @@ -11,7 +11,7 @@ public struct FailureInfo: Codable { public let file: String public let line: Int public let reason: String - + public init( file: String, line: Int, diff --git a/Sources/TestifySDK/Models/OutputFormat.swift b/Sources/TestifySDK/Models/OutputFormat.swift index 45dc358..f597cc5 100644 --- a/Sources/TestifySDK/Models/OutputFormat.swift +++ b/Sources/TestifySDK/Models/OutputFormat.swift @@ -1,13 +1,13 @@ // -// File.swift -// +// OutputFormat.swift +// Testify // // Created by Lengyel Gábor on 2023. 02. 17.. // import Foundation -public enum OutputFormat : String, CaseIterable, Sendable { +public enum OutputFormat: String, CaseIterable, Sendable { case json case junit case md diff --git a/Sources/TestifySDK/Models/TestCase.swift b/Sources/TestifySDK/Models/TestCase.swift index 234877a..8cd04a8 100644 --- a/Sources/TestifySDK/Models/TestCase.swift +++ b/Sources/TestifySDK/Models/TestCase.swift @@ -14,7 +14,7 @@ public struct TestCase: Codable { public let duration: TimeInterval public let outcome: Outcome public let failureInfo: FailureInfo? - + public init( moduleName: String, className: String, @@ -30,5 +30,5 @@ public struct TestCase: Codable { self.outcome = outcome self.failureInfo = failureInfo } - + } diff --git a/Sources/TestifySDK/Models/TestSuite.swift b/Sources/TestifySDK/Models/TestSuite.swift index c48cfb5..23097e3 100644 --- a/Sources/TestifySDK/Models/TestSuite.swift +++ b/Sources/TestifySDK/Models/TestSuite.swift @@ -7,10 +7,10 @@ import Foundation -public extension TestSuite { - static func parse(_ input: String) -> TestSuite? { +extension TestSuite { + public static func parse(_ input: String) -> TestSuite? { let decoder = RawTestResultDecoder() - let suite = try! decoder.decode(input) + let suite = try? decoder.decode(input) return suite } } @@ -23,7 +23,7 @@ public struct TestSuite: Codable { public var outcome: Outcome public var cases: [TestCase] public var children: [TestSuite] - + public init( name: String, startDate: Date, diff --git a/Sources/testify/main.swift b/Sources/testify/main.swift index bf80a66..bfcf5a9 100644 --- a/Sources/testify/main.swift +++ b/Sources/testify/main.swift @@ -10,14 +10,17 @@ import TestifySDK let args = CommandLine.arguments var outputFormat: OutputFormat = .json -if (args.count >= 2) { +if args.count >= 2 { if let format = OutputFormat(rawValue: args[1]) { outputFormat = format - } else { + } + else { let formats = OutputFormat.allCases - .map { "'\($0)'"} + .map { "'\($0)'" } .joined(separator: ", ") - fatalError("Error: Unknown output format. Available formats: \(formats)") + fatalError( + "Error: Unknown output format. Available formats: \(formats)" + ) } } @@ -26,7 +29,7 @@ var input: String = "" repeat { data = FileHandle.standardInput.availableData input += String(decoding: data, as: UTF8.self) -} while (data.count > 0) +} while data.count > 0 let decoder = RawTestResultDecoder() guard let suite = try decoder.decode(input) else { @@ -40,19 +43,19 @@ case .json: let jsonData = try encoder.encode(suite) print("\n", String(data: jsonData, encoding: .utf8)!, "\n") print("\n", String(decoding: data, as: UTF8.self), "\n") - + case .junit: let encoder = TestResultJunitEncoder() - let junitData = try! encoder.encode(suite) + let junitData = try encoder.encode(suite) print(junitData) - + case .md: let encoder = TestResultMarkdownEncoder() - let mdData = try! encoder.encode(suite) + let mdData = try encoder.encode(suite) print(mdData) - + case .gfm: let encoder = TestResultGitHubFlavoredMarkdownEncoder() - let mdData = try! encoder.encode(suite) + let mdData = try encoder.encode(suite) print(mdData) } diff --git a/Tests/TestifySDKTests/Bundle+Extensions.swift b/Tests/TestifySDKTests/Bundle+Extensions.swift index 5837b78..b4b1a3a 100644 --- a/Tests/TestifySDKTests/Bundle+Extensions.swift +++ b/Tests/TestifySDKTests/Bundle+Extensions.swift @@ -8,17 +8,23 @@ import Foundation extension Bundle { - + enum BundleCustomError: Error { case fileNotFound(file: String, extension: String) } - - func getURL(for file: String, withExtension extension: String) throws -> URL { - guard let url = Bundle.module.url( - forResource: file, - withExtension: `extension` - ) else { - throw BundleCustomError.fileNotFound(file: file, extension: `extension`) + + func getURL(for file: String, withExtension extension: String) throws -> URL + { + guard + let url = Bundle.module.url( + forResource: file, + withExtension: `extension` + ) + else { + throw BundleCustomError.fileNotFound( + file: file, + extension: `extension` + ) } return url } diff --git a/Tests/TestifySDKTests/TestifyJunitTests.swift b/Tests/TestifySDKTests/TestifyJunitTests.swift index fa4ff2a..5d85fa1 100644 --- a/Tests/TestifySDKTests/TestifyJunitTests.swift +++ b/Tests/TestifySDKTests/TestifyJunitTests.swift @@ -1,9 +1,14 @@ -import XCTest import Foundation +import XCTest + @testable import TestifySDK +#if canImport(FoundationXML) +import FoundationXML +#endif + final class TestifyJunitTests: XCTestCase { - + func testTests() throws { let testFiles = [ "PromiseUnexpectedFailure", @@ -17,32 +22,43 @@ final class TestifyJunitTests: XCTestCase { ] let decoder = RawTestResultDecoder() - + for file in testFiles { - - let testUrl = try Bundle.module.getURL(for: file, withExtension: "tests") - let xmlUrl = try Bundle.module.getURL(for: file, withExtension: "xml") + + let testUrl = try Bundle.module.getURL( + for: file, + withExtension: "tests" + ) + let xmlUrl = try Bundle.module.getURL( + for: file, + withExtension: "xml" + ) let testData = try Data(contentsOf: testUrl) - guard let testOutput = String(data: testData, encoding: .utf8) else { + guard let testOutput = String(data: testData, encoding: .utf8) + else { return XCTFail("Could not decode test data.") } - + let suite = try decoder.decode(testOutput) let xmlData = try Data(contentsOf: xmlUrl) let xmlParser = XMLParser(data: xmlData) let parser = SuiteXmlParser() xmlParser.delegate = parser xmlParser.parse() - + if let suite { - XCTAssertTrue(self.checkNumbers(suite, parser), "Invalid numbers count for \(file).") - } else { + XCTAssertTrue( + self.checkNumbers(suite, parser), + "Invalid numbers count for \(file)." + ) + } + else { XCTFail("Could not parse XML for \(file).") } } } - + //@TODO: is there a better way to check this ? func checkNumbers(_ suite: TestSuite, _ parser: SuiteXmlParser) -> Bool { let suites: [TestSuite] = suite.children.reduce([]) { $0 + $1.children } @@ -50,35 +66,38 @@ final class TestifyJunitTests: XCTestCase { var allFails = 0 for suite in suites { let tests = suite.cases.count - let failureCount = suite.cases.reduce(0) { $0 + ($1.outcome == .failure ? 1 : 0) } + let failureCount = suite.cases.reduce(0) { + $0 + ($1.outcome == .failure ? 1 : 0) + } allTests += tests allFails += failureCount } - return suites.count == parser.suites && - allTests == parser.tests && - allFails == parser.fails + return suites.count == parser.suites && allTests == parser.tests + && allFails == parser.fails } - - class SuiteXmlParser : NSObject, XMLParserDelegate { + + class SuiteXmlParser: NSObject, XMLParserDelegate { var suites = 0 var tests = 0 var fails = 0 - + func parser( _ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, - attributes attributeDict: [String : String] = [:] + attributes attributeDict: [String: String] = [:] ) { - if (elementName=="testsuite") { + if elementName == "testsuite" { suites += 1 - } else if (elementName=="testcase") { + } + else if elementName == "testcase" { tests += 1 - } else if (elementName=="failure") { + } + else if elementName == "failure" { fails += 1 } } } - + } diff --git a/Tests/TestifySDKTests/TestifyMarkdownTests.swift b/Tests/TestifySDKTests/TestifyMarkdownTests.swift index 048f1d7..848cc20 100644 --- a/Tests/TestifySDKTests/TestifyMarkdownTests.swift +++ b/Tests/TestifySDKTests/TestifyMarkdownTests.swift @@ -1,4 +1,5 @@ import XCTest + @testable import TestifySDK final class TestifyMarkdownTests: XCTestCase { @@ -17,64 +18,81 @@ final class TestifyMarkdownTests: XCTestCase { ] let decoder = RawTestResultDecoder() - + for file in testFiles { - let testUrl = try Bundle.module.getURL(for: file, withExtension: "tests") + let testUrl = try Bundle.module.getURL( + for: file, + withExtension: "tests" + ) let mdUrl = try Bundle.module.getURL(for: file, withExtension: "md") let testData = try Data(contentsOf: testUrl) - guard let testOutput = String(data: testData, encoding: .utf8) else { + guard let testOutput = String(data: testData, encoding: .utf8) + else { return XCTFail("Could not decode test data.") } let suite = try decoder.decode(testOutput) - + let resultData = try Data(contentsOf: mdUrl) - guard let testMdOutput = String(data: resultData, encoding: .utf8) else { + guard let testMdOutput = String(data: resultData, encoding: .utf8) + else { return XCTFail("Could not decode test data.") } - - let filtered = testMdOutput.split(separator: "\n").map({ String($0) }).filter { - $0.contains("tests were completed") && - $0.contains("passed") && - $0.contains("failed") - } - + + let filtered = testMdOutput.split(separator: "\n") + .map({ String($0) }) + .filter { + $0.contains("tests were completed") && $0.contains("passed") + && $0.contains("failed") + } + if let suite { - XCTAssertTrue(self.checkNumbers(suite, filtered), "Invalid numbers count for \(file).") - } else { + XCTAssertTrue( + self.checkNumbers(suite, filtered), + "Invalid numbers count for \(file)." + ) + } + else { XCTFail("Not found suite for \(file).") } - + } } - + //@TODO: is there a better way to check this ? func checkNumbers(_ suite: TestSuite, _ filtered: [String]) -> Bool { var mdAllTests = 0 var mdAllFails = 0 for item in filtered { - - let tests = Int(item.components(separatedBy: " tests were completed in ").filter { $0.contains(": ") }[0] - .components(separatedBy: ": ")[1]) - let fails = Int(item.components(separatedBy: " passed, ").filter { $0.contains("failed.") }[0] - .components(separatedBy: " failed.")[0]) + + let tests = Int( + item.components(separatedBy: " tests were completed in ") + .filter { $0.contains(": ") }[0] + .components(separatedBy: ": ")[1] + ) + let fails = Int( + item.components(separatedBy: " passed, ") + .filter { $0.contains("failed.") }[0] + .components(separatedBy: " failed.")[0] + ) mdAllTests += tests ?? 0 mdAllFails += fails ?? 0 } - + let suites: [TestSuite] = suite.children.reduce([]) { $0 + $1.children } var allTests = 0 var allFails = 0 for suite in suites { let tests = suite.cases.count - let failureCount = suite.cases.reduce(0) { $0 + ($1.outcome == .failure ? 1 : 0) } + let failureCount = suite.cases.reduce(0) { + $0 + ($1.outcome == .failure ? 1 : 0) + } allTests += tests allFails += failureCount } - - return suites.count == filtered.count && - allTests == mdAllTests && - allFails == mdAllFails + + return suites.count == filtered.count && allTests == mdAllTests + && allFails == mdAllFails } - + } diff --git a/Tests/TestifySDKTests/TestifySDKTests.swift b/Tests/TestifySDKTests/TestifySDKTests.swift index d892077..3032f02 100644 --- a/Tests/TestifySDKTests/TestifySDKTests.swift +++ b/Tests/TestifySDKTests/TestifySDKTests.swift @@ -1,4 +1,5 @@ import XCTest + @testable import TestifySDK final class TestifyTests: XCTestCase { @@ -17,36 +18,51 @@ final class TestifyTests: XCTestCase { ] let decoder = RawTestResultDecoder() - + for file in testFiles { - let testUrl = try Bundle.module.getURL(for: file, withExtension: "tests") - let jsonUrl = try Bundle.module.getURL(for: file, withExtension: "json") + let testUrl = try Bundle.module.getURL( + for: file, + withExtension: "tests" + ) + let jsonUrl = try Bundle.module.getURL( + for: file, + withExtension: "json" + ) let testData = try Data(contentsOf: testUrl) - guard let testOutput = String(data: testData, encoding: .utf8) else { + guard let testOutput = String(data: testData, encoding: .utf8) + else { return XCTFail("Could not decode test data.") } let resultData = try Data(contentsOf: jsonUrl) let suite = try decoder.decode(testOutput) - + let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 - let expectation = try decoder.decode(TestSuite.self, from: resultData) + let expectation = try decoder.decode( + TestSuite.self, + from: resultData + ) if let suite { - XCTAssertTrue(self.checkIsEqual(suite, expectation), "Invalid case count for \(file).") - } else { + XCTAssertTrue( + self.checkIsEqual(suite, expectation), + "Invalid case count for \(file)." + ) + } + else { XCTFail("no test suite found") } } } //@TODO: proper equation checking, for now I'm ok with this... ¯\_(ツ)_/¯ - func checkIsEqual(_ testSuite1: TestSuite, _ testSuite2: TestSuite) -> Bool { - return testSuite1.name == testSuite2.name && - testSuite1.outcome == testSuite2.outcome && - testSuite1.cases.count == testSuite2.cases.count && - testSuite1.children.count == testSuite2.children.count + func checkIsEqual(_ testSuite1: TestSuite, _ testSuite2: TestSuite) -> Bool + { + testSuite1.name == testSuite2.name + && testSuite1.outcome == testSuite2.outcome + && testSuite1.cases.count == testSuite2.cases.count + && testSuite1.children.count == testSuite2.children.count } - + }