diff --git a/Sources/App/Commands/Analyze.swift b/Sources/App/Commands/Analyze.swift index 071296525..691c9c425 100644 --- a/Sources/App/Commands/Analyze.swift +++ b/Sources/App/Commands/Analyze.swift @@ -87,9 +87,10 @@ extension Analyze { .forEach { pair in guard let (path, mod) = pair else { return } @Dependency(\.date.now) var now + @Dependency(\.fileManager) var fileManager let cutoff = now.addingTimeInterval(-Constants.gitCheckoutMaxAge) if mod < cutoff { - try Current.fileManager.removeItem(atPath: path) + try fileManager.removeItem(atPath: path) AppMetrics.analyzeTrimCheckoutsCount?.inc() } } @@ -143,7 +144,7 @@ extension Analyze { @Dependency(\.fileManager) var fileManager let checkoutDir = fileManager.checkoutsDirectory() Current.logger().info("Checkout directory: \(checkoutDir)") - if !Current.fileManager.fileExists(atPath: checkoutDir) { + if !fileManager.fileExists(atPath: checkoutDir) { try await createCheckoutsDirectory(client: client, path: checkoutDir) } @@ -236,9 +237,10 @@ extension Analyze { path: String) async throws { Current.logger().info("Creating checkouts directory at path: \(path)") do { - try Current.fileManager.createDirectory(atPath: path, - withIntermediateDirectories: false, - attributes: nil) + @Dependency(\.fileManager) var fileManager + try fileManager.createDirectory(atPath: path, + withIntermediateDirectories: false, + attributes: nil) } catch { let error = AppError.genericError(nil, "Failed to create checkouts directory: \(error.localizedDescription)") Current.logger().report(error: error) @@ -266,11 +268,12 @@ extension Analyze { /// - url: url to fetch from /// - Throws: Shell errors static func fetch(cacheDir: String, branch: String, url: String) async throws { + @Dependency(\.fileManager) var fileManager Current.logger().info("pulling \(url) in \(cacheDir)") // clean up stray lock files that might have remained from aborted commands for fileName in ["HEAD.lock", "index.lock"] { let filePath = cacheDir + "/.git/\(fileName)" - if Current.fileManager.fileExists(atPath: filePath) { + if fileManager.fileExists(atPath: filePath) { Current.logger().info("Removing stale \(fileName) at path: \(filePath)") try await Current.shell.run(command: .removeFile(from: filePath)) } @@ -295,7 +298,7 @@ extension Analyze { } do { - guard Current.fileManager.fileExists(atPath: cacheDir) else { + guard fileManager.fileExists(atPath: cacheDir) else { try await clone(cacheDir: cacheDir, url: package.model.url) return } @@ -529,7 +532,8 @@ extension Analyze { /// - Throws: Shell errors or AppError.invalidRevision if there is no Package.swift file /// - Returns: `Manifest` data static func dumpPackage(at path: String) async throws -> Manifest { - guard Current.fileManager.fileExists(atPath: path + "/Package.swift") else { + @Dependency(\.fileManager) var fileManager + guard fileManager.fileExists(atPath: path + "/Package.swift") else { // It's important to check for Package.swift - otherwise `dump-package` will go // up the tree through parent directories to find one throw AppError.invalidRevision(nil, "no Package.swift") diff --git a/Sources/App/Commands/ReAnalyzeVersions.swift b/Sources/App/Commands/ReAnalyzeVersions.swift index c428305a8..aab0b5d76 100644 --- a/Sources/App/Commands/ReAnalyzeVersions.swift +++ b/Sources/App/Commands/ReAnalyzeVersions.swift @@ -185,7 +185,7 @@ enum ReAnalyzeVersions { try await dependencies.yield { @Dependency(\.fileManager) var fileManager guard let cacheDir = fileManager.cacheDirectoryPath(for: pkg.model) else { return } - if !Current.fileManager.fileExists(atPath: cacheDir) || refreshCheckouts { + if !fileManager.fileExists(atPath: cacheDir) || refreshCheckouts { try await Analyze.refreshCheckout(package: pkg) } diff --git a/Sources/App/Core/AppEnvironment.swift b/Sources/App/Core/AppEnvironment.swift index c6ee9f448..2197cb64a 100644 --- a/Sources/App/Core/AppEnvironment.swift +++ b/Sources/App/Core/AppEnvironment.swift @@ -23,7 +23,6 @@ import FoundationNetworking struct AppEnvironment: Sendable { - var fileManager: FileManager var git: Git var logger: @Sendable () -> Logger var setLogger: @Sendable (Logger) -> Void @@ -35,7 +34,6 @@ extension AppEnvironment { nonisolated(unsafe) static var logger: Logger! static let live = AppEnvironment( - fileManager: .live, git: .live, logger: { logger }, setLogger: { logger in Self.logger = logger }, @@ -44,29 +42,6 @@ extension AppEnvironment { } -struct FileManager: Sendable { - var createDirectory: @Sendable (String, Bool, [FileAttributeKey : Any]?) throws -> Void - var fileExists: @Sendable (String) -> Bool - var removeItem: @Sendable (_ path: String) throws -> Void - var workingDirectory: @Sendable () -> String - - // pass-through methods to preserve argument labels - func createDirectory(atPath path: String, - withIntermediateDirectories createIntermediates: Bool, - attributes: [FileAttributeKey : Any]?) throws { - try createDirectory(path, createIntermediates, attributes) - } - func fileExists(atPath path: String) -> Bool { fileExists(path) } - func removeItem(atPath path: String) throws { try removeItem(path) } - - static let live: Self = .init( - createDirectory: { try Foundation.FileManager.default.createDirectory(atPath: $0, withIntermediateDirectories: $1, attributes: $2) }, - fileExists: { Foundation.FileManager.default.fileExists(atPath: $0) }, - removeItem: { try Foundation.FileManager.default.removeItem(atPath: $0) }, - workingDirectory: { DirectoryConfiguration.detect().workingDirectory } - ) -} - struct Git: Sendable { var commitCount: @Sendable (String) async throws -> Int diff --git a/Sources/App/Core/Dependencies/FileManagerClient.swift b/Sources/App/Core/Dependencies/FileManagerClient.swift index 65a3634cc..f3035353e 100644 --- a/Sources/App/Core/Dependencies/FileManagerClient.swift +++ b/Sources/App/Core/Dependencies/FileManagerClient.swift @@ -26,6 +26,10 @@ struct FileManagerClient { var checkoutsDirectory: @Sendable () -> String = { reportIssue("checkoutsDirectory"); return "SPI-checkouts" } var contents: @Sendable (_ atPath: String) -> Data? var contentsOfDirectory: @Sendable (_ atPath: String) throws -> [String] + var createDirectory: @Sendable (_ atPath: String, _ withIntermediateDirectories: Bool, _ attributes: [FileAttributeKey : Any]?) throws -> Void + var fileExists: @Sendable (_ atPath: String) -> Bool = { reportIssue("fileExists"); return Foundation.FileManager.default.fileExists(atPath: $0) } + var removeItem: @Sendable (_ atPath: String) throws -> Void + var workingDirectory: @Sendable () -> String = { reportIssue("workingDirectory"); return "" } } @@ -43,7 +47,11 @@ extension FileManagerClient: DependencyKey { attributesOfItem: { try Foundation.FileManager.default.attributesOfItem(atPath: $0) }, checkoutsDirectory: { Environment.get("CHECKOUTS_DIR") ?? DirectoryConfiguration.detect().workingDirectory + "SPI-checkouts" }, contents: { Foundation.FileManager.default.contents(atPath: $0) }, - contentsOfDirectory: { try Foundation.FileManager.default.contentsOfDirectory(atPath: $0) } + contentsOfDirectory: { try Foundation.FileManager.default.contentsOfDirectory(atPath: $0) }, + createDirectory: { try Foundation.FileManager.default.createDirectory(atPath: $0, withIntermediateDirectories: $1, attributes: $2) }, + fileExists: { Foundation.FileManager.default.fileExists(atPath: $0) }, + removeItem: { try Foundation.FileManager.default.removeItem(atPath: $0) }, + workingDirectory: { DirectoryConfiguration.detect().workingDirectory } ) } } @@ -54,6 +62,7 @@ extension FileManagerClient: TestDependencyKey { var mock = Self() // Override the `unimplemented` default because it is a very common dependency. mock.checkoutsDirectory = { "SPI-checkouts" } + mock.workingDirectory = { DirectoryConfiguration.detect().workingDirectory } return mock } } diff --git a/Sources/App/Core/Emoji.swift b/Sources/App/Core/Emoji.swift index 01ce129d7..c3dd1f8ae 100644 --- a/Sources/App/Core/Emoji.swift +++ b/Sources/App/Core/Emoji.swift @@ -13,6 +13,8 @@ // limitations under the License. import Foundation + +import Dependencies import Vapor @@ -33,8 +35,8 @@ struct EmojiStorage { var regularExpression: NSRegularExpression? init() { - let pathToEmojiFile = Current.fileManager.workingDirectory() - .appending("Resources/emoji.json") + @Dependency(\.fileManager) var fileManager + let pathToEmojiFile = fileManager.workingDirectory().appending("Resources/emoji.json") lookup = [:] regularExpression = nil diff --git a/Sources/App/Core/Extensions/Badge.swift b/Sources/App/Core/Extensions/Badge.swift index a1d9fdec6..9abb2f5d1 100644 --- a/Sources/App/Core/Extensions/Badge.swift +++ b/Sources/App/Core/Extensions/Badge.swift @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import Vapor import Foundation +import Dependencies +import Vapor + struct Badge: Content, Equatable { var schemaVersion: Int @@ -68,8 +70,8 @@ enum BadgeType: String, Codable { extension Badge { static private func loadSVGLogo() -> String? { - let pathToFile = Current.fileManager.workingDirectory() - .appending("Public/images/logo-tiny.svg") + @Dependency(\.fileManager) var fileManager + let pathToFile = fileManager.workingDirectory().appending("Public/images/logo-tiny.svg") return try? String(contentsOfFile: pathToFile, encoding: .utf8) } diff --git a/Sources/App/Core/PackageCollection+signing.swift b/Sources/App/Core/PackageCollection+signing.swift index 329c5d79e..4cbbe1b10 100644 --- a/Sources/App/Core/PackageCollection+signing.swift +++ b/Sources/App/Core/PackageCollection+signing.swift @@ -59,9 +59,12 @@ extension SignedCollection { return true } - static let certsDir = URL(fileURLWithPath: Current.fileManager.workingDirectory()) - .appendingPathComponent("Resources") - .appendingPathComponent("Certs") + static var certsDir: URL { + @Dependency(\.fileManager) var fileManager + return URL(fileURLWithPath: fileManager.workingDirectory()) + .appendingPathComponent("Resources") + .appendingPathComponent("Certs") + } static let signer = PackageCollectionSigning( trustedRootCertsDir: certsDir, diff --git a/Sources/App/Core/PackageContributors.swift b/Sources/App/Core/PackageContributors.swift index 49532bf30..a6823a346 100644 --- a/Sources/App/Core/PackageContributors.swift +++ b/Sources/App/Core/PackageContributors.swift @@ -13,6 +13,8 @@ // limitations under the License. import Foundation + +import Dependencies import ShellOut import Vapor @@ -65,8 +67,8 @@ enum PackageContributors { /// Gets the git history in a string log private static func runShortlog(gitCacheDirectoryPath: String, packageID: UUID?) async throws -> String { - - if Current.fileManager.fileExists(atPath: gitCacheDirectoryPath) == false { + @Dependency(\.fileManager) var fileManager + if fileManager.fileExists(atPath: gitCacheDirectoryPath) == false { throw AppError.cacheDirectoryDoesNotExist(packageID, gitCacheDirectoryPath) } diff --git a/Sources/App/Views/Blog/BlogActions+Model.swift b/Sources/App/Views/Blog/BlogActions+Model.swift index aede699a1..941ed0ce3 100644 --- a/Sources/App/Views/Blog/BlogActions+Model.swift +++ b/Sources/App/Views/Blog/BlogActions+Model.swift @@ -25,7 +25,8 @@ enum BlogActions { struct Model { static var blogIndexYmlPath: String { - Current.fileManager.workingDirectory().appending("Resources/Blog/posts.yml") + @Dependency(\.fileManager) var fileManager + return fileManager.workingDirectory().appending("Resources/Blog/posts.yml") } struct PostSummary: Equatable { @@ -80,7 +81,7 @@ extension BlogActions.Model.PostSummary { var postMarkdown: String { @Dependency(\.fileManager) var fileManager - let markdownPath = Current.fileManager.workingDirectory() + let markdownPath = fileManager.workingDirectory() .appending("Resources/Blog/Posts/") .appending(slug) .appending(".md") diff --git a/Sources/App/Views/MarkdownPage.swift b/Sources/App/Views/MarkdownPage.swift index ce176f0a4..4ae8d1a51 100644 --- a/Sources/App/Views/MarkdownPage.swift +++ b/Sources/App/Views/MarkdownPage.swift @@ -13,8 +13,10 @@ // limitations under the License. import Foundation -import Plot + +import Dependencies import Ink +import Plot class MarkdownPage: PublicPage { @@ -28,7 +30,8 @@ class MarkdownPage: PublicPage { let html: String? init(path: String, _ markdownFilename: String) { - let pathToMarkdownFile = Current.fileManager.workingDirectory() + @Dependency(\.fileManager) var fileManager + let pathToMarkdownFile = fileManager.workingDirectory() .appending("Resources/Markdown/") .appending(markdownFilename) diff --git a/Sources/App/Views/ReadyForSwift6/ReadyForSwift6Show+Model.swift b/Sources/App/Views/ReadyForSwift6/ReadyForSwift6Show+Model.swift index 71b1b9810..840474298 100644 --- a/Sources/App/Views/ReadyForSwift6/ReadyForSwift6Show+Model.swift +++ b/Sources/App/Views/ReadyForSwift6/ReadyForSwift6Show+Model.swift @@ -29,8 +29,10 @@ enum ReadyForSwift6Show { func readyForSwift6Chart(kind: ChartKind, includeTotals: Bool = false) -> Node { @Dependency(\.fileManager) var fileManager - let plotDataPath = Current.fileManager.workingDirectory().appending("Resources/ChartData/\(kind.dataFile)") - let eventDataPath = Current.fileManager.workingDirectory().appending("Resources/ChartData/rfs6-events.json") + let plotDataPath = fileManager.workingDirectory() + .appending("Resources/ChartData/\(kind.dataFile)") + let eventDataPath = fileManager.workingDirectory() + .appending("Resources/ChartData/rfs6-events.json") guard let plotData = fileManager.contents(atPath: plotDataPath)?.compactJson(), let eventData = fileManager.contents(atPath: eventDataPath)?.compactJson() else { return .p("Couldn’t load chart data.") } diff --git a/Tests/AppTests/AnalyzeErrorTests.swift b/Tests/AppTests/AnalyzeErrorTests.swift index 9d1dac693..53fd289f5 100644 --- a/Tests/AppTests/AnalyzeErrorTests.swift +++ b/Tests/AppTests/AnalyzeErrorTests.swift @@ -115,6 +115,7 @@ final class AnalyzeErrorTests: AppTestCase { func test_analyze_refreshCheckout_failed() async throws { try await withDependencies { $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } } operation: { Current.shell.run = { @Sendable cmd, path in switch cmd { @@ -130,9 +131,7 @@ final class AnalyzeErrorTests: AppTestCase { } // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) + try await Analyze.analyze(client: app.client, database: app.db, mode: .limit(10)) // validate try await defaultValidation() @@ -147,6 +146,7 @@ final class AnalyzeErrorTests: AppTestCase { func test_analyze_updateRepository_invalidPackageCachePath() async throws { try await withDependencies { $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } } operation: { // setup let pkg = try await Package.find(badPackageID, on: app.db).unwrap() @@ -157,9 +157,7 @@ final class AnalyzeErrorTests: AppTestCase { try await pkg.save(on: app.db) // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) + try await Analyze.analyze(client: app.client, database: app.db, mode: .limit(10)) // validate try await defaultValidation() @@ -174,6 +172,7 @@ final class AnalyzeErrorTests: AppTestCase { func test_analyze_getPackageInfo_gitCheckout_error() async throws { try await withDependencies { $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } } operation: { // setup Current.shell.run = { @Sendable cmd, path in @@ -187,9 +186,7 @@ final class AnalyzeErrorTests: AppTestCase { } // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) + try await Analyze.analyze(client: app.client, database: app.db, mode: .limit(10)) // validate try await defaultValidation() @@ -204,15 +201,13 @@ final class AnalyzeErrorTests: AppTestCase { func test_analyze_dumpPackage_missing_manifest() async throws { try await withDependencies { $0.environment.loadSPIManifest = { _ in nil } - } operation: { - // setup - Current.fileManager.fileExists = { @Sendable path in + $0.fileManager.fileExists = { @Sendable path in if path.hasSuffix("github.com-foo-1/Package.swift") { return false } return true } - + } operation: { // MUT try await Analyze.analyze(client: app.client, database: app.db, diff --git a/Tests/AppTests/AnalyzerTests.swift b/Tests/AppTests/AnalyzerTests.swift index 6df84f97e..b0f870d20 100644 --- a/Tests/AppTests/AnalyzerTests.swift +++ b/Tests/AppTests/AnalyzerTests.swift @@ -33,6 +33,8 @@ class AnalyzerTests: AppTestCase { // End-to-end test, where we mock at the shell command level (i.e. we // don't mock the git commands themselves to ensure we're running the // expected shell commands for the happy path.) + let checkoutDir = QueueIsolated(nil) + let firstDirCloned = QueueIsolated(false) try await withDependencies { $0.date.now = .now $0.environment.allowSocialPosts = { true } @@ -43,6 +45,17 @@ class AnalyzerTests: AppTestCase { return nil } } + $0.fileManager.createDirectory = { @Sendable path, _, _ in checkoutDir.setValue(path) } + $0.fileManager.fileExists = { @Sendable path in + if let outDir = checkoutDir.value, + path == "\(outDir)/github.com-foo-1" { return firstDirCloned.value } + // let the check for the second repo checkout path succeed to simulate pull + if let outDir = checkoutDir.value, + path == "\(outDir)/github.com-foo-2" { return true } + if path.hasSuffix("Package.swift") { return true } + if path.hasSuffix("Package.resolved") { return true } + return false + } $0.httpClient.mastodonPost = { @Sendable _ in } } operation: { // setup @@ -62,20 +75,7 @@ class AnalyzerTests: AppTestCase { owner: "foo", stars: 100).save(on: app.db) - let checkoutDir = QueueIsolated(nil) let commands = QueueIsolated<[Command]>([]) - let firstDirCloned = QueueIsolated(false) - Current.fileManager.fileExists = { @Sendable path in - if let outDir = checkoutDir.value, - path == "\(outDir)/github.com-foo-1" { return firstDirCloned.value } - // let the check for the second repo checkout path succeed to simulate pull - if let outDir = checkoutDir.value, - path == "\(outDir)/github.com-foo-2" { return true } - if path.hasSuffix("Package.swift") { return true } - if path.hasSuffix("Package.resolved") { return true } - return false - } - Current.fileManager.createDirectory = { @Sendable path, _, _ in checkoutDir.setValue(path) } Current.git = .live Current.shell.run = { @Sendable cmd, path in let trimmedPath = path.replacingOccurrences(of: checkoutDir.value!, with: ".") @@ -220,6 +220,7 @@ class AnalyzerTests: AppTestCase { $0.date.now = .now $0.environment.allowSocialPosts = { true } $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } $0.httpClient.mastodonPost = { @Sendable _ in } } operation: { // setup @@ -244,8 +245,6 @@ class AnalyzerTests: AppTestCase { packageName: "foo-1", reference: .tag(1, 0, 0)).save(on: app.db) - Current.fileManager.fileExists = { @Sendable _ in true } - Current.git.commitCount = { @Sendable _ in 12 } Current.git.firstCommitDate = { @Sendable _ in .t0 } Current.git.lastCommitDate = { @Sendable _ in .t2 } @@ -315,6 +314,7 @@ class AnalyzerTests: AppTestCase { // Ensure a package that fails analysis goes back to ingesting and isn't stuck in an analysis loop try await withDependencies { $0.date.now = .now + $0.fileManager.fileExists = { @Sendable _ in true } } operation: { // setup do { @@ -334,9 +334,7 @@ class AnalyzerTests: AppTestCase { try await XCTAssertEqualAsync(try await Package.fetchCandidates(app.db, for: .analysis, limit: 10).count, 1) // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) + try await Analyze.analyze(client: app.client, database: app.db, mode: .limit(10)) // Ensure candidate selection is now zero for analysis // (and also for ingestion, as we're immediately after analysis) @@ -361,6 +359,7 @@ class AnalyzerTests: AppTestCase { $0.date.now = .now $0.environment.allowSocialPosts = { true } $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } } operation: { // setup let urls = ["https://github.com/foo/1", "https://github.com/foo/2"] @@ -396,9 +395,7 @@ class AnalyzerTests: AppTestCase { } // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) + try await Analyze.analyze(client: app.client, database: app.db, mode: .limit(10)) // assert packages have been updated let packages = try await Package.query(on: app.db).sort(\.$createdAt).all() @@ -409,10 +406,18 @@ class AnalyzerTests: AppTestCase { func test_continue_on_exception() async throws { // Test to ensure exceptions don't interrupt processing + let checkoutDir: NIOLockedValueBox = .init(nil) try await withDependencies { $0.date.now = .now $0.environment.allowSocialPosts = { true } $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.createDirectory = { @Sendable path, _, _ in checkoutDir.withLockedValue { $0 = path } } + $0.fileManager.fileExists = { @Sendable path in + if let outDir = checkoutDir.withLockedValue({ $0 }), path == "\(outDir)/github.com-foo-1" { return true } + if let outDir = checkoutDir.withLockedValue({ $0 }), path == "\(outDir)/github.com-foo-2" { return true } + if path.hasSuffix("Package.swift") { return true } + return false + } } operation: { // setup let urls = ["https://github.com/foo/1", "https://github.com/foo/2"] @@ -420,15 +425,6 @@ class AnalyzerTests: AppTestCase { for p in pkgs { try await Repository(package: p, defaultBranch: "main").save(on: app.db) } - let checkoutDir: NIOLockedValueBox = .init(nil) - - Current.fileManager.fileExists = { @Sendable path in - if let outDir = checkoutDir.withLockedValue({ $0 }), path == "\(outDir)/github.com-foo-1" { return true } - if let outDir = checkoutDir.withLockedValue({ $0 }), path == "\(outDir)/github.com-foo-2" { return true } - if path.hasSuffix("Package.swift") { return true } - return false - } - Current.fileManager.createDirectory = { @Sendable path, _, _ in checkoutDir.withLockedValue { $0 = path } } Current.git = .live @@ -501,58 +497,65 @@ class AnalyzerTests: AppTestCase { @MainActor func test_refreshCheckout() async throws { - // setup - let pkg = try await savePackage(on: app.db, "1".asGithubUrl.url) - try await Repository(package: pkg, defaultBranch: "main").save(on: app.db) - Current.fileManager.fileExists = { @Sendable _ in true } - let commands = QueueIsolated<[String]>([]) - Current.shell.run = { @Sendable cmd, path in - commands.withValue { $0.append(cmd.description) } - return "" - } - let jpr = try await Package.fetchCandidate(app.db, id: pkg.id!) + try await withDependencies { + $0.fileManager.fileExists = { @Sendable _ in true } + } operation: { + // setup + let pkg = try await savePackage(on: app.db, "1".asGithubUrl.url) + try await Repository(package: pkg, defaultBranch: "main").save(on: app.db) + let commands = QueueIsolated<[String]>([]) + Current.shell.run = { @Sendable cmd, path in + commands.withValue { $0.append(cmd.description) } + return "" + } + let jpr = try await Package.fetchCandidate(app.db, id: pkg.id!) - // MUT - _ = try await Analyze.refreshCheckout(package: jpr) + // MUT + _ = try await Analyze.refreshCheckout(package: jpr) - // validate - assertSnapshot(of: commands.value, as: .dump) + // validate + assertSnapshot(of: commands.value, as: .dump) + } } func test_updateRepository() async throws { - // setup - Current.git.commitCount = { @Sendable _ in 12 } - Current.git.firstCommitDate = { @Sendable _ in .t0 } - Current.git.lastCommitDate = { @Sendable _ in .t1 } - Current.git.shortlog = { @Sendable _ in + try await withDependencies { + $0.fileManager.fileExists = { @Sendable _ in true } + } operation: { + // setup + Current.git.commitCount = { @Sendable _ in 12 } + Current.git.firstCommitDate = { @Sendable _ in .t0 } + Current.git.lastCommitDate = { @Sendable _ in .t1 } + Current.git.shortlog = { @Sendable _ in """ 10\tPerson 1 2\tPerson 2 """ - } - Current.shell.run = { @Sendable cmd, _ in throw TestError.unknownCommand } - let pkg = Package(id: .id0, url: "1".asGithubUrl.url) - try await pkg.save(on: app.db) - try await Repository(id: .id1, package: pkg, defaultBranch: "main").save(on: app.db) - let jpr = try await Package.fetchCandidate(app.db, id: pkg.id!) + } + Current.shell.run = { @Sendable cmd, _ in throw TestError.unknownCommand } + let pkg = Package(id: .id0, url: "1".asGithubUrl.url) + try await pkg.save(on: app.db) + try await Repository(id: .id1, package: pkg, defaultBranch: "main").save(on: app.db) + let jpr = try await Package.fetchCandidate(app.db, id: pkg.id!) - // MUT - try await Analyze.updateRepository(on: app.db, package: jpr) + // MUT + try await Analyze.updateRepository(on: app.db, package: jpr) - // validate - do { // ensure JPR relation is updated - XCTAssertEqual(jpr.repository?.commitCount, 12) - XCTAssertEqual(jpr.repository?.firstCommitDate, .t0) - XCTAssertEqual(jpr.repository?.lastCommitDate, .t1) - XCTAssertEqual(jpr.repository?.authors, PackageAuthors(authors: [Author(name: "Person 1")], - numberOfContributors: 1)) - } - do { // ensure changes are persisted - let repo = try await Repository.find(.id1, on: app.db) - XCTAssertEqual(repo?.commitCount, 12) - XCTAssertEqual(repo?.firstCommitDate, .t0) - XCTAssertEqual(repo?.lastCommitDate, .t1) - XCTAssertEqual(repo?.authors, PackageAuthors(authors: [Author(name: "Person 1")], numberOfContributors: 1)) + // validate + do { // ensure JPR relation is updated + XCTAssertEqual(jpr.repository?.commitCount, 12) + XCTAssertEqual(jpr.repository?.firstCommitDate, .t0) + XCTAssertEqual(jpr.repository?.lastCommitDate, .t1) + XCTAssertEqual(jpr.repository?.authors, PackageAuthors(authors: [Author(name: "Person 1")], + numberOfContributors: 1)) + } + do { // ensure changes are persisted + let repo = try await Repository.find(.id1, on: app.db) + XCTAssertEqual(repo?.commitCount, 12) + XCTAssertEqual(repo?.firstCommitDate, .t0) + XCTAssertEqual(repo?.lastCommitDate, .t1) + XCTAssertEqual(repo?.authors, PackageAuthors(authors: [Author(name: "Person 1")], numberOfContributors: 1)) + } } } @@ -750,6 +753,7 @@ class AnalyzerTests: AppTestCase { // Tests getPackageInfo(package:version:) try await withDependencies { $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } } operation: { // setup let commands = QueueIsolated<[String]>([]) @@ -889,6 +893,7 @@ class AnalyzerTests: AppTestCase { $0.date.now = .now $0.environment.allowSocialPosts = { true } $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } } operation: { // setup Current.git.commitCount = { @Sendable _ in 12 } @@ -953,79 +958,86 @@ class AnalyzerTests: AppTestCase { func test_issue_70() async throws { // Certain git commands fail when index.lock exists // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/70 - // setup - try await savePackage(on: app.db, "1".asGithubUrl.url, processingStage: .ingestion) - let pkgs = try await Package.fetchCandidates(app.db, for: .analysis, limit: 10) - - // claim every file exists, including our ficticious 'index.lock' for which - // we want to trigger the cleanup mechanism - Current.fileManager.fileExists = { @Sendable path in true } + try await withDependencies { + // claim every file exists, including our ficticious 'index.lock' for which + // we want to trigger the cleanup mechanism + $0.fileManager.fileExists = { @Sendable path in true } + } operation: { + // setup + try await savePackage(on: app.db, "1".asGithubUrl.url, processingStage: .ingestion) + let pkgs = try await Package.fetchCandidates(app.db, for: .analysis, limit: 10) - let commands = QueueIsolated<[String]>([]) - Current.shell.run = { @Sendable cmd, path in - commands.withValue { $0.append(cmd.description) } - return "" - } + let commands = QueueIsolated<[String]>([]) + Current.shell.run = { @Sendable cmd, path in + commands.withValue { $0.append(cmd.description) } + return "" + } - // MUT - let res = await pkgs.mapAsync { @Sendable pkg in - await Result { - try await Analyze.refreshCheckout(package: pkg) + // MUT + let res = await pkgs.mapAsync { @Sendable pkg in + await Result { + try await Analyze.refreshCheckout(package: pkg) + } } - } - // validation - XCTAssertEqual(res.map(\.isSuccess), [true]) - assertSnapshot(of: commands.value, as: .dump) + // validation + XCTAssertEqual(res.map(\.isSuccess), [true]) + assertSnapshot(of: commands.value, as: .dump) + } } @MainActor func test_issue_498() async throws { // git checkout can still fail despite git reset --hard + git clean // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/498 - // setup - try await savePackage(on: app.db, "1".asGithubUrl.url, processingStage: .ingestion) - let pkgs = try await Package.fetchCandidates(app.db, for: .analysis, limit: 10) - - // claim every file exists, including our ficticious 'index.lock' for which - // we want to trigger the cleanup mechanism - Current.fileManager.fileExists = { @Sendable path in true } - - let commands = QueueIsolated<[String]>([]) - Current.shell.run = { @Sendable cmd, path in - commands.withValue { $0.append(cmd.description) } - if cmd == .gitCheckout(branch: "master") { - throw TestError.simulatedCheckoutError + try await withDependencies { + // claim every file exists, including our ficticious 'index.lock' for which + // we want to trigger the cleanup mechanism + $0.fileManager.fileExists = { @Sendable path in true } + } operation: { + // setup + try await savePackage(on: app.db, "1".asGithubUrl.url, processingStage: .ingestion) + let pkgs = try await Package.fetchCandidates(app.db, for: .analysis, limit: 10) + + let commands = QueueIsolated<[String]>([]) + Current.shell.run = { @Sendable cmd, path in + commands.withValue { $0.append(cmd.description) } + if cmd == .gitCheckout(branch: "master") { + throw TestError.simulatedCheckoutError + } + return "" } - return "" - } - // MUT - let res = await pkgs.mapAsync { @Sendable pkg in - await Result { - try await Analyze.refreshCheckout(package: pkg) + // MUT + let res = await pkgs.mapAsync { @Sendable pkg in + await Result { + try await Analyze.refreshCheckout(package: pkg) + } } - } - // validation - XCTAssertEqual(res.map(\.isSuccess), [true]) - assertSnapshot(of: commands.value, as: .dump) + // validation + XCTAssertEqual(res.map(\.isSuccess), [true]) + assertSnapshot(of: commands.value, as: .dump) + } } func test_dumpPackage_5_4() async throws { // Test parsing a Package.swift that requires a 5.4 toolchain // NB: If this test fails on macOS make sure xcode-select -p // points to the correct version of Xcode! - // setup - Current.fileManager = .live - Current.shell = .live - try await withTempDir { tempDir in - let fixture = fixturesDirectory() - .appendingPathComponent("5.4-Package-swift").path - let fname = tempDir.appending("/Package.swift") - try await ShellOut.shellOut(to: .copyFile(from: fixture, to: fname)) - let m = try await Analyze.dumpPackage(at: tempDir) - XCTAssertEqual(m.name, "VisualEffects") + try await withDependencies { + $0.fileManager.fileExists = FileManagerClient.liveValue.fileExists(atPath:) + } operation: { + // setup + Current.shell = .live + try await withTempDir { tempDir in + let fixture = fixturesDirectory() + .appendingPathComponent("5.4-Package-swift").path + let fname = tempDir.appending("/Package.swift") + try await ShellOut.shellOut(to: .copyFile(from: fixture, to: fname)) + let m = try await Analyze.dumpPackage(at: tempDir) + XCTAssertEqual(m.name, "VisualEffects") + } } } @@ -1034,16 +1046,19 @@ class AnalyzerTests: AppTestCase { // NB: If this test fails on macOS make sure xcode-select -p // points to the correct version of Xcode! // See also https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/1441 - // setup - Current.fileManager = .live - Current.shell = .live - try await withTempDir { tempDir in - let fixture = fixturesDirectory() - .appendingPathComponent("5.5-Package-swift").path - let fname = tempDir.appending("/Package.swift") - try await ShellOut.shellOut(to: .copyFile(from: fixture, to: fname)) - let m = try await Analyze.dumpPackage(at: tempDir) - XCTAssertEqual(m.name, "Firestarter") + try await withDependencies { + $0.fileManager.fileExists = FileManagerClient.liveValue.fileExists(atPath:) + } operation: { + // setup + Current.shell = .live + try await withTempDir { tempDir in + let fixture = fixturesDirectory() + .appendingPathComponent("5.5-Package-swift").path + let fname = tempDir.appending("/Package.swift") + try await ShellOut.shellOut(to: .copyFile(from: fixture, to: fname)) + let m = try await Analyze.dumpPackage(at: tempDir) + XCTAssertEqual(m.name, "Firestarter") + } } } @@ -1051,16 +1066,19 @@ class AnalyzerTests: AppTestCase { // Test parsing a 5.9 Package.swift with a macro target // NB: If this test fails on macOS make sure xcode-select -p // points to the correct version of Xcode! - // setup - Current.fileManager = .live - Current.shell = .live - try await withTempDir { tempDir in - let fixture = fixturesDirectory() - .appendingPathComponent("5.9-Package-swift").path - let fname = tempDir.appending("/Package.swift") - try await ShellOut.shellOut(to: .copyFile(from: fixture, to: fname)) - let m = try await Analyze.dumpPackage(at: tempDir) - XCTAssertEqual(m.name, "StaticMemberIterable") + try await withDependencies { + $0.fileManager.fileExists = FileManagerClient.liveValue.fileExists(atPath:) + } operation: { + // setup + Current.shell = .live + try await withTempDir { tempDir in + let fixture = fixturesDirectory() + .appendingPathComponent("5.9-Package-swift").path + let fname = tempDir.appending("/Package.swift") + try await ShellOut.shellOut(to: .copyFile(from: fixture, to: fname)) + let m = try await Analyze.dumpPackage(at: tempDir) + XCTAssertEqual(m.name, "StaticMemberIterable") + } } } @@ -1077,7 +1095,6 @@ class AnalyzerTests: AppTestCase { // NB: If this test fails on macOS make sure xcode-select -p // points to the correct version of Xcode! // setup - Current.fileManager = .live Current.shell = .live try await withTempDir { @Sendable tempDir in let fixture = fixturesDirectory() @@ -1134,25 +1151,28 @@ class AnalyzerTests: AppTestCase { func test_issue_693() async throws { // Handle moved tags // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/693 - // setup - do { - let pkg = try await savePackage(on: app.db, id: .id0, "1".asGithubUrl.url) - try await Repository(package: pkg, defaultBranch: "main").save(on: app.db) - } - let pkg = try await Package.fetchCandidate(app.db, id: .id0) - Current.fileManager.fileExists = { @Sendable _ in true } - let commands = QueueIsolated<[String]>([]) - Current.shell.run = { @Sendable cmd, _ in - commands.withValue { $0.append(cmd.description) } - if cmd == .gitFetchAndPruneTags { throw TestError.simulatedFetchError } - return "" - } + try await withDependencies { + $0.fileManager.fileExists = { @Sendable _ in true } + } operation: { + // setup + do { + let pkg = try await savePackage(on: app.db, id: .id0, "1".asGithubUrl.url) + try await Repository(package: pkg, defaultBranch: "main").save(on: app.db) + } + let pkg = try await Package.fetchCandidate(app.db, id: .id0) + let commands = QueueIsolated<[String]>([]) + Current.shell.run = { @Sendable cmd, _ in + commands.withValue { $0.append(cmd.description) } + if cmd == .gitFetchAndPruneTags { throw TestError.simulatedFetchError } + return "" + } - // MUT - _ = try await Analyze.refreshCheckout(package: pkg) + // MUT + _ = try await Analyze.refreshCheckout(package: pkg) - // validate - assertSnapshot(of: commands.value, as: .dump) + // validate + assertSnapshot(of: commands.value, as: .dump) + } } func test_updateLatestVersions() async throws { @@ -1218,39 +1238,43 @@ class AnalyzerTests: AppTestCase { func test_issue_914() async throws { // Ensure we handle 404 repos properly // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/914 - // setup - let checkoutDir = "/checkouts" - do { - let url = "1".asGithubUrl.url - let pkg = Package.init(url: url, processingStage: .ingestion) - try await pkg.save(on: app.db) - Current.fileManager.fileExists = { @Sendable path in + try await withDependencies { + $0.fileManager.fileExists = { @Sendable path in if path.hasSuffix("github.com-foo-1") { return false } return true } - let repoDir = try checkoutDir + "/" + XCTUnwrap(pkg.cacheDirectoryName) - struct ShellOutError: Error {} - Current.shell.run = { @Sendable cmd, path in - if cmd == .gitClone(url: url, to: repoDir) { - throw ShellOutError() + } operation: { + // setup + let checkoutDir = "/checkouts" + do { + let url = "1".asGithubUrl.url + let pkg = Package.init(url: url, processingStage: .ingestion) + try await pkg.save(on: app.db) + let repoDir = try checkoutDir + "/" + XCTUnwrap(pkg.cacheDirectoryName) + struct ShellOutError: Error {} + Current.shell.run = { @Sendable cmd, path in + if cmd == .gitClone(url: url, to: repoDir) { + throw ShellOutError() + } + throw TestError.unknownCommand } - throw TestError.unknownCommand } - } - let lastUpdated = Date() + let lastUpdated = Date() - // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) + // MUT + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(10)) - // validate - let pkg = try await Package.query(on: app.db).first().unwrap() - XCTAssertTrue(pkg.updatedAt! > lastUpdated) - XCTAssertEqual(pkg.status, .analysisFailed) + // validate + let pkg = try await Package.query(on: app.db).first().unwrap() + XCTAssertTrue(pkg.updatedAt! > lastUpdated) + XCTAssertEqual(pkg.status, .analysisFailed) + } } func test_trimCheckouts() throws { + let removedPaths = NIOLockedValueBox<[String]>([]) try withDependencies { $0.date.now = .t0 $0.fileManager.attributesOfItem = { @Sendable path in @@ -1261,11 +1285,8 @@ class AnalyzerTests: AppTestCase { } $0.fileManager.checkoutsDirectory = { "/checkouts" } $0.fileManager.contentsOfDirectory = { @Sendable _ in ["foo", "bar"] } + $0.fileManager.removeItem = { @Sendable p in removedPaths.withLockedValue { $0.append(p) } } } operation: { - // setup - let removedPaths = NIOLockedValueBox<[String]>([]) - Current.fileManager.removeItem = { @Sendable p in removedPaths.withLockedValue { $0.append(p) } } - // MUT try Analyze.trimCheckouts() @@ -1277,102 +1298,105 @@ class AnalyzerTests: AppTestCase { func test_issue_2571_tags() async throws { // Ensure bad git commands do not delete existing tag revisions // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2571 - let pkgId = UUID() - let pkg = Package(id: pkgId, url: "1".asGithubUrl.url, processingStage: .ingestion) - try await pkg.save(on: app.db) - try await Repository(package: pkg, - defaultBranch: "main", - name: "1", - owner: "foo").save(on: app.db) - try await Version(package: pkg, - commit: "commit0", - commitDate: .t0, - latest: .defaultBranch, - packageName: "foo-1", - reference: .branch("main")).save(on: app.db) - try await Version(package: pkg, - commit: "commit0", - commitDate: .t0, - latest: .release, - packageName: "foo-1", - reference: .tag(1, 0, 0)).save(on: app.db) - Current.fileManager.fileExists = { @Sendable _ in true } - Current.git.commitCount = { @Sendable _ in 2 } - Current.git.firstCommitDate = { @Sendable _ in .t0 } - Current.git.hasBranch = { @Sendable _, _ in true } - Current.git.lastCommitDate = { @Sendable _ in .t1 } - struct Error: Swift.Error { } - Current.git.shortlog = { @Sendable _ in + try await withDependencies { + $0.fileManager.fileExists = { @Sendable _ in true } + } operation: { + let pkgId = UUID() + let pkg = Package(id: pkgId, url: "1".asGithubUrl.url, processingStage: .ingestion) + try await pkg.save(on: app.db) + try await Repository(package: pkg, + defaultBranch: "main", + name: "1", + owner: "foo").save(on: app.db) + try await Version(package: pkg, + commit: "commit0", + commitDate: .t0, + latest: .defaultBranch, + packageName: "foo-1", + reference: .branch("main")).save(on: app.db) + try await Version(package: pkg, + commit: "commit0", + commitDate: .t0, + latest: .release, + packageName: "foo-1", + reference: .tag(1, 0, 0)).save(on: app.db) + Current.git.commitCount = { @Sendable _ in 2 } + Current.git.firstCommitDate = { @Sendable _ in .t0 } + Current.git.hasBranch = { @Sendable _, _ in true } + Current.git.lastCommitDate = { @Sendable _ in .t1 } + struct Error: Swift.Error { } + Current.git.shortlog = { @Sendable _ in """ 1\tPerson 1 1\tPerson 2 """ - } - Current.shell.run = { @Sendable cmd, path in "" } + } + Current.shell.run = { @Sendable cmd, path in "" } - do { // first scenario: bad getTags - Current.git.getTags = { @Sendable _ in throw Error() } - Current.git.revisionInfo = { @Sendable _, _ in .init(commit: "", date: .t1) } + do { // first scenario: bad getTags + Current.git.getTags = { @Sendable _ in throw Error() } + Current.git.revisionInfo = { @Sendable _, _ in .init(commit: "", date: .t1) } - // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(1)) + // MUT + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(1)) - // validate versions - let p = try await Package.find(pkgId, on: app.db).unwrap() - try await p.$versions.load(on: app.db) - let versions = p.versions.map(\.reference.description).sorted() - XCTAssertEqual(versions, ["1.0.0", "main"]) - } + // validate versions + let p = try await Package.find(pkgId, on: app.db).unwrap() + try await p.$versions.load(on: app.db) + let versions = p.versions.map(\.reference.description).sorted() + XCTAssertEqual(versions, ["1.0.0", "main"]) + } - do { // second scenario: revisionInfo throws - Current.git.getTags = { @Sendable _ in [.tag(1, 0, 0)] } - Current.git.revisionInfo = { @Sendable _, _ in throw Error() } + do { // second scenario: revisionInfo throws + Current.git.getTags = { @Sendable _ in [.tag(1, 0, 0)] } + Current.git.revisionInfo = { @Sendable _, _ in throw Error() } - // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(1)) + // MUT + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(1)) - // validate versions - let p = try await Package.find(pkgId, on: app.db).unwrap() - try await p.$versions.load(on: app.db) - let versions = p.versions.map(\.reference.description).sorted() - XCTAssertEqual(versions, ["1.0.0", "main"]) - } + // validate versions + let p = try await Package.find(pkgId, on: app.db).unwrap() + try await p.$versions.load(on: app.db) + let versions = p.versions.map(\.reference.description).sorted() + XCTAssertEqual(versions, ["1.0.0", "main"]) + } - do { // second scenario: gitTags throws - Current.git.getTags = { @Sendable _ in throw Error() } - Current.git.revisionInfo = { @Sendable _, _ in .init(commit: "", date: .t1) } + do { // second scenario: gitTags throws + Current.git.getTags = { @Sendable _ in throw Error() } + Current.git.revisionInfo = { @Sendable _, _ in .init(commit: "", date: .t1) } - // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(1)) + // MUT + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(1)) - // validate versions - let p = try await Package.find(pkgId, on: app.db).unwrap() - try await p.$versions.load(on: app.db) - let versions = p.versions.map(\.reference.description).sorted() - XCTAssertEqual(versions, ["1.0.0", "main"]) - } + // validate versions + let p = try await Package.find(pkgId, on: app.db).unwrap() + try await p.$versions.load(on: app.db) + let versions = p.versions.map(\.reference.description).sorted() + XCTAssertEqual(versions, ["1.0.0", "main"]) + } - do { // third scenario: everything throws - Current.shell.run = { @Sendable _, _ in throw Error() } - Current.git.getTags = { @Sendable _ in throw Error() } - Current.git.revisionInfo = { @Sendable _, _ in throw Error() } + do { // third scenario: everything throws + Current.shell.run = { @Sendable _, _ in throw Error() } + Current.git.getTags = { @Sendable _ in throw Error() } + Current.git.revisionInfo = { @Sendable _, _ in throw Error() } - // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(1)) + // MUT + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(1)) - // validate versions - let p = try await Package.find(pkgId, on: app.db).unwrap() - try await p.$versions.load(on: app.db) - let versions = p.versions.map(\.reference.description).sorted() - XCTAssertEqual(versions, ["1.0.0", "main"]) + // validate versions + let p = try await Package.find(pkgId, on: app.db).unwrap() + try await p.$versions.load(on: app.db) + let versions = p.versions.map(\.reference.description).sorted() + XCTAssertEqual(versions, ["1.0.0", "main"]) + } } } @@ -1381,6 +1405,7 @@ class AnalyzerTests: AppTestCase { // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2571 try await withDependencies { $0.date.now = .now + $0.fileManager.fileExists = { @Sendable _ in true } } operation: { let pkgId = UUID() let pkg = Package(id: pkgId, url: "1".asGithubUrl.url, processingStage: .ingestion) @@ -1401,7 +1426,6 @@ class AnalyzerTests: AppTestCase { latest: .release, packageName: "foo-1", reference: .tag(1, 0, 0)).save(on: app.db) - Current.fileManager.fileExists = { @Sendable _ in true } Current.git.commitCount = { @Sendable _ in 2 } Current.git.firstCommitDate = { @Sendable _ in .t0 } Current.git.hasBranch = { @Sendable _, _ in true } @@ -1492,6 +1516,7 @@ class AnalyzerTests: AppTestCase { try await withDependencies { $0.date.now = .now $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } } operation: { // setup let pkg = try await savePackage(on: app.db, id: .id0, "https://github.com/foo/1".url, processingStage: .ingestion) diff --git a/Tests/AppTests/ErrorReportingTests.swift b/Tests/AppTests/ErrorReportingTests.swift index 13d7dd8c4..51f4ca5d2 100644 --- a/Tests/AppTests/ErrorReportingTests.swift +++ b/Tests/AppTests/ErrorReportingTests.swift @@ -51,42 +51,47 @@ class ErrorReportingTests: AppTestCase { } func test_Analyzer_error_reporting() async throws { - // setup - try await Package(id: .id1, url: "1".asGithubUrl.url, processingStage: .ingestion).save(on: app.db) - Current.fileManager.fileExists = { @Sendable _ in true } - Current.shell.run = { @Sendable cmd, path in - if cmd.description == "git tag" { return "1.0.0" } - // returning a blank string will cause an exception when trying to - // decode it as the manifest result - we use this to simulate errors - return "invalid" - } + try await withDependencies { + $0.fileManager.fileExists = { @Sendable _ in true } + } operation: { + // setup + try await Package(id: .id1, url: "1".asGithubUrl.url, processingStage: .ingestion).save(on: app.db) + Current.shell.run = { @Sendable cmd, path in + if cmd.description == "git tag" { return "1.0.0" } + // returning a blank string will cause an exception when trying to + // decode it as the manifest result - we use this to simulate errors + return "invalid" + } - // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) + // MUT + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(10)) - // validation - logger.logs.withValue { - XCTAssertEqual($0, [ - .init(level: .critical, message: "updatePackages: unusually high error rate: 1/1 = 100.0%"), - .init(level: .warning, message: #"App.AppError.genericError(Optional(\#(UUID.id1)), "updateRepository: no repository")"#) - ]) + // validation + logger.logs.withValue { + XCTAssertEqual($0, [ + .init(level: .critical, message: "updatePackages: unusually high error rate: 1/1 = 100.0%"), + .init(level: .warning, message: #"App.AppError.genericError(Optional(\#(UUID.id1)), "updateRepository: no repository")"#) + ]) + } } } func test_invalidPackageCachePath() async throws { - // setup - try await savePackages(on: app.db, ["1", "2"], processingStage: .ingestion) + try await withDependencies { + $0.fileManager.fileExists = { @Sendable _ in true } + } operation: { + // setup + try await savePackages(on: app.db, ["1", "2"], processingStage: .ingestion) - // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) + // MUT + try await Analyze.analyze(client: app.client, database: app.db, mode: .limit(10)) - // validation - let packages = try await Package.query(on: app.db).sort(\.$url).all() - XCTAssertEqual(packages.map(\.status), [.invalidCachePath, .invalidCachePath]) + // validation + let packages = try await Package.query(on: app.db).sort(\.$url).all() + XCTAssertEqual(packages.map(\.status), [.invalidCachePath, .invalidCachePath]) + } } } diff --git a/Tests/AppTests/IngestionTests.swift b/Tests/AppTests/IngestionTests.swift index f75509815..5330f1672 100644 --- a/Tests/AppTests/IngestionTests.swift +++ b/Tests/AppTests/IngestionTests.swift @@ -432,8 +432,8 @@ class IngestionTests: AppTestCase { $0.date.now = .now $0.environment.allowSocialPosts = { false } $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } } operation: { [db = app.db] in - Current.fileManager.fileExists = { @Sendable _ in true } Current.git.commitCount = { @Sendable _ in 1 } Current.git.firstCommitDate = { @Sendable _ in .t0 } Current.git.lastCommitDate = { @Sendable _ in .t0 } diff --git a/Tests/AppTests/MastodonTests.swift b/Tests/AppTests/MastodonTests.swift index 056548931..5122190f4 100644 --- a/Tests/AppTests/MastodonTests.swift +++ b/Tests/AppTests/MastodonTests.swift @@ -26,6 +26,7 @@ final class MastodonTests: AppTestCase { try await withDependencies { $0.environment.allowSocialPosts = { true } $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } $0.github.fetchLicense = { @Sendable _, _ in nil } $0.github.fetchMetadata = { @Sendable owner, repository in .mock(owner: owner, repository: repository) } $0.github.fetchReadme = { @Sendable _, _ in nil } diff --git a/Tests/AppTests/MetricsTests.swift b/Tests/AppTests/MetricsTests.swift index eab244170..add475694 100644 --- a/Tests/AppTests/MetricsTests.swift +++ b/Tests/AppTests/MetricsTests.swift @@ -129,14 +129,18 @@ class MetricsTests: AppTestCase { } func test_analyzeDurationSeconds() async throws { - // setup - let pkg = try await savePackage(on: app.db, "1") + try await withDependencies { + $0.fileManager.fileExists = { @Sendable _ in true } + } operation: { + // setup + let pkg = try await savePackage(on: app.db, "1") - // MUT - try await Analyze.analyze(client: app.client, database: app.db, mode: .id(pkg.id!)) + // MUT + try await Analyze.analyze(client: app.client, database: app.db, mode: .id(pkg.id!)) - // validation - XCTAssert((AppMetrics.analyzeDurationSeconds?.get()) ?? 0 > 0) + // validation + XCTAssert((AppMetrics.analyzeDurationSeconds?.get()) ?? 0 > 0) + } } func test_triggerBuildsDurationSeconds() async throws { diff --git a/Tests/AppTests/Mocks/AppEnvironment+mock.swift b/Tests/AppTests/Mocks/AppEnvironment+mock.swift index dd3aabb85..8ec149989 100644 --- a/Tests/AppTests/Mocks/AppEnvironment+mock.swift +++ b/Tests/AppTests/Mocks/AppEnvironment+mock.swift @@ -22,7 +22,6 @@ import Vapor extension AppEnvironment { static func mock(eventLoop: EventLoop) -> Self { .init( - fileManager: .mock, git: .mock, logger: { logger }, setLogger: { logger in Self.logger = logger }, diff --git a/Tests/AppTests/Mocks/AppFileManager+mock.swift b/Tests/AppTests/Mocks/AppFileManager+mock.swift deleted file mode 100644 index 7ac649cf0..000000000 --- a/Tests/AppTests/Mocks/AppFileManager+mock.swift +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright Dave Verwer, Sven A. Schmidt, and other contributors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@testable import App - -import Vapor - - -extension App.FileManager { - static let mock = Self.mock(fileExists: true) - static func mock(fileExists: Bool) -> Self { - .init( - createDirectory: { _, _, _ in }, - fileExists: { path in fileExists }, - removeItem: { _ in }, - workingDirectory: { DirectoryConfiguration.detect().workingDirectory } - ) - } -} diff --git a/Tests/AppTests/PackageContributorsTests.swift b/Tests/AppTests/PackageContributorsTests.swift index 27230c402..c8927e926 100644 --- a/Tests/AppTests/PackageContributorsTests.swift +++ b/Tests/AppTests/PackageContributorsTests.swift @@ -16,6 +16,9 @@ import XCTest @testable import App +import Dependencies + + class PackageContributorsTests : AppTestCase { func test_packageAuthors_hasAuthors() throws { @@ -64,10 +67,12 @@ class PackageContributorsTests : AppTestCase { func test_PackageContributors_extract() async throws { - // setup - let pkg = try await savePackage(on: app.db, "1".asGithubUrl.url) - Current.fileManager.fileExists = { @Sendable _ in true } - Current.git.shortlog = { @Sendable _ in + try await withDependencies { + $0.fileManager.fileExists = { @Sendable _ in true } + } operation: { + // setup + let pkg = try await savePackage(on: app.db, "1".asGithubUrl.url) + Current.git.shortlog = { @Sendable _ in """ 1000\tPerson 1 871\tPerson 2 @@ -81,19 +86,19 @@ class PackageContributorsTests : AppTestCase { 40\tPerson 10 11\tPerson 11 """ + } + + // MUT + let pkgAuthors = try await PackageContributors.extract(gitCacheDirectoryPath: "", + packageID: pkg.id) + + XCTAssertEqual(pkgAuthors.authors, [Author(name: "Person 1") , + Author(name: "Person 2"), + Author(name: "Person 3"), + Author(name: "Person 4"), + Author(name: "Person 5")]) + XCTAssertEqual(pkgAuthors.numberOfContributors, 6) } - - // MUT - let pkgAuthors = try await PackageContributors.extract(gitCacheDirectoryPath: "", - packageID: pkg.id) - - XCTAssertEqual(pkgAuthors.authors, [Author(name: "Person 1") , - Author(name: "Person 2"), - Author(name: "Person 3"), - Author(name: "Person 4"), - Author(name: "Person 5")]) - XCTAssertEqual(pkgAuthors.numberOfContributors, 6) - } diff --git a/Tests/AppTests/PackageController+routesTests.swift b/Tests/AppTests/PackageController+routesTests.swift index 420702b58..06dc9e55d 100644 --- a/Tests/AppTests/PackageController+routesTests.swift +++ b/Tests/AppTests/PackageController+routesTests.swift @@ -1501,6 +1501,10 @@ class PackageController_routesTests: SnapshotTestCase { $0.currentReferenceCache = .disabled $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable path in + if path.hasSuffix("Package.resolved") { return false } + return true + } $0.httpClient.fetchDocumentation = { @Sendable _ in .ok(body: .mockIndexHTML()) } $0.timeZone = .utc } operation: { @@ -1515,11 +1519,7 @@ class PackageController_routesTests: SnapshotTestCase { latest: .defaultBranch, packageName: "bar", reference: .branch("main")) - .save(on: app.db) - Current.fileManager.fileExists = { @Sendable path in - if path.hasSuffix("Package.resolved") { return false } - return true - } + .save(on: app.db) Current.git = .init( commitCount: { _ in 2 }, firstCommitDate: { _ in .t0 }, diff --git a/Tests/AppTests/PackageTests.swift b/Tests/AppTests/PackageTests.swift index 5b624484e..ede27604c 100644 --- a/Tests/AppTests/PackageTests.swift +++ b/Tests/AppTests/PackageTests.swift @@ -311,6 +311,7 @@ final class PackageTests: AppTestCase { let url = "1".asGithubUrl try await withDependencies { $0.date.now = .now + $0.fileManager.fileExists = { @Sendable _ in true } $0.github.fetchLicense = { @Sendable _, _ in nil } $0.github.fetchMetadata = { @Sendable owner, repository in .mock(owner: owner, repository: repository) } $0.github.fetchReadme = { @Sendable _, _ in nil } diff --git a/Tests/AppTests/PipelineTests.swift b/Tests/AppTests/PipelineTests.swift index 24ec8e424..cc65d5b9d 100644 --- a/Tests/AppTests/PipelineTests.swift +++ b/Tests/AppTests/PipelineTests.swift @@ -162,6 +162,7 @@ class PipelineTests: AppTestCase { try await withDependencies { $0.date.now = .now $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } $0.github.fetchLicense = { @Sendable _, _ in nil } $0.github.fetchMetadata = { @Sendable owner, repository in .mock(owner: owner, repository: repository) } $0.github.fetchReadme = { @Sendable _, _ in nil } diff --git a/Tests/AppTests/ReAnalyzeVersionsTests.swift b/Tests/AppTests/ReAnalyzeVersionsTests.swift index b1354dcd0..9e38bbcb8 100644 --- a/Tests/AppTests/ReAnalyzeVersionsTests.swift +++ b/Tests/AppTests/ReAnalyzeVersionsTests.swift @@ -30,6 +30,7 @@ class ReAnalyzeVersionsTests: AppTestCase { $0.date.now = .t0 $0.environment.allowSocialPosts = { true } $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } $0.httpClient.mastodonPost = { @Sendable _ in } } operation: { // setup @@ -182,6 +183,7 @@ class ReAnalyzeVersionsTests: AppTestCase { try await withDependencies { $0.date.now = .t2 $0.environment.loadSPIManifest = { _ in nil } + $0.fileManager.fileExists = { @Sendable _ in true } } operation: { let pkg = try await savePackage(on: app.db, "https://github.com/foo/1".url,