Skip to content

Commit 70d5490

Browse files
committed
Initial release
0 parents  commit 70d5490

31 files changed

+257924
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj

Dockerfile

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM swift
2+
WORKDIR /app
3+
COPY . ./
4+
CMD swift package clean
5+
CMD swift test --parallel

LICENSE

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2+
Version 2, December 2004
3+
4+
Copyright (C) 2018-2019 Binary Birds
5+
6+
Authors:
7+
8+
Tibor Bodecs <[email protected]>
9+
10+
Everyone is permitted to copy and distribute verbatim or modified
11+
copies of this license document, and changing it is allowed as long
12+
as the name is changed.
13+
14+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
15+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
16+
17+
0. You just DO WHAT THE FUCK YOU WANT TO.

Package.swift

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// swift-tools-version:4.2
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "Testify",
6+
products: [
7+
.library(name: "Testify", targets: ["Testify"]),
8+
],
9+
targets: [
10+
.target(
11+
name: "Testify",
12+
dependencies: []),
13+
.testTarget(
14+
name: "TestifyTests",
15+
dependencies: ["Testify"]),
16+
]
17+
)

README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Testify (✅)
2+
3+
Testify converts XCTest output into a proper structure (JSON), or it'll miserably fail. 😉
4+
5+
## Usage
6+
7+
8+
```swift
9+
import Testify
10+
11+
let suite = TestSuite.parse("test-output-string")
12+
```
13+
14+
## Install
15+
16+
Just use the [Swift Package Manager](https://theswiftdev.com/2017/11/09/swift-package-manager-tutorial/) as usual:
17+
18+
```swift
19+
.package(url: "https://github.com/binarybirds/testify", from: "1.0.0"),
20+
```
21+
22+
⚠️ Don't forget to add "Testify" to your target as a dependency!
23+
24+
25+
26+
## License
27+
28+
[WTFPL](LICENSE) - Do what the fuck you want to.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// String+Regex.swift
3+
// Testify
4+
//
5+
// Created by Tibor Bödecs on 2019. 01. 17..
6+
//
7+
8+
import Foundation
9+
10+
extension String {
11+
12+
func match(regex pattern: String) -> String? {
13+
let regex = try! NSRegularExpression(pattern: pattern)
14+
let matches = regex.matches(in: self, range: NSRange(location: 0, length: self.count))
15+
guard
16+
let match = matches.first,
17+
let range = Range(match.range, in: self)
18+
else {
19+
return nil
20+
}
21+
return String(self[range])
22+
}
23+
24+
var matchedTestName: String? {
25+
guard let match = self.match(regex: "(\\'.+\\')") else {
26+
return nil
27+
}
28+
return String(match.dropFirst().dropLast())
29+
}
30+
31+
var matchedDate: String? {
32+
return self.match(regex: "(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}\\:\\d{2}\\:\\d{2}\\.\\d{3})")
33+
}
34+
35+
var matchedSeconds: String? {
36+
return self.match(regex: "(\\d+\\.\\d+)")
37+
}
38+
39+
var matchedUnexpected: String? {
40+
return String(self.match(regex: "\\((\\d+)")!.dropFirst())
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//
2+
// TestSuite+Parse.swift
3+
// Testify
4+
//
5+
// Created by Tibor Bödecs on 2019. 01. 20..
6+
//
7+
8+
import Foundation
9+
10+
public extension TestSuite {
11+
12+
public static func parse(_ output: String) -> TestSuite {
13+
14+
let dateFormatter = DateFormatter()
15+
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
16+
17+
var suites: [TestSuite] = []
18+
var currentCaseName: String?
19+
var testCaseOutput: String!
20+
var gatherTestCaseOutput = false
21+
22+
let lines = output.split(separator: "\n").map({ String($0) })
23+
for (index, line) in lines.enumerated() {
24+
// start or end test suite
25+
if line.contains("Test Suite") {
26+
if line.contains("started") {
27+
let name = line.matchedTestName!
28+
let date = dateFormatter.date(from: line.matchedDate!)!
29+
30+
suites.append(TestSuite(name: name,
31+
startDate: date,
32+
endDate: date,
33+
unexpected: 0,
34+
outcome: .failure))
35+
continue;
36+
}
37+
else {
38+
var suite = suites.last!
39+
suites = Array(suites.dropLast())
40+
41+
suite.outcome = line.contains("passed") ? .success : .failure
42+
suite.endDate = dateFormatter.date(from: line.matchedDate!)!
43+
44+
if index+1 < lines.count {
45+
let nextLine = lines[index+1]
46+
if nextLine.contains("Executed") {
47+
suite.unexpected = Int(nextLine.matchedUnexpected!)!
48+
}
49+
}
50+
51+
if suites.isEmpty {
52+
suites.append(suite)
53+
}
54+
else {
55+
var parentSuite = suites.last!
56+
suites = Array(suites.dropLast())
57+
parentSuite.children.append(suite)
58+
suites.append(parentSuite)
59+
}
60+
continue;
61+
}
62+
}
63+
if line.contains("Test Case") {
64+
if line.contains("started") {
65+
testCaseOutput = ""
66+
gatherTestCaseOutput = true
67+
currentCaseName = line.matchedTestName
68+
continue;
69+
}
70+
else {
71+
gatherTestCaseOutput = false
72+
var suite = suites.last!
73+
suites = Array(suites.dropLast())
74+
let outcome: TestOutcome = line.contains("passed") ? .success : .failure
75+
let caseName = currentCaseName!.dropFirst(2).dropLast()
76+
let firstSplit = caseName.split(separator: ".")
77+
let secondSplit = firstSplit[1].split(separator: " ")
78+
79+
var failureInfo: TestFailureInfo? = nil
80+
if outcome == .failure {
81+
let outputSplit = testCaseOutput.split(separator: ":")
82+
let file = String(outputSplit[0])
83+
let line = Int(outputSplit[1])!
84+
let reason = String(outputSplit.dropFirst(4)
85+
.joined(separator: ":")
86+
.trimmingCharacters(in: CharacterSet(charactersIn: "-").union(.whitespaces)))
87+
failureInfo = TestFailureInfo(file: file, line: line, reason: reason)
88+
}
89+
90+
let testCase = TestCase(moduleName: String(firstSplit[0]),
91+
className: String(secondSplit[0]),
92+
testName: String(secondSplit[1]),
93+
duration: TimeInterval(line.matchedSeconds!)!,
94+
outcome: outcome,
95+
failureInfo: failureInfo)
96+
suite.cases.append(testCase)
97+
suites.append(suite)
98+
currentCaseName = nil
99+
continue;
100+
}
101+
}
102+
if gatherTestCaseOutput {
103+
testCaseOutput += line
104+
}
105+
}
106+
return suites.first!
107+
}
108+
}

Sources/Testify/Models/TestCase.swift

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// TestCase.swift
3+
// Testify
4+
//
5+
// Created by Tibor Bödecs on 2019. 01. 17..
6+
//
7+
8+
import Foundation
9+
10+
public struct TestCase: Codable {
11+
12+
public var moduleName: String
13+
public var className: String
14+
public var testName: String
15+
public var duration: TimeInterval
16+
public var outcome: TestOutcome
17+
public var failureInfo: TestFailureInfo?
18+
19+
public init(moduleName: String,
20+
className: String,
21+
testName: String,
22+
duration: TimeInterval,
23+
outcome: TestOutcome,
24+
failureInfo: TestFailureInfo? = nil) {
25+
self.moduleName = moduleName
26+
self.className = className
27+
self.testName = testName
28+
self.duration = duration
29+
self.outcome = outcome
30+
self.failureInfo = failureInfo
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// TestFailureInfo.swift
3+
// Testify
4+
//
5+
// Created by Tibor Bödecs on 2019. 01. 19..
6+
//
7+
8+
import Foundation
9+
10+
public struct TestFailureInfo: Codable {
11+
12+
public var file: String
13+
public var line: Int
14+
public var reason: String
15+
16+
public init(file: String, line: Int, reason: String) {
17+
self.file = file
18+
self.line = line
19+
self.reason = reason
20+
}
21+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// TestOutcome.swift
3+
// Testify
4+
//
5+
// Created by Tibor Bödecs on 2019. 01. 18..
6+
//
7+
8+
import Foundation
9+
10+
public enum TestOutcome: String, Codable {
11+
case success
12+
case failure
13+
}
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// TestSuite.swift
3+
// Testify
4+
//
5+
// Created by Tibor Bödecs on 2019. 01. 17..
6+
//
7+
8+
import Foundation
9+
10+
public struct TestSuite: Codable {
11+
12+
public var name: String
13+
public var startDate: Date
14+
public var endDate: Date
15+
public var unexpected: Int
16+
public var outcome: TestOutcome
17+
public var cases: [TestCase]
18+
public var children: [TestSuite]
19+
20+
public init(name: String,
21+
startDate: Date,
22+
endDate: Date,
23+
unexpected: Int,
24+
outcome: TestOutcome,
25+
cases: [TestCase] = [],
26+
children: [TestSuite] = []) {
27+
self.name = name
28+
self.startDate = startDate
29+
self.endDate = endDate
30+
self.unexpected = unexpected
31+
self.outcome = outcome
32+
self.cases = cases
33+
self.children = children
34+
}
35+
}

0 commit comments

Comments
 (0)