diff --git a/CHANGELOG.md b/CHANGELOG.md index d79a0df..1c31b3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ ## master +## 2.3.5 +* Adding support for `buckLogFilePath` for the extraction of Buck Build related rules - Bruno Rocha +* Adding Buck support to `TestCountProvider` - Bruno Rocha + ## 2.3.4 * Fixes version string and makes error methods public for custom providers - Bruno Rocha diff --git a/ExampleProject/Gemfile b/ExampleProject/Gemfile index 5a15923..a49b1ff 100644 --- a/ExampleProject/Gemfile +++ b/ExampleProject/Gemfile @@ -1,4 +1,4 @@ source "https://rubygems.org" -gem "fastlane", "~> 2.102.0" +gem "fastlane", "2.128.0" gem "cocoapods" \ No newline at end of file diff --git a/ExampleProject/Gemfile.lock b/ExampleProject/Gemfile.lock index a884d84..b64e705 100644 --- a/ExampleProject/Gemfile.lock +++ b/ExampleProject/Gemfile.lock @@ -52,12 +52,13 @@ GEM concurrent-ruby (1.1.5) declarative (0.0.10) declarative-option (0.1.0) - domain_name (0.5.20180417) + digest-crc (0.4.1) + domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.2) - emoji_regex (0.1.1) + dotenv (2.7.5) + emoji_regex (1.0.1) escape (0.0.4) - excon (0.64.0) + excon (0.66.0) faraday (0.15.4) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) @@ -66,15 +67,15 @@ GEM faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) fastimage (2.1.5) - fastlane (2.102.0) + fastlane (2.128.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) - bundler (>= 1.12.0, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) colored commander-fastlane (>= 4.4.6, < 5.0.0) dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (~> 0.1) + emoji_regex (>= 0.1, < 2.0) excon (>= 0.45.0, < 1.0.0) faraday (~> 0.9) faraday-cookie_jar (~> 0.0.6) @@ -82,25 +83,26 @@ GEM fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) google-api-client (>= 0.21.2, < 0.24.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) highline (>= 1.7.2, < 2.0.0) json (< 3.0.0) - mini_magick (~> 4.5.1) - multi_json + jwt (~> 2.1.0) + mini_magick (>= 4.9.4, < 5.0.0) multi_xml (~> 0.5) multipart-post (~> 2.0.0) plist (>= 3.1.0, < 4.0.0) public_suffix (~> 2.0.0) - rubyzip (>= 1.2.1, < 2.0.0) + rubyzip (>= 1.2.2, < 2.0.0) security (= 0.1.3) simctl (~> 1.6.3) slack-notifier (>= 2.0.0, < 3.0.0) - terminal-notifier (>= 1.6.2, < 2.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (>= 1.4.5, < 2.0.0) tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) - xcodeproj (>= 1.5.7, < 2.0.0) - xcpretty (~> 0.2.8) + xcodeproj (>= 1.8.1, < 2.0.0) + xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) fourflusher (2.2.0) fuzzy_match (2.0.4) @@ -113,6 +115,15 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) signet (~> 0.9) + google-cloud-core (1.3.0) + google-cloud-env (~> 1.0) + google-cloud-env (1.2.0) + faraday (~> 0.11) + google-cloud-storage (1.16.0) + digest-crc (~> 0.4) + google-api-client (~> 0.23) + google-cloud-core (~> 1.2) + googleauth (>= 0.6.2, < 0.10.0) googleauth (0.6.7) faraday (~> 0.12) jwt (>= 1.4, < 3.0) @@ -132,7 +143,7 @@ GEM mime-types (3.2.2) mime-types-data (~> 3.2015) mime-types-data (3.2019.0331) - mini_magick (4.5.1) + mini_magick (4.9.5) minitest (5.11.3) molinillo (0.6.6) multi_json (1.13.1) @@ -152,7 +163,7 @@ GEM retriable (3.1.2) rouge (2.0.7) ruby-macho (1.4.0) - rubyzip (1.2.2) + rubyzip (1.2.3) security (0.1.3) signet (0.11.0) addressable (~> 2.3) @@ -163,21 +174,21 @@ GEM CFPropertyList naturally slack-notifier (2.3.2) - terminal-notifier (1.8.0) + terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) - tty-cursor (0.6.1) - tty-screen (0.6.5) - tty-spinner (0.9.0) - tty-cursor (~> 0.6.0) + tty-cursor (0.7.0) + tty-screen (0.7.0) + tty-spinner (0.9.1) + tty-cursor (~> 0.7) tzinfo (1.2.5) thread_safe (~> 0.1) uber (0.1.0) unf (0.1.4) unf_ext unf_ext (0.0.7.6) - unicode-display_width (1.5.0) + unicode-display_width (1.6.0) word_wrap (1.0.0) xcodeproj (1.8.2) CFPropertyList (>= 2.3.3, < 4.0) @@ -185,7 +196,7 @@ GEM claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.2.6) - xcpretty (0.2.8) + xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.0) xcpretty (~> 0.2, >= 0.0.7) @@ -195,7 +206,7 @@ PLATFORMS DEPENDENCIES cocoapods - fastlane (~> 2.102.0) + fastlane (= 2.128.0) BUNDLED WITH - 1.16.5 + 1.17.3 diff --git a/ExampleProject/Infofile.swift b/ExampleProject/Infofile.swift index 47ed7cc..c6fb056 100644 --- a/ExampleProject/Infofile.swift +++ b/ExampleProject/Infofile.swift @@ -18,6 +18,6 @@ let output = api.extract(IPASizeProvider.self) + api.extract(LongestTestDurationProvider.self) + api.extract(ArchiveDurationProvider.self) -api.sendToSlack(output: output, webhookUrl: "slackUrlHere") - +//api.sendToSlack(output: output, webhookUrl: "slackUrlHere") +api.print(output: output) api.save(output: output) diff --git a/ExampleProject/SwiftInfoExample.xcodeproj/project.pbxproj b/ExampleProject/SwiftInfoExample.xcodeproj/project.pbxproj index c75cdb3..b3629ea 100644 --- a/ExampleProject/SwiftInfoExample.xcodeproj/project.pbxproj +++ b/ExampleProject/SwiftInfoExample.xcodeproj/project.pbxproj @@ -412,7 +412,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 4GEC7S6VF4; + DEVELOPMENT_TEAM = U7KHKH9L7R; INFOPLIST_FILE = SwiftInfoExample/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -431,7 +431,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 4GEC7S6VF4; + DEVELOPMENT_TEAM = U7KHKH9L7R; INFOPLIST_FILE = SwiftInfoExample/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/ExampleProject/SwiftInfoExample.xcodeproj/xcuserdata/bruno.rocha.xcuserdatad/xcschemes/xcschememanagement.plist b/ExampleProject/SwiftInfoExample.xcodeproj/xcuserdata/bruno.rocha.xcuserdatad/xcschemes/xcschememanagement.plist index d935489..3f6fdd9 100644 --- a/ExampleProject/SwiftInfoExample.xcodeproj/xcuserdata/bruno.rocha.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/ExampleProject/SwiftInfoExample.xcodeproj/xcuserdata/bruno.rocha.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ SwiftInfoExample.xcscheme_^#shared#^_ orderHint - 2 + 0 diff --git a/ExampleProject/SwiftInfoExample.xcworkspace/xcuserdata/bruno.rocha.xcuserdatad/UserInterfaceState.xcuserstate b/ExampleProject/SwiftInfoExample.xcworkspace/xcuserdata/bruno.rocha.xcuserdatad/UserInterfaceState.xcuserstate index f1b2e50..7424391 100644 Binary files a/ExampleProject/SwiftInfoExample.xcworkspace/xcuserdata/bruno.rocha.xcuserdatad/UserInterfaceState.xcuserstate and b/ExampleProject/SwiftInfoExample.xcworkspace/xcuserdata/bruno.rocha.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Formula/swiftinfo.rb b/Formula/swiftinfo.rb index e9034d4..fb95127 100644 --- a/Formula/swiftinfo.rb +++ b/Formula/swiftinfo.rb @@ -1,11 +1,11 @@ class Swiftinfo < Formula desc "📊 Extract and analyze the evolution of an iOS app's code." homepage "https://github.com/rockbruno/SwiftInfo" - version "2.3.4" + version "2.3.5" url "https://github.com/rockbruno/SwiftInfo/releases/download/#{version}/swiftinfo.zip" # TODO: Try something to provide a SHA automatically - depends_on :xcode => ["10.2", :build] + depends_on :xcode => ["10.3", :build] def install bin.install Dir["bin/*"] diff --git a/README.md b/README.md index b5472c6..e65b279 100644 --- a/README.md +++ b/README.md @@ -12,31 +12,33 @@ By default SwiftInfo will assume you're extracting info from a release build and ## Available Providers -| **Type Name** | **Description** | **Requirements** | -|---|:---:|:---:| -| **📦 IPASizeProvider** | Size of the .ipa archive (Not the App Store size!) | Successful xcodebuild archive and build logs | -| **📊 CodeCoverageProvider** | Code coverage percentage | Test logs, Xcode developer tools, Test targets with code coverage reports enabled | -| **👶 TargetCountProvider** | Number of targets (dependencies) | Build logs | -| **🎯 TestCountProvider** | Sum of all test target's test count | Test logs | -| **⚠️ WarningCountProvider** | Number of warnings in a build | Build logs | -| **🧙‍♂️ OBJCFileCountProvider** | Number of OBJ-C files and headers (for mixed OBJ-C / Swift projects) | Build logs | -| **⏰ LongestTestDurationProvider** | The name and duration of the longest test | Test logs | -| **🛏 TotalTestDurationProvider** | Time it took to build and run all tests | Test logs | -| **🖼 LargestAssetCatalogProvider** | The name and size of the largest asset catalog | Build logs | -| **🎨 TotalAssetCatalogsSizeProvider** | The sum of the size of all asset catalogs | Build logs | -| **💻 LinesOfCodeProvider** | Executable lines of code | Same as CodeCoverageProvider. | -| **🚚 ArchiveDurationProvider** | Time it took to build and archive the app | Successful xcodebuild archive and build logs | -| **📷 LargestAssetProvider** | The largest asset in the project. Only considers files inside asset catalogs. | Build logs | +| **Type Name** | **Description** | **Requirements** | **Supported build systems** +|---|:---:|:---:|:---:| +| **📦 IPASizeProvider** | Size of the .ipa archive (not the App Store size!) | .ipa available in the `#{PROJECT_DIR}/build` folder | Xcode/Buck | +| **📊 CodeCoverageProvider** | Code coverage percentage | Test logs, Xcode developer tools, Test targets with code coverage reports enabled | Xcode | +| **👶 TargetCountProvider** | Number of targets (dependencies) | Build logs | Xcode | +| **🎯 TestCountProvider** | Sum of all test target's test count | Test logs (if building with Xcode) or Buck build log (if building with Buck) | Xcode/Buck | +| **⚠️ WarningCountProvider** | Number of warnings in a build | Build logs | Xcode | +| **🧙‍♂️ OBJCFileCountProvider** | Number of OBJ-C files and headers (for mixed OBJ-C / Swift projects) | Build logs | Xcode | +| **⏰ LongestTestDurationProvider** | The name and duration of the longest test | Test logs | Xcode | +| **🛏 TotalTestDurationProvider** | Time it took to build and run all tests | Test logs | Xcode | +| **🖼 LargestAssetCatalogProvider** | The name and size of the largest asset catalog | Build logs | Xcode | +| **🎨 TotalAssetCatalogsSizeProvider** | The sum of the size of all asset catalogs | Build logs | Xcode | +| **💻 LinesOfCodeProvider** | Executable lines of code | Same as CodeCoverageProvider. | Xcode | +| **🚚 ArchiveDurationProvider** | Time it took to build and archive the app | Successful xcodebuild archive and build logs | Xcode | +| **📷 LargestAssetProvider** | The largest asset in the project. Only considers files inside asset catalogs. | Build logs | Xcode | ## Usage -SwiftInfo extracts information by analyzing the logs that logs that Xcode generates when you build and/or test your app. Because it requires these logs to work, SwiftInfo is meant to be used alongside a build automation tool like [fastlane](https://github.com/fastlane/fastlane). The following topics describe how you can retrieve these logs and setup SwiftInfo itself. +SwiftInfo extracts information by analyzing the logs that your build system generates when you build and/or test your app. Because it requires these logs to work, SwiftInfo is meant to be used alongside a build automation tool like [fastlane](https://github.com/fastlane/fastlane). The following topics describe how you can retrieve these logs and setup SwiftInfo itself. We'll show how to get the logs first as you'll need them to configure SwiftInfo. **Note:** This repository contains an example project. Check it out to see the tool in action! -### Retrieving raw logs with [fastlane](https://github.com/fastlane/fastlane) +### If building with Xcode + +#### Retrieving raw logs with [fastlane](https://github.com/fastlane/fastlane) If you use fastlane, you can expose the raw logs by adding the `buildlog_path` argument to `scan` (test logs) and `gym` (build logs). Here's a simple example of a fastlane step that runs tests, submits an archive to TestFlight and runs SwiftInfo (be sure to edit the folder paths to what's being used by your project): @@ -72,7 +74,7 @@ lane :beta do end ``` -### Retrieving raw logs manually +#### Retrieving raw logs manually An alternative that doesn't require fastlane is to simply manually run `xcodebuild` / `xctest` and pipe the output to a file. We don't recommend doing this in a real project, but it can be useful if you just want to test the tool without having to setup fastlane. @@ -80,8 +82,18 @@ An alternative that doesn't require fastlane is to simply manually run `xcodebui xcodebuild -workspace ./Example.xcworkspace -scheme Example &> ./build/build_log/Example-Release.log ``` +### If building with Buck + +If you're building with Buck, you can pipe the output to a file similarly to the Xcode example. + +``` +buck build //SwiftRocks:SwiftRocksPackage &> ./build/buck_log/SwiftRocks.log +``` + ## Configuring SwiftInfo +### If building with Xcode + SwiftInfo itself is configured by creating a `Infofile.swift` file in your project's root. Here's an example one: ```swift @@ -119,13 +131,24 @@ if isInPullRequestMode { } ``` -- 1: Use `FileUtils` to configure the path of your logs. If you're using fastlane and don't know what the name of the log files are going to be, just run it once to have it create them. +- 1: Use `FileUtils` to configure the path of your logs. If you're retrieving them with fastlane and don't know what the name of the log files are going to be, just run it once to have it create them. - 2: Create a `SwiftInfo` instance by passing your project's information. - 3: Use `SwiftInfo`'s `extract()` to extract and append all the information you want into a single property. - 4: Lastly, you can act upon this output. Here, I print the results to a pull request if [danger-SwiftInfo](https://github.com/rockbruno/danger-SwiftInfo) is being used, or send it to Slack / save it to the repo if this is the result of a release build. You can see `SwiftInfo`'s properties and methods [here.](Sources/SwiftInfoCore/SwiftInfo.swift) +### If building with Buck + +The setup for Buck projects is similar to the Xcode one, with the difference being that Buck rules use `FileUtils.buckLogFilePath` instead. If you're using a rule that isn't exclusive to Buck, you should also pass the `buildSystem: .buck` argument to the rule. + +```swift +FileUtils.buckLogFilePath = "./build/buck_log/SwiftRocks.log" +// ... +let output = api.extract(TestCountProvider.self, args: .init(buildSystem: .buck)) +// ... +``` + ## Available Arguments To be able to support different types of projects, SwiftInfo provides customization options to some providers. Click on each of them to see their documentation! @@ -136,6 +159,8 @@ To be able to support different types of projects, SwiftInfo provides customizat [📊 CodeCoverageProvider](Sources/SwiftInfoCore/Providers/CodeCoverageProvider.swift#L11) +[🎯 TestCountProvider](Sources/SwiftInfoCore/Providers/TestCountProvider.swift#L9) + ## Output After successfully extracting data, you can call `api.save(output: output)` to have SwiftInfo add/update a json file in the `{Infofile path}/SwiftInfo-output` folder. It's important to add this file to version control after the running the tool as this is what SwiftInfo uses to compare new pieces of information. diff --git a/Sources/SwiftInfo/main.swift b/Sources/SwiftInfo/main.swift index 8e05f92..a049309 100644 --- a/Sources/SwiftInfo/main.swift +++ b/Sources/SwiftInfo/main.swift @@ -7,7 +7,7 @@ public struct Main { static func run() { let fileUtils = FileUtils() let toolchainPath = getToolchainPath() - log("SwiftInfo 2.3.4") + log("SwiftInfo 2.3.5") if ProcessInfo.processInfo.arguments.contains("-version") { exit(0) } diff --git a/Sources/SwiftInfoCore/BuildSystem.swift b/Sources/SwiftInfoCore/BuildSystem.swift new file mode 100644 index 0000000..2f21ac6 --- /dev/null +++ b/Sources/SwiftInfoCore/BuildSystem.swift @@ -0,0 +1,6 @@ +import Foundation + +public enum BuildSystem { + case xcode + case buck +} diff --git a/Sources/SwiftInfoCore/FileUtils/FileUtils.swift b/Sources/SwiftInfoCore/FileUtils/FileUtils.swift index a023f89..20f9698 100644 --- a/Sources/SwiftInfoCore/FileUtils/FileUtils.swift +++ b/Sources/SwiftInfoCore/FileUtils/FileUtils.swift @@ -4,6 +4,7 @@ public struct FileUtils { public static let supportedInfofilePaths = ["./", "../", "../../", "../../../"] public static var buildLogFilePath = "" public static var testLogFilePath = "" + public static var buckLogFilePath = "" public let outputFileName = "SwiftInfoOutput.json" public let infofileName = "Infofile.swift" @@ -53,6 +54,20 @@ public struct FileUtils { } } + public func buckLog() throws -> String { + let folder = try infofileFolder() + let url = URL(fileURLWithPath: folder + FileUtils.buckLogFilePath) + do { + return try fileOpener.stringContents(ofUrl: url) + } catch { + throw SwiftInfoError.generic(""" + Buck's log not found! + Expected path: \(FileUtils.buckLogFilePath) + Thrown error: \(error.localizedDescription) + """) + } + } + public func buildLog() throws -> String { let folder = try infofileFolder() let url = URL(fileURLWithPath: folder + FileUtils.buildLogFilePath) diff --git a/Sources/SwiftInfoCore/Providers/TestCountProvider.swift b/Sources/SwiftInfoCore/Providers/TestCountProvider.swift index 8f74158..fbc1d11 100644 --- a/Sources/SwiftInfoCore/Providers/TestCountProvider.swift +++ b/Sources/SwiftInfoCore/Providers/TestCountProvider.swift @@ -2,7 +2,17 @@ import Foundation public struct TestCountProvider: InfoProvider { - public struct Args {} + public struct Args { + /// The build system that was used to test the app. + /// If Xcode was used, SwiftInfo will parse the `testLogFilePath` file, + /// but if Buck was used, `buckLogFilePath` will be used instead. + public let buildSystem: BuildSystem + + public init(buildSystem: BuildSystem = .xcode) { + self.buildSystem = buildSystem + } + } + public typealias Arguments = Args public static let identifier: String = "test_count" @@ -15,15 +25,34 @@ public struct TestCountProvider: InfoProvider { } public static func extract(fromApi api: SwiftInfo, args: Args?) throws -> TestCountProvider { - let testLog = try api.fileUtils.testLog() - let count = testLog.insensitiveMatch(regex: "Test Case '.*' passed").count + - testLog.insensitiveMatch(regex: "Test Case '.*' failed").count + let count: Int + if args?.buildSystem == .buck { + count = try getCountFromBuck(api) + } else { + count = try getCountFromXcode(api) + } guard count > 0 else { fail("Failing because 0 tests were found, and this is probably not intentional.") } return TestCountProvider(count: count) } + static func getCountFromXcode(_ api: SwiftInfo) throws -> Int { + let testLog = try api.fileUtils.testLog() + return testLog.insensitiveMatch(regex: "Test Case '.*' passed").count + + testLog.insensitiveMatch(regex: "Test Case '.*' failed").count + } + + static func getCountFromBuck(_ api: SwiftInfo) throws -> Int { + let buckLog = try api.fileUtils.buckLog() + let regexString = "s *([0-9]*) Passed *([0-9]*) Skipped *([0-9]*) Failed" + let results = buckLog.matchResults(regex: regexString) + let passed = results.compactMap { Int($0.captureGroup(1, originalString: buckLog)) } + let skipped = results.compactMap { Int($0.captureGroup(2, originalString: buckLog)) } + let failed = results.compactMap { Int($0.captureGroup(3, originalString: buckLog)) } + return (passed + skipped + failed).reduce(0, +) + } + public func summary(comparingWith other: TestCountProvider?, args: Args?) -> Summary { let prefix = description return Summary.genericFor(prefix: prefix, now: count, old: other?.count, increaseIsBad: false) { diff --git a/Sources/SwiftInfoCore/Regex.swift b/Sources/SwiftInfoCore/Regex.swift index ed2cde6..e04c2f1 100644 --- a/Sources/SwiftInfoCore/Regex.swift +++ b/Sources/SwiftInfoCore/Regex.swift @@ -1,10 +1,14 @@ import Foundation extension String { - func match(regex: String, options: NSRegularExpression.Options = []) -> [String] { + func matchResults(regex: String, options: NSRegularExpression.Options = []) -> [NSTextCheckingResult] { let regex = try! NSRegularExpression(pattern: regex, options: options) let nsString = self as NSString - return regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)).map { + return regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)) + } + + func match(regex: String, options: NSRegularExpression.Options = []) -> [String] { + return matchResults(regex: regex, options: options).map { String(self[Range($0.range, in: self)!]) } } @@ -13,3 +17,15 @@ extension String { return match(regex: regex, options: [.caseInsensitive]) } } + +extension NSTextCheckingResult { + func captureGroup(_ index: Int, originalString: String) -> String { + let groupRange = range(at: index) + let groupStartIndex = originalString.index(originalString.startIndex, + offsetBy: groupRange.location) + let groupEndIndex = originalString.index(groupStartIndex, + offsetBy: groupRange.length) + let substring = originalString[groupStartIndex.. [XCTestCaseEntry] { return [ - testCase(CoreTests.__allTests), - testCase(FileUtilsTests.__allTests), - testCase(ProviderTests.__allTests), - testCase(SlackFormatterTests.__allTests), + testCase(CoreTests.__allTests__CoreTests), + testCase(FileUtilsTests.__allTests__FileUtilsTests), + testCase(ProjectInfoDataTests.__allTests__ProjectInfoDataTests), + testCase(ProviderTests.__allTests__ProviderTests), + testCase(SlackFormatterTests.__allTests__SlackFormatterTests), ] } #endif