diff --git a/Resources/ChartData/rfs6-errors.json b/Resources/ChartData/rfs6-errors.json index 150f9b31c..64a355db4 100644 --- a/Resources/ChartData/rfs6-errors.json +++ b/Resources/ChartData/rfs6-errors.json @@ -254,4 +254,4 @@ } ] } -] \ No newline at end of file +] diff --git a/Sources/App/Commands/Analyze.swift b/Sources/App/Commands/Analyze.swift index 72fbbde29..071296525 100644 --- a/Sources/App/Commands/Analyze.swift +++ b/Sources/App/Commands/Analyze.swift @@ -71,14 +71,15 @@ extension Analyze { static func trimCheckouts() throws { + @Dependency(\.fileManager) var fileManager let checkoutDir = URL( - fileURLWithPath: Current.fileManager.checkoutsDirectory(), + fileURLWithPath: fileManager.checkoutsDirectory(), isDirectory: true ) - try Current.fileManager.contentsOfDirectory(atPath: checkoutDir.path) + try fileManager.contentsOfDirectory(atPath: checkoutDir.path) .map { dir -> (String, Date)? in let url = checkoutDir.appendingPathComponent(dir) - guard let mod = try Current.fileManager + guard let mod = try fileManager .attributesOfItem(atPath: url.path)[.modificationDate] as? Date else { return nil } return (url.path, mod) @@ -139,7 +140,8 @@ extension Analyze { AppMetrics.analyzeCandidatesCount?.set(packages.count) // get or create directory - let checkoutDir = Current.fileManager.checkoutsDirectory() + @Dependency(\.fileManager) var fileManager + let checkoutDir = fileManager.checkoutsDirectory() Current.logger().info("Checkout directory: \(checkoutDir)") if !Current.fileManager.fileExists(atPath: checkoutDir) { try await createCheckoutsDirectory(client: client, path: checkoutDir) @@ -251,8 +253,9 @@ extension Analyze { /// - Throws: Shell errors static func clone(cacheDir: String, url: String) async throws { Current.logger().info("cloning \(url) to \(cacheDir)") + @Dependency(\.fileManager) var fileManager try await Current.shell.run(command: .gitClone(url: URL(string: url)!, to: cacheDir), - at: Current.fileManager.checkoutsDirectory()) + at: fileManager.checkoutsDirectory()) } @@ -286,7 +289,8 @@ extension Analyze { /// - Parameters: /// - package: `Package` to refresh static func refreshCheckout(package: Joined) async throws { - guard let cacheDir = Current.fileManager.cacheDirectoryPath(for: package.model) else { + @Dependency(\.fileManager) var fileManager + guard let cacheDir = fileManager.cacheDirectoryPath(for: package.model) else { throw AppError.invalidPackageCachePath(package.model.id, package.model.url) } @@ -322,7 +326,8 @@ extension Analyze { guard let repo = package.repository else { throw AppError.genericError(package.model.id, "updateRepository: no repository") } - guard let gitDirectory = Current.fileManager.cacheDirectoryPath(for: package.model) else { + @Dependency(\.fileManager) var fileManager + guard let gitDirectory = fileManager.cacheDirectoryPath(for: package.model) else { throw AppError.invalidPackageCachePath(package.model.id, package.model.url) } @@ -375,7 +380,8 @@ extension Analyze { /// - Returns: future with incoming `Version`s static func getIncomingVersions(client: Client, package: Joined) async throws -> [Version] { - guard let cacheDir = Current.fileManager.cacheDirectoryPath(for: package.model) else { + @Dependency(\.fileManager) var fileManager + guard let cacheDir = fileManager.cacheDirectoryPath(for: package.model) else { throw AppError.invalidPackageCachePath(package.model.id, package.model.url) } @@ -546,7 +552,8 @@ extension Analyze { /// - Returns: `Result` with `Manifest` data static func getPackageInfo(package: Joined, version: Version) async throws -> PackageInfo { // check out version in cache directory - guard let cacheDir = Current.fileManager.cacheDirectoryPath(for: package.model) else { + @Dependency(\.fileManager) var fileManager + guard let cacheDir = fileManager.cacheDirectoryPath(for: package.model) else { throw AppError.invalidPackageCachePath(package.model.id, package.model.url) } diff --git a/Sources/App/Commands/ReAnalyzeVersions.swift b/Sources/App/Commands/ReAnalyzeVersions.swift index f35bfb020..c428305a8 100644 --- a/Sources/App/Commands/ReAnalyzeVersions.swift +++ b/Sources/App/Commands/ReAnalyzeVersions.swift @@ -183,7 +183,8 @@ enum ReAnalyzeVersions { try await withEscapedDependencies { dependencies in try await database.transaction { tx in try await dependencies.yield { - guard let cacheDir = Current.fileManager.cacheDirectoryPath(for: pkg.model) else { return } + @Dependency(\.fileManager) var fileManager + guard let cacheDir = fileManager.cacheDirectoryPath(for: pkg.model) else { return } if !Current.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 bfa4450f5..c6ee9f448 100644 --- a/Sources/App/Core/AppEnvironment.swift +++ b/Sources/App/Core/AppEnvironment.swift @@ -45,23 +45,12 @@ extension AppEnvironment { struct FileManager: Sendable { - var attributesOfItem: @Sendable (_ path: String) throws -> [FileAttributeKey : Any] - var contentsOfDirectory: @Sendable (_ path: String) throws -> [String] - var contents: @Sendable (_ atPath: String) -> Data? - var checkoutsDirectory: @Sendable () -> String 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 attributesOfItem(atPath path: String) throws -> [FileAttributeKey : Any] { - try attributesOfItem(path) - } - func contents(atPath path: String) -> Data? { contents(path) } - func contentsOfDirectory(atPath path: String) throws -> [String] { - try contentsOfDirectory(path) - } func createDirectory(atPath path: String, withIntermediateDirectories createIntermediates: Bool, attributes: [FileAttributeKey : Any]?) throws { @@ -71,10 +60,6 @@ struct FileManager: Sendable { func removeItem(atPath path: String) throws { try removeItem(path) } static let live: Self = .init( - attributesOfItem: { try Foundation.FileManager.default.attributesOfItem(atPath: $0) }, - contentsOfDirectory: { try Foundation.FileManager.default.contentsOfDirectory(atPath: $0) }, - contents: { Foundation.FileManager.default.contents(atPath: $0) }, - checkoutsDirectory: { Environment.get("CHECKOUTS_DIR") ?? DirectoryConfiguration.detect().workingDirectory + "SPI-checkouts" }, 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) }, @@ -83,14 +68,6 @@ struct FileManager: Sendable { } -extension FileManager { - func cacheDirectoryPath(for package: Package) -> String? { - guard let dirname = package.cacheDirectoryName else { return nil } - return checkoutsDirectory() + "/" + dirname - } -} - - struct Git: Sendable { var commitCount: @Sendable (String) async throws -> Int var firstCommitDate: @Sendable (String) async throws -> Date diff --git a/Sources/App/Core/Dependencies/FileManagerClient.swift b/Sources/App/Core/Dependencies/FileManagerClient.swift new file mode 100644 index 000000000..65a3634cc --- /dev/null +++ b/Sources/App/Core/Dependencies/FileManagerClient.swift @@ -0,0 +1,67 @@ +// 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. + +import Foundation + +import Dependencies +import DependenciesMacros +import IssueReporting +import Vapor + + +@DependencyClient +struct FileManagerClient { + var attributesOfItem: @Sendable (_ atPath: String) throws -> [FileAttributeKey : Any] + var checkoutsDirectory: @Sendable () -> String = { reportIssue("checkoutsDirectory"); return "SPI-checkouts" } + var contents: @Sendable (_ atPath: String) -> Data? + var contentsOfDirectory: @Sendable (_ atPath: String) throws -> [String] +} + + +extension FileManagerClient { + func cacheDirectoryPath(for package: Package) -> String? { + guard let dirname = package.cacheDirectoryName else { return nil } + return checkoutsDirectory() + "/" + dirname + } +} + + +extension FileManagerClient: DependencyKey { + static var liveValue: Self { + .init( + 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) } + ) + } +} + + +extension FileManagerClient: TestDependencyKey { + static var testValue: Self { + var mock = Self() + // Override the `unimplemented` default because it is a very common dependency. + mock.checkoutsDirectory = { "SPI-checkouts" } + return mock + } +} + + +extension DependencyValues { + var fileManager: FileManagerClient { + get { self[FileManagerClient.self] } + set { self[FileManagerClient.self] = newValue } + } +} diff --git a/Sources/App/Views/Blog/BlogActions+Model.swift b/Sources/App/Views/Blog/BlogActions+Model.swift index 7d33c064c..aede699a1 100644 --- a/Sources/App/Views/Blog/BlogActions+Model.swift +++ b/Sources/App/Views/Blog/BlogActions+Model.swift @@ -63,7 +63,8 @@ enum BlogActions { } static func allSummaries() throws -> [PostSummary] { - guard let data = Current.fileManager.contents(atPath: Self.blogIndexYmlPath) else { + @Dependency(\.fileManager) var fileManager + guard let data = fileManager.contents(atPath: Self.blogIndexYmlPath) else { throw AppError.genericError(nil, "failed to read posts.yml") } @@ -78,11 +79,12 @@ enum BlogActions { extension BlogActions.Model.PostSummary { var postMarkdown: String { + @Dependency(\.fileManager) var fileManager let markdownPath = Current.fileManager.workingDirectory() .appending("Resources/Blog/Posts/") .appending(slug) .appending(".md") - if let markdownData = Current.fileManager.contents(atPath: markdownPath), + if let markdownData = fileManager.contents(atPath: markdownPath), let markdown = String(data: markdownData, encoding: .utf8) { let parsedMarkdown = MarkdownParser().parse(markdown) diff --git a/Sources/App/Views/ReadyForSwift6/ReadyForSwift6Show+Model.swift b/Sources/App/Views/ReadyForSwift6/ReadyForSwift6Show+Model.swift index 55a86127d..71b1b9810 100644 --- a/Sources/App/Views/ReadyForSwift6/ReadyForSwift6Show+Model.swift +++ b/Sources/App/Views/ReadyForSwift6/ReadyForSwift6Show+Model.swift @@ -13,8 +13,11 @@ // limitations under the License. import Foundation + +import Dependencies import Plot + enum ReadyForSwift6Show { struct Model { @@ -25,11 +28,12 @@ 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") - guard let plotData = Current.fileManager.contents(atPath: plotDataPath)?.compactJson(), - let eventData = Current.fileManager.contents(atPath: eventDataPath)?.compactJson() - else { return .p("Couldn’t load chart data.") } + guard let plotData = fileManager.contents(atPath: plotDataPath)?.compactJson(), + let eventData = fileManager.contents(atPath: eventDataPath)?.compactJson() + else { return .p("Couldn’t load chart data.") } return .div( .data(named: "controller", value: "vega-chart"), @@ -73,7 +77,7 @@ private extension ReadyForSwift6Show.Model.ChartKind { private extension Data { func compactJson() -> String? { guard let json = try? JSONSerialization.jsonObject(with: self), - let compactedJsonData = try? JSONSerialization.data(withJSONObject: json), + let compactedJsonData = try? JSONSerialization.data(withJSONObject: json, options: [.sortedKeys]), let compactJson = String(data: compactedJsonData, encoding: .utf8) else { return nil } return compactJson diff --git a/Sources/App/Views/ResourceReloadIdentifier.swift b/Sources/App/Views/ResourceReloadIdentifier.swift index a6846fb03..f73898344 100644 --- a/Sources/App/Views/ResourceReloadIdentifier.swift +++ b/Sources/App/Views/ResourceReloadIdentifier.swift @@ -36,11 +36,12 @@ struct ResourceReloadIdentifier { private static func modificationDate(forLocalResource resource: String) -> Date { @Dependency(\.date.now) var now + @Dependency(\.fileManager) var fileManager let pathToPublic = DirectoryConfiguration.detect().publicDirectory let url = URL(fileURLWithPath: pathToPublic + resource) // Assume the file has been modified *now* if the file can't be found. - guard let attributes = try? Current.fileManager.attributesOfItem(atPath: url.path) + guard let attributes = try? fileManager.attributesOfItem(atPath: url.path) else { return now } // Also assume the file is modified now if the attribute doesn't exist. diff --git a/Tests/AppTests/AnalyzerTests.swift b/Tests/AppTests/AnalyzerTests.swift index e24bb2a82..6df84f97e 100644 --- a/Tests/AppTests/AnalyzerTests.swift +++ b/Tests/AppTests/AnalyzerTests.swift @@ -507,11 +507,7 @@ class AnalyzerTests: AppTestCase { Current.fileManager.fileExists = { @Sendable _ in true } let commands = QueueIsolated<[String]>([]) Current.shell.run = { @Sendable cmd, path in - // mask variable checkout - let checkoutDir = Current.fileManager.checkoutsDirectory() - commands.withValue { - $0.append(cmd.description.replacingOccurrences(of: checkoutDir, with: "...")) - } + commands.withValue { $0.append(cmd.description) } return "" } let jpr = try await Package.fetchCandidate(app.db, id: pkg.id!) @@ -961,17 +957,13 @@ class AnalyzerTests: AppTestCase { try await savePackage(on: app.db, "1".asGithubUrl.url, processingStage: .ingestion) let pkgs = try await Package.fetchCandidates(app.db, for: .analysis, limit: 10) - let checkoutDir = Current.fileManager.checkoutsDirectory() // 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 { - let c = cmd.description.replacingOccurrences(of: checkoutDir, with: "...") - $0.append(c) - } + commands.withValue { $0.append(cmd.description) } return "" } @@ -995,17 +987,13 @@ class AnalyzerTests: AppTestCase { try await savePackage(on: app.db, "1".asGithubUrl.url, processingStage: .ingestion) let pkgs = try await Package.fetchCandidates(app.db, for: .analysis, limit: 10) - let checkoutDir = Current.fileManager.checkoutsDirectory() // 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 { - let c = cmd.description.replacingOccurrences(of: checkoutDir, with: "${checkouts}") - $0.append(c) - } + commands.withValue { $0.append(cmd.description) } if cmd == .gitCheckout(branch: "master") { throw TestError.simulatedCheckoutError } @@ -1155,11 +1143,7 @@ class AnalyzerTests: AppTestCase { Current.fileManager.fileExists = { @Sendable _ in true } let commands = QueueIsolated<[String]>([]) Current.shell.run = { @Sendable cmd, _ in - commands.withValue { - // mask variable checkout - let checkoutDir = Current.fileManager.checkoutsDirectory() - $0.append(cmd.description.replacingOccurrences(of: checkoutDir, with: "...")) - } + commands.withValue { $0.append(cmd.description) } if cmd == .gitFetchAndPruneTags { throw TestError.simulatedFetchError } return "" } @@ -1235,6 +1219,7 @@ class AnalyzerTests: AppTestCase { // 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) @@ -1243,13 +1228,13 @@ class AnalyzerTests: AppTestCase { if path.hasSuffix("github.com-foo-1") { return false } return true } - let repoDir = try Current.fileManager.checkoutsDirectory() + "/" + XCTUnwrap(pkg.cacheDirectoryName) + 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() } - fatalError("should not be reached") + throw TestError.unknownCommand } } let lastUpdated = Date() @@ -1268,16 +1253,16 @@ class AnalyzerTests: AppTestCase { func test_trimCheckouts() throws { try withDependencies { $0.date.now = .t0 - } operation: { - // setup - Current.fileManager.checkoutsDirectory = { "/checkouts" } - Current.fileManager.contentsOfDirectory = { @Sendable _ in ["foo", "bar"] } - Current.fileManager.attributesOfItem = { @Sendable path in + $0.fileManager.attributesOfItem = { @Sendable path in [ "/checkouts/foo": [FileAttributeKey.modificationDate: Date.t0.adding(days: -31)], "/checkouts/bar": [FileAttributeKey.modificationDate: Date.t0.adding(days: -29)], ][path]! } + $0.fileManager.checkoutsDirectory = { "/checkouts" } + $0.fileManager.contentsOfDirectory = { @Sendable _ in ["foo", "bar"] } + } operation: { + // setup let removedPaths = NIOLockedValueBox<[String]>([]) Current.fileManager.removeItem = { @Sendable p in removedPaths.withLockedValue { $0.append(p) } } diff --git a/Tests/AppTests/AppEnvironmentTests.swift b/Tests/AppTests/AppEnvironmentTests.swift index a2ae993d8..c7990ce74 100644 --- a/Tests/AppTests/AppEnvironmentTests.swift +++ b/Tests/AppTests/AppEnvironmentTests.swift @@ -23,12 +23,16 @@ import Vapor class AppEnvironmentTests: XCTestCase { func test_Filemanager_checkoutsDirectory() throws { - Current.fileManager = .live - unsetenv("CHECKOUTS_DIR") - XCTAssertEqual(Current.fileManager.checkoutsDirectory(), - DirectoryConfiguration.detect().workingDirectory + "SPI-checkouts") - setenv("CHECKOUTS_DIR", "/tmp/foo", 1) - XCTAssertEqual(Current.fileManager.checkoutsDirectory(), "/tmp/foo") + withDependencies { + $0.fileManager.checkoutsDirectory = FileManagerClient.liveValue.checkoutsDirectory + } operation: { + unsetenv("CHECKOUTS_DIR") + @Dependency(\.fileManager) var fileManager + XCTAssertEqual(fileManager.checkoutsDirectory(), + DirectoryConfiguration.detect().workingDirectory + "SPI-checkouts") + setenv("CHECKOUTS_DIR", "/tmp/foo", 1) + XCTAssertEqual(fileManager.checkoutsDirectory(), "/tmp/foo") + } } func test_maintenanceMessage() throws { diff --git a/Tests/AppTests/BlogActionsModelTests.swift b/Tests/AppTests/BlogActionsModelTests.swift index 198144087..506d4ea37 100644 --- a/Tests/AppTests/BlogActionsModelTests.swift +++ b/Tests/AppTests/BlogActionsModelTests.swift @@ -22,22 +22,21 @@ import Dependencies class BlogActionsModelTests: AppTestCase { func test_init_loadSummaries() async throws { - Current.fileManager.contents = { @Sendable _ in - """ - - slug: post-1 - title: Blog post title one - summary: Summary of blog post one - published_at: 2024-01-01 - published: true - - slug: post-2 - title: Blog post title two - summary: Summary of blog post two - published_at: 2024-01-02 - published: false - """.data(using: .utf8) - } - try withDependencies { + $0.fileManager.contents = { @Sendable _ in + """ + - slug: post-1 + title: Blog post title one + summary: Summary of blog post one + published_at: 2024-01-01 + published: true + - slug: post-2 + title: Blog post title two + summary: Summary of blog post two + published_at: 2024-01-02 + published: false + """.data(using: .utf8) + } $0.timeZone = .utc } operation: { try withDependencies { // Ensure dev shows all summaries @@ -75,24 +74,26 @@ class BlogActionsModelTests: AppTestCase { } func test_postSummary_postMarkdown_load() async throws { - Current.fileManager.contents = { @Sendable _ in - """ - This is some Markdown with [a link](https://example.com) and some _formatting_. - """.data(using: .utf8) - } - let summary = BlogActions.Model.PostSummary.mock() + withDependencies { + $0.fileManager.contents = { @Sendable _ in + """ + This is some Markdown with [a link](https://example.com) and some _formatting_. + """.data(using: .utf8) + } + } operation: { + let summary = BlogActions.Model.PostSummary.mock() - // MUT - let markdown = summary.postMarkdown + // MUT + let markdown = summary.postMarkdown - XCTAssertEqual(markdown, "

This is some Markdown with a link and some formatting.

") + XCTAssertEqual(markdown, "

This is some Markdown with a link and some formatting.

") + } } func test_decode_posts_yml() async throws { // setup - Current.fileManager = .live - try withDependencies { + $0.fileManager.contents = FileManagerClient.liveValue.contents(atPath:) $0.timeZone = .utc } operation: { // MUT diff --git a/Tests/AppTests/Mocks/AppFileManager+mock.swift b/Tests/AppTests/Mocks/AppFileManager+mock.swift index 6f61afc49..7ac649cf0 100644 --- a/Tests/AppTests/Mocks/AppFileManager+mock.swift +++ b/Tests/AppTests/Mocks/AppFileManager+mock.swift @@ -21,10 +21,6 @@ extension App.FileManager { static let mock = Self.mock(fileExists: true) static func mock(fileExists: Bool) -> Self { .init( - attributesOfItem: { _ in [:] }, - contentsOfDirectory: { _ in [] }, - contents: { _ in .init() }, - checkoutsDirectory: { DirectoryConfiguration.detect().workingDirectory + "SPI-checkouts" }, createDirectory: { _, _, _ in }, fileExists: { path in fileExists }, removeItem: { _ in }, diff --git a/Tests/AppTests/PackageContributorsTests.swift b/Tests/AppTests/PackageContributorsTests.swift index 99bb29f6c..27230c402 100644 --- a/Tests/AppTests/PackageContributorsTests.swift +++ b/Tests/AppTests/PackageContributorsTests.swift @@ -83,12 +83,8 @@ class PackageContributorsTests : AppTestCase { """ } - guard let gitCacheDirectoryPath = Current.fileManager.cacheDirectoryPath(for: pkg) else { - throw AppError.invalidPackageCachePath(pkg.id, pkg.url) - } - // MUT - let pkgAuthors = try await PackageContributors.extract(gitCacheDirectoryPath: gitCacheDirectoryPath, + let pkgAuthors = try await PackageContributors.extract(gitCacheDirectoryPath: "", packageID: pkg.id) XCTAssertEqual(pkgAuthors.authors, [Author(name: "Person 1") , diff --git a/Tests/AppTests/WebpageSnapshotTests.swift b/Tests/AppTests/WebpageSnapshotTests.swift index 377be204b..fb2f3d9db 100644 --- a/Tests/AppTests/WebpageSnapshotTests.swift +++ b/Tests/AppTests/WebpageSnapshotTests.swift @@ -579,10 +579,70 @@ class WebpageSnapshotTests: SnapshotTestCase { } func test_ReadyForSwift6Show() throws { - let model = ReadyForSwift6Show.Model() - let page = { ReadyForSwift6Show.View(path: "", model: model).document() } + withDependencies { + $0.fileManager.contents = { @Sendable path in + switch path { + case _ where path.hasSuffix("rfs6-packages.json"): + return Data( + """ + [ + { + "id" : "all", + "name" : "All packages", + "total" : 3395, + "values" : [ + { + "date" : "2024-05-04", + "toolchainId" : "org.swift.600202404221a", + "toolchainLabel" : "Swift 6.0 Development Snapshot 2024-04-22 (a)", + "value" : 1295 + } + ] + } + ] + """.utf8 + ) + case _ where path.hasSuffix("rfs6-errors.json"): + return Data( + """ + [ + { + "id" : "all", + "name" : "All packages", + "total" : 3395, + "values" : [ + { + "date" : "2024-05-04", + "toolchainId" : "org.swift.600202404221a", + "toolchainLabel" : "Swift 6.0 Development Snapshot 2024-04-22 (a)", + "value" : 56911 + } + ] + } + ] + """.utf8 + ) + case _ where path.hasSuffix("rfs6-events.json"): + return Data( + """ + [ + { + "date": "2024-06-10", + "value": "Xcode 16 beta 1 released at WWDC '24" + } + ] + """.utf8 + ) + default: + return nil + } + } + } operation: { + let model = ReadyForSwift6Show.Model() + let page = { ReadyForSwift6Show.View(path: "", model: model).document() } - assertSnapshot(of: page, as: .html) + assertSnapshot(of: page, as: .html) + } } func test_ValidateSPIManifest_show() throws { @@ -602,20 +662,22 @@ class WebpageSnapshotTests: SnapshotTestCase { } func test_Blog_show() { - Current.fileManager.contents = { @Sendable _ in - """ - This is some Markdown with [a link](https://example.com) and some _formatting_. - - ![Two logos](/images/blog/swift-package-index-and-apple-logos.png) - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ut ante vel diam sagittis hendrerit id eget nunc. Proin non ex eget dolor tristique lacinia placerat et turpis. In dui dui, malesuada eu lectus nec, rhoncus feugiat nisi. Fusce pulvinar neque quis rutrum ullamcorper. Aliquam erat volutpat. Aliquam et molestie velit. Suspendisse sollicitudin arcu lorem, tristique iaculis quam lobortis non. Vivamus in euismod velit. Proin justo arcu, placerat ac sapien sed, tempus aliquet ligula. Pellentesque ultricies, diam eget porta maximus, massa metus sagittis tellus, in vehicula elit erat sed metus. In mattis arcu imperdiet placerat vehicula. Vestibulum elementum iaculis tortor, sed feugiat ante posuere quis. Sed hendrerit, nisl ut tristique tincidunt, odio neque interdum ex, eget consectetur lectus dui eget felis. Donec in viverra lectus. Nunc fringilla molestie nibh ac iaculis. Morbi ac risus ut tellus posuere laoreet. Donec vehicula non sapien et mattis. Phasellus iaculis lacinia ipsum, eget congue nisl ornare ac. Vestibulum nec nibh suscipit, facilisis risus id, sollicitudin quam. Pellentesque eu quam quis magna sollicitudin consequat ac varius massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse et quam dui. Nunc dapibus erat vel elementum facilisis. Quisque mollis, lacus sit amet tincidunt egestas, nunc purus viverra eros, ut vestibulum eros eros nec nulla. Morbi ultrices, arcu non volutpat tincidunt, orci justo commodo mi, vel scelerisque odio turpis nec velit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque luctus a nisi tristique ullamcorper. Nunc fermentum lorem eget augue eleifend interdum. Nullam tincidunt turpis euismod convallis pretium. Etiam accumsan fermentum consectetur. Maecenas est justo, vulputate finibus blandit pulvinar, malesuada sit amet tellus. Etiam quis mauris a nulla gravida placerat et imperdiet justo. Nullam vitae leo in velit viverra lobortis. Fusce lacinia quam erat. Suspendisse ut metus magna. Vestibulum consectetur ligula at turpis tristique molestie. Nunc maximus tempor porta. Morbi in mauris vitae eros laoreet tincidunt feugiat vel lacus. Nullam dignissim non dolor sed fringilla. Morbi eget vestibulum odio, ac hendrerit massa. Nullam sodales bibendum purus, et convallis nulla facilisis vitae. Aliquam eget sem lacus. Morbi hendrerit nec nibh vitae tristique. Aenean id erat sit amet justo commodo pharetra. Nam at erat accumsan, consectetur nunc sit amet, convallis nibh. Quisque semper ex orci, id vehicula magna ornare laoreet. Donec ac accumsan libero, non imperdiet dolor. Duis imperdiet tempor erat quis iaculis. Etiam eget sodales lacus, ac semper tortor. Aenean sed dolor nec dolor pretium placerat. Cras eleifend felis magna, nec elementum leo pharetra in. Nam enim nulla, sodales in eleifend ut, imperdiet eget neque. Praesent congue turpis sed felis maximus dapibus. Mauris efficitur nisi in euismod mattis. Nullam semper dui risus. Proin mollis interdum turpis, vestibulum tempor risus blandit faucibus. Nulla posuere sagittis ligula et commodo. - """.data(using: .utf8) + withDependencies { + $0.fileManager.contents = { @Sendable _ in + """ + This is some Markdown with [a link](https://example.com) and some _formatting_. + + ![Two logos](/images/blog/swift-package-index-and-apple-logos.png) + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ut ante vel diam sagittis hendrerit id eget nunc. Proin non ex eget dolor tristique lacinia placerat et turpis. In dui dui, malesuada eu lectus nec, rhoncus feugiat nisi. Fusce pulvinar neque quis rutrum ullamcorper. Aliquam erat volutpat. Aliquam et molestie velit. Suspendisse sollicitudin arcu lorem, tristique iaculis quam lobortis non. Vivamus in euismod velit. Proin justo arcu, placerat ac sapien sed, tempus aliquet ligula. Pellentesque ultricies, diam eget porta maximus, massa metus sagittis tellus, in vehicula elit erat sed metus. In mattis arcu imperdiet placerat vehicula. Vestibulum elementum iaculis tortor, sed feugiat ante posuere quis. Sed hendrerit, nisl ut tristique tincidunt, odio neque interdum ex, eget consectetur lectus dui eget felis. Donec in viverra lectus. Nunc fringilla molestie nibh ac iaculis. Morbi ac risus ut tellus posuere laoreet. Donec vehicula non sapien et mattis. Phasellus iaculis lacinia ipsum, eget congue nisl ornare ac. Vestibulum nec nibh suscipit, facilisis risus id, sollicitudin quam. Pellentesque eu quam quis magna sollicitudin consequat ac varius massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse et quam dui. Nunc dapibus erat vel elementum facilisis. Quisque mollis, lacus sit amet tincidunt egestas, nunc purus viverra eros, ut vestibulum eros eros nec nulla. Morbi ultrices, arcu non volutpat tincidunt, orci justo commodo mi, vel scelerisque odio turpis nec velit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque luctus a nisi tristique ullamcorper. Nunc fermentum lorem eget augue eleifend interdum. Nullam tincidunt turpis euismod convallis pretium. Etiam accumsan fermentum consectetur. Maecenas est justo, vulputate finibus blandit pulvinar, malesuada sit amet tellus. Etiam quis mauris a nulla gravida placerat et imperdiet justo. Nullam vitae leo in velit viverra lobortis. Fusce lacinia quam erat. Suspendisse ut metus magna. Vestibulum consectetur ligula at turpis tristique molestie. Nunc maximus tempor porta. Morbi in mauris vitae eros laoreet tincidunt feugiat vel lacus. Nullam dignissim non dolor sed fringilla. Morbi eget vestibulum odio, ac hendrerit massa. Nullam sodales bibendum purus, et convallis nulla facilisis vitae. Aliquam eget sem lacus. Morbi hendrerit nec nibh vitae tristique. Aenean id erat sit amet justo commodo pharetra. Nam at erat accumsan, consectetur nunc sit amet, convallis nibh. Quisque semper ex orci, id vehicula magna ornare laoreet. Donec ac accumsan libero, non imperdiet dolor. Duis imperdiet tempor erat quis iaculis. Etiam eget sodales lacus, ac semper tortor. Aenean sed dolor nec dolor pretium placerat. Cras eleifend felis magna, nec elementum leo pharetra in. Nam enim nulla, sodales in eleifend ut, imperdiet eget neque. Praesent congue turpis sed felis maximus dapibus. Mauris efficitur nisi in euismod mattis. Nullam semper dui risus. Proin mollis interdum turpis, vestibulum tempor risus blandit faucibus. Nulla posuere sagittis ligula et commodo. + """.data(using: .utf8) + } + } operation: { + let model = BlogActions.Model.PostSummary.mock() + let page = { BlogActions.Show.View(path: "", model: model).document() } + + assertSnapshot(of: page, as: .html) } - - let model = BlogActions.Model.PostSummary.mock() - let page = { BlogActions.Show.View(path: "", model: model).document() } - - assertSnapshot(of: page, as: .html) } } diff --git a/Tests/AppTests/__Snapshots__/AnalyzerTests/test_issue_498.1.txt b/Tests/AppTests/__Snapshots__/AnalyzerTests/test_issue_498.1.txt index 0930c28e9..b8d8a1d1e 100644 --- a/Tests/AppTests/__Snapshots__/AnalyzerTests/test_issue_498.1.txt +++ b/Tests/AppTests/__Snapshots__/AnalyzerTests/test_issue_498.1.txt @@ -1,9 +1,9 @@ ▿ 8 elements - - "rm -f ${checkouts}/github.com-foo-1/.git/HEAD.lock" - - "rm -f ${checkouts}/github.com-foo-1/.git/index.lock" + - "rm -f SPI-checkouts/github.com-foo-1/.git/HEAD.lock" + - "rm -f SPI-checkouts/github.com-foo-1/.git/index.lock" - "git reset --hard" - "git clean -fdx" - "git fetch --tags --prune-tags --prune" - "git checkout master --quiet" - - "rm -r -f ${checkouts}/github.com-foo-1" - - "git clone https://github.com/foo/1 ${checkouts}/github.com-foo-1 --quiet" + - "rm -r -f SPI-checkouts/github.com-foo-1" + - "git clone https://github.com/foo/1 SPI-checkouts/github.com-foo-1 --quiet" diff --git a/Tests/AppTests/__Snapshots__/AnalyzerTests/test_issue_693.1.txt b/Tests/AppTests/__Snapshots__/AnalyzerTests/test_issue_693.1.txt index d05a50b50..84dcaaa21 100644 --- a/Tests/AppTests/__Snapshots__/AnalyzerTests/test_issue_693.1.txt +++ b/Tests/AppTests/__Snapshots__/AnalyzerTests/test_issue_693.1.txt @@ -1,8 +1,8 @@ ▿ 7 elements - - "rm -f .../github.com-foo-1/.git/HEAD.lock" - - "rm -f .../github.com-foo-1/.git/index.lock" + - "rm -f SPI-checkouts/github.com-foo-1/.git/HEAD.lock" + - "rm -f SPI-checkouts/github.com-foo-1/.git/index.lock" - "git reset --hard" - "git clean -fdx" - "git fetch --tags --prune-tags --prune" - - "rm -r -f .../github.com-foo-1" - - "git clone https://github.com/foo/1 .../github.com-foo-1 --quiet" + - "rm -r -f SPI-checkouts/github.com-foo-1" + - "git clone https://github.com/foo/1 SPI-checkouts/github.com-foo-1 --quiet" diff --git a/Tests/AppTests/__Snapshots__/AnalyzerTests/test_issue_70.1.txt b/Tests/AppTests/__Snapshots__/AnalyzerTests/test_issue_70.1.txt index a97fbbabf..c16d7c7f0 100644 --- a/Tests/AppTests/__Snapshots__/AnalyzerTests/test_issue_70.1.txt +++ b/Tests/AppTests/__Snapshots__/AnalyzerTests/test_issue_70.1.txt @@ -1,6 +1,6 @@ ▿ 7 elements - - "rm -f .../github.com-foo-1/.git/HEAD.lock" - - "rm -f .../github.com-foo-1/.git/index.lock" + - "rm -f SPI-checkouts/github.com-foo-1/.git/HEAD.lock" + - "rm -f SPI-checkouts/github.com-foo-1/.git/index.lock" - "git reset --hard" - "git clean -fdx" - "git fetch --tags --prune-tags --prune" diff --git a/Tests/AppTests/__Snapshots__/AnalyzerTests/test_refreshCheckout.1.txt b/Tests/AppTests/__Snapshots__/AnalyzerTests/test_refreshCheckout.1.txt index 43de439b6..02371cf7f 100644 --- a/Tests/AppTests/__Snapshots__/AnalyzerTests/test_refreshCheckout.1.txt +++ b/Tests/AppTests/__Snapshots__/AnalyzerTests/test_refreshCheckout.1.txt @@ -1,6 +1,6 @@ ▿ 7 elements - - "rm -f .../github.com-foo-1/.git/HEAD.lock" - - "rm -f .../github.com-foo-1/.git/index.lock" + - "rm -f SPI-checkouts/github.com-foo-1/.git/HEAD.lock" + - "rm -f SPI-checkouts/github.com-foo-1/.git/index.lock" - "git reset --hard" - "git clean -fdx" - "git fetch --tags --prune-tags --prune" diff --git a/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/test_ReadyForSwift6Show.1.html b/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/test_ReadyForSwift6Show.1.html index 1278f7dd9..722e098a3 100644 --- a/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/test_ReadyForSwift6Show.1.html +++ b/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/test_ReadyForSwift6Show.1.html @@ -90,12 +90,18 @@

Ready for Swift 6

Total packages with Swift 6 zero data race safety errors

This chart shows packages with zero data race safety compiler diagnostics during a successful build on at least one tested platform.

-

Couldn’t load chart data.

+
+ + +

Total Swift 6 data race safety errors

This chart shows the total number of all data race safety diagnostics across all packages.

-

Couldn’t load chart data.

+
+ + +

Frequently asked questions

Q: What is a “data race safety error”?