mirror of
https://github.com/ReactiveX/RxSwift.git
synced 2024-10-05 06:27:29 +03:00
293 lines
11 KiB
Swift
Executable File
293 lines
11 KiB
Swift
Executable File
#!/usr/bin/swift
|
|
//
|
|
// package-spm.swift
|
|
// scripts
|
|
//
|
|
// Created by Krunoslav Zaher on 12/26/15.
|
|
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/**
|
|
This script packages normal Rx* structure into `Sources` directory.
|
|
|
|
* creates and updates links to normal project structure
|
|
* builds unit tests `main.swift`
|
|
|
|
Unfortunately, Swift support for Linux, libdispatch and package manager are still quite unstable,
|
|
so certain class of unit tests is excluded for now.
|
|
|
|
*/
|
|
|
|
// It is kind of ironic that we need to additionally package for package manager :/
|
|
|
|
let fileManager = FileManager.default
|
|
|
|
let allowedExtensions = [
|
|
".swift",
|
|
".h",
|
|
".m",
|
|
]
|
|
// Those tests are dependent on conditional compilation logic and it's hard to handle them automatically
|
|
// They usually test some internal state, so it should be ok to exclude them for now.
|
|
let excludedTests: [String] = [
|
|
"testConcat_TailRecursionCollection",
|
|
"testConcat_TailRecursionSequence",
|
|
"testMapCompose_OptimizationIsPerformed",
|
|
"testMapCompose_OptimizationIsNotPerformed",
|
|
"testObserveOn_EnsureCorrectImplementationIsChosen",
|
|
"testObserveOnDispatchQueue_EnsureCorrectImplementationIsChosen",
|
|
"testResourceLeaksDetectionIsTurnedOn",
|
|
"testAnonymousObservable_disposeReferenceDoesntRetainObservable",
|
|
"testObserveOnDispatchQueue_DispatchQueueSchedulerIsSerial"
|
|
]
|
|
|
|
let excludedTestClasses: [String] = [
|
|
/*"ObservableConcurrentSchedulerConcurrencyTest",
|
|
"SubjectConcurrencyTest",
|
|
"VirtualSchedulerTest",
|
|
"HistoricalSchedulerTest"*/
|
|
"BagTest"
|
|
]
|
|
|
|
let throwingWordsInTests: [String] = [
|
|
/*"error",
|
|
"fail",
|
|
"throw",
|
|
"retrycount",
|
|
"retrywhen",*/
|
|
]
|
|
|
|
func isExtensionAllowed(_ path: String) -> Bool {
|
|
return (allowedExtensions.map { path.hasSuffix($0) }).reduce(false) { $0 || $1 }
|
|
}
|
|
|
|
func checkExtension(_ path: String) throws {
|
|
if !isExtensionAllowed(path) {
|
|
throw NSError(domain: "Security", code: -1, userInfo: ["path" : path])
|
|
}
|
|
}
|
|
|
|
func packageRelativePath(_ paths: [String], targetDirName: String, excluded: [String] = []) throws {
|
|
let targetPath = "Sources/\(targetDirName)"
|
|
|
|
print("Checking " + targetPath)
|
|
|
|
for file in try fileManager.contentsOfDirectory(atPath: targetPath) {
|
|
if file != "include" && file != ".DS_Store" {
|
|
print("Checking extension \(file)")
|
|
try checkExtension(file)
|
|
|
|
print("Cleaning \(file)")
|
|
try fileManager.removeItem(atPath: "\(targetPath)/\(file)")
|
|
}
|
|
}
|
|
|
|
for sourcePath in paths {
|
|
var isDirectory: ObjCBool = false
|
|
fileManager.fileExists(atPath: sourcePath, isDirectory: &isDirectory)
|
|
|
|
let files: [String] = isDirectory.boolValue ? fileManager.subpaths(atPath: sourcePath)!
|
|
: [sourcePath]
|
|
|
|
for file in files {
|
|
if !isExtensionAllowed(file) {
|
|
print("Skipping \(file)")
|
|
continue
|
|
}
|
|
|
|
if excluded.contains(file) {
|
|
print("Skipping \(file)")
|
|
continue
|
|
}
|
|
|
|
let fileRelativePath = isDirectory.boolValue ? "\(sourcePath)/\(file)" : file
|
|
|
|
let destinationURL = NSURL(string: "../../\(fileRelativePath)")!
|
|
|
|
let fileName = (file as NSString).lastPathComponent
|
|
let atURL = NSURL(string: "file:///\(fileManager.currentDirectoryPath)/\(targetPath)/\(fileName)")!
|
|
|
|
if fileName.hasSuffix(".h") {
|
|
let sourcePath = NSURL(string: "file:///" + fileManager.currentDirectoryPath + "/" + sourcePath + "/" + file)!
|
|
//throw NSError(domain: sourcePath.description, code: -1, userInfo: nil)
|
|
try fileManager.copyItem(at: sourcePath as URL, to: atURL as URL)
|
|
}
|
|
else {
|
|
print("Linking \(fileName) [\(atURL)] -> \(destinationURL)")
|
|
try fileManager.createSymbolicLink(at: atURL as URL, withDestinationURL: destinationURL as URL)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func buildAllTestsTarget(_ testsPath: String) throws {
|
|
let splitClasses = "(?:class|extension)\\s+(\\w+)"
|
|
let testMethods = "\\s+func\\s+(test\\w+)"
|
|
|
|
let splitClassesRegularExpression = try! NSRegularExpression(pattern: splitClasses, options:[])
|
|
let testMethodsExpression = try! NSRegularExpression(pattern: testMethods, options: [])
|
|
|
|
var reducedMethods: [String: [String]] = [:]
|
|
|
|
for file in try fileManager.contentsOfDirectory(atPath: testsPath) {
|
|
if !file.hasSuffix(".swift") || file == "main.swift" {
|
|
continue
|
|
}
|
|
|
|
let fileRelativePath = "\(testsPath)/\(file)"
|
|
let testContent = try String(contentsOfFile: fileRelativePath, encoding: String.Encoding.utf8)
|
|
|
|
print(fileRelativePath)
|
|
|
|
let classMatches = splitClassesRegularExpression.matches(in: testContent as String, options: [], range: NSRange(location: 0, length: testContent.characters.count))
|
|
let matchIndexes = classMatches
|
|
.map { $0.range.location }
|
|
let classNames = classMatches.map { (testContent as NSString).substring(with: $0.rangeAt(1)) as NSString }
|
|
|
|
let ranges = zip([0] + matchIndexes, matchIndexes + [testContent.characters.count]).map { NSRange(location: $0, length: $1 - $0) }
|
|
let classRanges = ranges[1 ..< ranges.count]
|
|
|
|
let classes = zip(classNames, classRanges.map { (testContent as NSString).substring(with: $0) as NSString })
|
|
|
|
for (name, classCode) in classes {
|
|
if excludedTestClasses.contains(name as String) {
|
|
print("Skipping \(name)")
|
|
continue
|
|
}
|
|
|
|
let methodMatches = testMethodsExpression.matches(in: classCode as String, options: [], range: NSRange(location: 0, length: classCode.length))
|
|
let methodNameRanges = methodMatches.map { $0.rangeAt(1) }
|
|
let testMethodNames = methodNameRanges
|
|
.map { classCode.substring(with: $0) }
|
|
.filter { !excludedTests.contains($0) }
|
|
|
|
if testMethodNames.count == 0 {
|
|
continue
|
|
}
|
|
|
|
let existingMethods = reducedMethods[name as String] ?? []
|
|
reducedMethods[name as String] = existingMethods + testMethodNames
|
|
}
|
|
}
|
|
|
|
var mainContent = [String]()
|
|
|
|
mainContent.append("// this file is autogenerated using `./scripts/package-swift-manager.swift`")
|
|
mainContent.append("import XCTest")
|
|
mainContent.append("import RxSwift")
|
|
mainContent.append("")
|
|
mainContent.append("protocol RxTestCase {")
|
|
mainContent.append("#if os(macOS)")
|
|
mainContent.append(" init()")
|
|
mainContent.append(" static var allTests: [(String, (Self) -> () -> ())] { get }")
|
|
mainContent.append("#endif")
|
|
mainContent.append(" func setUp()")
|
|
mainContent.append(" func tearDown()")
|
|
mainContent.append("}")
|
|
mainContent.append("")
|
|
|
|
for (name, methods) in reducedMethods {
|
|
|
|
mainContent.append("")
|
|
mainContent.append("final class \(name)_ : \(name), RxTestCase {")
|
|
mainContent.append(" #if os(macOS)")
|
|
mainContent.append(" required override init() {")
|
|
mainContent.append(" super.init()")
|
|
mainContent.append(" }")
|
|
mainContent.append(" #endif")
|
|
mainContent.append("")
|
|
mainContent.append(" static var allTests: [(String, (\(name)_) -> () -> ())] { return [")
|
|
for method in methods {
|
|
// throwing error on Linux, you will crash
|
|
let isTestCaseHandlingError = throwingWordsInTests.map { (method as String).lowercased().contains($0) }.reduce(false) { $0 || $1 }
|
|
mainContent.append(" \(isTestCaseHandlingError ? "//" : "")(\"\(method)\", \(name).\(method)),")
|
|
}
|
|
mainContent.append(" ] }")
|
|
mainContent.append("}")
|
|
}
|
|
|
|
mainContent.append("#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)")
|
|
mainContent.append("")
|
|
mainContent.append("func testCase<T: RxTestCase>(_ tests: [(String, (T) -> () -> ())]) -> () -> () {")
|
|
mainContent.append(" return {")
|
|
mainContent.append(" for testCase in tests {")
|
|
mainContent.append(" print(\"Test \\(testCase)\")")
|
|
mainContent.append(" for test in T.allTests {")
|
|
mainContent.append(" let testInstance = T()")
|
|
mainContent.append(" testInstance.setUp()")
|
|
mainContent.append(" print(\" testing \\(test.0)\")")
|
|
mainContent.append(" test.1(testInstance)()")
|
|
mainContent.append(" testInstance.tearDown()")
|
|
mainContent.append(" }")
|
|
mainContent.append(" }")
|
|
mainContent.append(" }")
|
|
mainContent.append("}")
|
|
mainContent.append("")
|
|
mainContent.append("func XCTMain(_ tests: [() -> ()]) {")
|
|
mainContent.append(" for testCase in tests {")
|
|
mainContent.append(" testCase()")
|
|
mainContent.append(" }")
|
|
mainContent.append("}")
|
|
mainContent.append("")
|
|
mainContent.append("#endif")
|
|
mainContent.append("")
|
|
mainContent.append(" XCTMain([")
|
|
for testCase in reducedMethods.keys {
|
|
mainContent.append(" testCase(\(testCase)_.allTests),")
|
|
}
|
|
mainContent.append(" ])")
|
|
mainContent.append("//}")
|
|
mainContent.append("")
|
|
|
|
let serializedMainContent = mainContent.joined(separator: "\n")
|
|
try serializedMainContent.write(toFile: "\(testsPath)/main.swift", atomically: true, encoding: String.Encoding.utf8)
|
|
}
|
|
|
|
|
|
try packageRelativePath(["RxSwift"], targetDirName: "RxSwift")
|
|
//try packageRelativePath(["RxCocoa/Common", "RxCocoa/macOS", "RxCocoa/RxCocoa.h"], targetDirName: "RxCocoa")
|
|
|
|
try packageRelativePath([
|
|
"RxCocoa/RxCocoa.swift",
|
|
"RxCocoa/CocoaUnits",
|
|
"RxCocoa/Common",
|
|
"RxCocoa/Foundation",
|
|
"RxCocoa/iOS",
|
|
"RxCocoa/macOS",
|
|
], targetDirName: "RxCocoa")
|
|
try packageRelativePath([
|
|
"RxCocoa/Runtime/include",
|
|
], targetDirName: "RxCocoaRuntime/include")
|
|
try packageRelativePath([
|
|
"RxCocoa/Runtime/_RX.m",
|
|
"RxCocoa/Runtime/_RXDelegateProxy.m",
|
|
"RxCocoa/Runtime/_RXKVOObserver.m",
|
|
"RxCocoa/Runtime/_RXObjCRuntime.m",
|
|
], targetDirName: "RxCocoaRuntime")
|
|
|
|
try packageRelativePath(["RxBlocking"], targetDirName: "RxBlocking")
|
|
try packageRelativePath(["RxTest"], targetDirName: "RxTest")
|
|
// It doesn't work under `Tests` subpath ¯\_(ツ)_/¯
|
|
try packageRelativePath([
|
|
"Tests/RxSwiftTests",
|
|
"Tests/RxBlockingTests",
|
|
"RxSwift/RxMutableBox.swift",
|
|
"Tests/RxTest.swift",
|
|
"Tests/Recorded+Timeless.swift",
|
|
"Tests/TestErrors.swift",
|
|
"Tests/XCTest+AllTests.swift",
|
|
"Platform",
|
|
],
|
|
targetDirName: "AllTestz",
|
|
excluded: [
|
|
"Tests/VirtualSchedulerTest.swift",
|
|
"Tests/HistoricalSchedulerTest.swift",
|
|
// @testable import doesn't work well in Linux :/
|
|
"BagTest.swift"
|
|
])
|
|
|
|
try buildAllTestsTarget("Sources/AllTestz")
|
|
|