diff --git a/Tests/AppTests/MaintainerInfoIndexViewTests.swift b/Tests/AppTests/MaintainerInfoIndexViewTests.swift index 14382f260..72a0812a4 100644 --- a/Tests/AppTests/MaintainerInfoIndexViewTests.swift +++ b/Tests/AppTests/MaintainerInfoIndexViewTests.swift @@ -14,21 +14,20 @@ @testable import App -import XCTest +import Testing -final class MaintainerInfoIndexViewTests: XCTestCase { - func test_spiManifestCommonUseCasesDocLink() throws { - XCTAssertEqual( - MaintainerInfoIndex.View.spiManifestCommonUseCasesDocLink(.linuxImages), - "/SwiftPackageIndex/SPIManifest/~/documentation/spimanifest/commonusecases#linuxImages" +@Suite struct MaintainerInfoIndexViewTests { + + @Test func spiManifestCommonUseCasesDocLink() throws { + #expect( + MaintainerInfoIndex.View.spiManifestCommonUseCasesDocLink(.linuxImages) == "/SwiftPackageIndex/SPIManifest/~/documentation/spimanifest/commonusecases#linuxImages" ) } - func test_spiManifestDocLink() throws { - XCTAssertEqual( - MaintainerInfoIndex.View.spiManifestDocLink(), - "/SwiftPackageIndex/SPIManifest/~/documentation/spimanifest" + @Test func spiManifestDocLink() throws { + #expect( + MaintainerInfoIndex.View.spiManifestDocLink() == "/SwiftPackageIndex/SPIManifest/~/documentation/spimanifest" ) } diff --git a/Tests/AppTests/ManifestTests.swift b/Tests/AppTests/ManifestTests.swift index 1e4d122e3..ea16ff7b6 100644 --- a/Tests/AppTests/ManifestTests.swift +++ b/Tests/AppTests/ManifestTests.swift @@ -12,14 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + @testable import App -import XCTest +import Testing -class ManifestTests: XCTestCase { +@Suite struct ManifestTests { - func test_decode_Product_Type() throws { + @Test func decode_Product_Type() throws { // Test product type decoding. // JSON snippets via `swift package dump-package` from the following // Package.swift `products` definition: @@ -45,8 +47,7 @@ class ManifestTests: XCTestCase { } } """.utf8) - XCTAssertEqual(try JSONDecoder().decode(Test.self, from: data), - .init(type: .executable)) + #expect(try JSONDecoder().decode(Test.self, from: data) == .init(type: .executable)) } do { // lib - automatic let data = Data(""" @@ -56,8 +57,7 @@ class ManifestTests: XCTestCase { } } """.utf8) - XCTAssertEqual(try JSONDecoder().decode(Test.self, from: data), - .init(type: .library(.automatic))) + #expect(try JSONDecoder().decode(Test.self, from: data) == .init(type: .library(.automatic))) } do { // lib - dynamic let data = Data(""" @@ -67,8 +67,7 @@ class ManifestTests: XCTestCase { } } """.utf8) - XCTAssertEqual(try JSONDecoder().decode(Test.self, from: data), - .init(type: .library(.dynamic))) + #expect(try JSONDecoder().decode(Test.self, from: data) == .init(type: .library(.dynamic))) } do { // lib - static let data = Data(""" @@ -78,8 +77,7 @@ class ManifestTests: XCTestCase { } } """.utf8) - XCTAssertEqual(try JSONDecoder().decode(Test.self, from: data), - .init(type: .library(.static))) + #expect(try JSONDecoder().decode(Test.self, from: data) == .init(type: .library(.static))) } do { // test let data = Data(""" @@ -89,30 +87,29 @@ class ManifestTests: XCTestCase { } } """.utf8) - XCTAssertEqual(try JSONDecoder().decode(Test.self, from: data), - .init(type: .test)) + #expect(try JSONDecoder().decode(Test.self, from: data) == .init(type: .test)) } } - func test_decode_basic() throws { + @Test func decode_basic() throws { let data = try fixtureData(for: "manifest-1.json") let m = try JSONDecoder().decode(Manifest.self, from: data) - XCTAssertEqual(m.name, "SPI-Server") - XCTAssertEqual(m.platforms, [.init(platformName: .macos, version: "10.15")]) - XCTAssertEqual(m.products, [.init(name: "Some Product", + #expect(m.name == "SPI-Server") + #expect(m.platforms == [.init(platformName: .macos, version: "10.15")]) + #expect(m.products == [.init(name: "Some Product", targets: ["t1", "t2"], type: .library(.automatic))]) - XCTAssertEqual(m.swiftLanguageVersions, ["4", "4.2", "5"]) - XCTAssertEqual(m.targets, [.init(name: "App", type: .regular), + #expect(m.swiftLanguageVersions == ["4", "4.2", "5"]) + #expect(m.targets == [.init(name: "App", type: .regular), .init(name: "Run", type: .regular), .init(name: "AppTests", type: .test)]) - XCTAssertEqual(m.toolsVersion, .init(version: "5.2.0")) + #expect(m.toolsVersion == .init(version: "5.2.0")) } - func test_decode_products_complex() throws { + @Test func decode_products_complex() throws { let data = try fixtureData(for: "SwiftNIO.json") let m = try JSONDecoder().decode(Manifest.self, from: data) - XCTAssertEqual(m.products, [ + #expect(m.products == [ .init(name: "NIOEchoServer", targets: ["NIOEchoServer"], type: .executable), @@ -176,28 +173,27 @@ class ManifestTests: XCTestCase { ]) } - func test_platform_list() throws { + @Test func platform_list() throws { // Test to ensure the platforms listed in the DTO struct Manifest.Platform.Name // do not accidentally diverge from those in the db entity's Platform.Name - XCTAssertEqual(Manifest.Platform.Name.allCases.map(\.rawValue).sorted(), - Platform.Name.allCases.map(\.rawValue).sorted()) + #expect(Manifest.Platform.Name.allCases.map(\.rawValue).sorted() == Platform.Name.allCases.map(\.rawValue).sorted()) } - func test_decode_plugin_products() throws { + @Test func decode_plugin_products() throws { let data = try fixtureData(for: "manifest-plugin.json") let m = try JSONDecoder().decode(Manifest.self, from: data) - XCTAssertEqual(m.products, [ + #expect(m.products == [ .init(name: "Swift-DocC", targets: ["Swift-DocC"], type: .plugin), .init(name: "Swift-DocC Preview", targets: ["Swift-DocC Preview"], type: .plugin), ]) } - func test_issue_2875() throws { + @Test func issue_2875() throws { // Support decoding custom platform with different capitalisation // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2875 let data = try fixtureData(for: "Lottie-ios.json") let m = try JSONDecoder().decode(Manifest.self, from: data) - XCTAssertEqual(m.platforms, [ + #expect(m.platforms == [ .init(platformName: .ios, version: "11.0"), .init(platformName: .macos, version: "10.11"), .init(platformName: .tvos, version: "11.0"), diff --git a/Tests/AppTests/MastodonTests.swift b/Tests/AppTests/MastodonTests.swift index a21db530b..04c9bfa1c 100644 --- a/Tests/AppTests/MastodonTests.swift +++ b/Tests/AppTests/MastodonTests.swift @@ -16,17 +16,17 @@ import Dependencies import SemanticVersion -import XCTVapor +import Testing -final class MastodonTests: AppTestCase { +@Suite struct MastodonTests { - func test_apiURL() throws { + @Test func apiURL() throws { let url = try Mastodon.apiURL(with: "message") - XCTAssert(url.contains("visibility=unlisted"), "was: \(url)") + #expect(url.contains("visibility=unlisted"), "was: \(url)") } - func test_endToEnd() async throws { + @Test func endToEnd() async throws { let message = QueueIsolated(nil) try await withDependencies { $0.environment.allowSocialPosts = { true } @@ -52,7 +52,7 @@ final class MastodonTests: AppTestCase { if message.value == nil { message.setValue(msg) } else { - XCTFail("message must only be set once") + Issue.record("message must only be set once") } } $0.shell.run = { @Sendable cmd, path in @@ -62,67 +62,69 @@ final class MastodonTests: AppTestCase { return "" } } operation: { - // setup - let url = "https://github.com/foo/bar" - - try await withDependencies { - $0.date.now = .now - $0.packageListRepository.fetchPackageList = { @Sendable _ in [url.url] } - $0.packageListRepository.fetchPackageDenyList = { @Sendable _ in [] } - $0.packageListRepository.fetchCustomCollections = { @Sendable _ in [] } - $0.packageListRepository.fetchCustomCollection = { @Sendable _, _ in [] } - } operation: { - // run first two processing steps - try await reconcile(client: app.client, database: app.db) - try await Ingestion.ingest(client: app.client, database: app.db, mode: .limit(10)) - - // MUT - analyze, triggering the post - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) - - do { - let msg = try XCTUnwrap(message.value) - XCTAssertTrue(msg.hasPrefix("📦 foo just added a new package, Mock"), "was \(msg)") - } + try await withApp { app in + // setup + let url = "https://github.com/foo/bar" - // run stages again to simulate the cycle... - message.setValue(nil) - try await reconcile(client: app.client, database: app.db) - } + try await withDependencies { + $0.date.now = .now + $0.packageListRepository.fetchPackageList = { @Sendable _ in [url.url] } + $0.packageListRepository.fetchPackageDenyList = { @Sendable _ in [] } + $0.packageListRepository.fetchCustomCollections = { @Sendable _ in [] } + $0.packageListRepository.fetchCustomCollection = { @Sendable _, _ in [] } + } operation: { + // run first two processing steps + try await reconcile(client: app.client, database: app.db) + try await Ingestion.ingest(client: app.client, database: app.db, mode: .limit(10)) - try await withDependencies { - $0.date.now = .now.addingTimeInterval(Constants.reIngestionDeadtime) - } operation: { - try await Ingestion.ingest(client: app.client, database: app.db, mode: .limit(10)) + // MUT - analyze, triggering the post + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(10)) - // MUT - analyze, triggering posts if any - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) - } + do { + let msg = try #require(message.value) + #expect(msg.hasPrefix("📦 foo just added a new package, Mock"), "was \(msg)") + } - // validate - there are no new posts to send - XCTAssertNil(message.value) + // run stages again to simulate the cycle... + message.setValue(nil) + try await reconcile(client: app.client, database: app.db) + } - // Now simulate receiving a package update: version 2.0.0 - try await withDependencies { - $0.git.getTags = { @Sendable _ in [.tag(2, 0, 0)] } - } operation: { try await withDependencies { - // fast forward our clock by the deadtime interval again (*2) and re-ingest - $0.date.now = .now.addingTimeInterval(Constants.reIngestionDeadtime * 2) + $0.date.now = .now.addingTimeInterval(Constants.reIngestionDeadtime) } operation: { try await Ingestion.ingest(client: app.client, database: app.db, mode: .limit(10)) - // MUT - analyze again + + // MUT - analyze, triggering posts if any try await Analyze.analyze(client: app.client, database: app.db, mode: .limit(10)) } - // validate - let msg = try XCTUnwrap(message.value) - XCTAssertTrue(msg.hasPrefix("⬆️ foo just released Mock v2.0.0"), "was: \(msg)") + // validate - there are no new posts to send + #expect(message.value == nil) + + // Now simulate receiving a package update: version 2.0.0 + try await withDependencies { + $0.git.getTags = { @Sendable _ in [.tag(2, 0, 0)] } + } operation: { + try await withDependencies { + // fast forward our clock by the deadtime interval again (*2) and re-ingest + $0.date.now = .now.addingTimeInterval(Constants.reIngestionDeadtime * 2) + } operation: { + try await Ingestion.ingest(client: app.client, database: app.db, mode: .limit(10)) + // MUT - analyze again + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(10)) + } + + // validate + let msg = try #require(message.value) + #expect(msg.hasPrefix("⬆️ foo just released Mock v2.0.0"), "was: \(msg)") + } } } } diff --git a/Tests/AppTests/MetricsTests.swift b/Tests/AppTests/MetricsTests.swift index add475694..b6eb1f8eb 100644 --- a/Tests/AppTests/MetricsTests.swift +++ b/Tests/AppTests/MetricsTests.swift @@ -12,16 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + @testable import App import Dependencies import Prometheus -import XCTest +import Testing -class MetricsTests: AppTestCase { +@Suite struct MetricsTests { - func test_basic() async throws { + @Test func basic() async throws { try await withDependencies { $0.buildSystem.triggerBuild = { @Sendable _, _, _, _, _, _, _ in .init(status: .ok, webUrl: "") @@ -29,133 +31,141 @@ class MetricsTests: AppTestCase { $0.environment.builderToken = { "builder token" } $0.environment.gitlabPipelineToken = { "pipeline token" } } operation: { - // setup - trigger build to increment counter - let versionId = UUID() - do { // save minimal package + version - let p = Package(id: UUID(), url: "1") - try await p.save(on: app.db) - try await Version(id: versionId, package: p, reference: .branch("main")).save(on: app.db) + try await withApp { app in + // setup - trigger build to increment counter + let versionId = UUID() + do { // save minimal package + version + let p = Package(id: UUID(), url: "1") + try await p.save(on: app.db) + try await Version(id: versionId, package: p, reference: .branch("main")).save(on: app.db) + } + try await triggerBuildsUnchecked(on: app.db, + triggers: [ + .init(versionId: versionId, + buildPairs: [.init(.macosSpm, .v3)])! + ]) + + // MUT + try await app.test(.GET, "metrics", afterResponse: { res async in + // validation + #expect(res.status == .ok) + let content = res.body.asString() + #expect(content.contains( + #"spi_build_trigger_count{swiftVersion="\#(SwiftVersion.v3)", platform="macos-spm"}"# + ), "was:\n\(content)") + }) } - try await triggerBuildsUnchecked(on: app.db, - triggers: [ - .init(versionId: versionId, - buildPairs: [.init(.macosSpm, .v3)])! - ]) - - // MUT - try await app.test(.GET, "metrics", afterResponse: { res async in - // validation - XCTAssertEqual(res.status, .ok) - let content = res.body.asString() - XCTAssertTrue(content.contains( - #"spi_build_trigger_count{swiftVersion="\#(SwiftVersion.v3)", platform="macos-spm"}"# - ), "was:\n\(content)") - }) } } - func test_versions_added() async throws { - // setup - let initialAddedBranch = try XCTUnwrap( - AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .branch)) - ) - let initialAddedTag = try XCTUnwrap( - AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .tag)) - ) - let initialDeletedBranch = try XCTUnwrap( - AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .branch)) - ) - let initialDeletedTag = try XCTUnwrap( - AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .tag)) - ) - let pkg = try await savePackage(on: app.db, "1") - let new = [ - try Version(package: pkg, reference: .branch("main")), - try Version(package: pkg, reference: .tag(1, 2, 3)), - try Version(package: pkg, reference: .tag(2, 0, 0)), - ] - let del = [ - try Version(package: pkg, reference: .branch("main")), - try Version(package: pkg, reference: .tag(1, 0, 0)), - ] - try await del.save(on: app.db) - - // MUT - try await Analyze.applyVersionDelta(on: app.db, - delta: .init(toAdd: new, toDelete: del)) - - // validation - XCTAssertEqual( - AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .branch)), - initialAddedBranch + 1 - ) - XCTAssertEqual( - AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .tag)), - initialAddedTag + 2 - ) - XCTAssertEqual( - AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .branch)), - initialDeletedBranch + 1 - ) - XCTAssertEqual( - AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .tag)), - initialDeletedTag + 1 - ) + @Test func versions_added() async throws { + try await withApp { app in + // setup + let initialAddedBranch = try #require( + AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .branch)) + ) + let initialAddedTag = try #require( + AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .tag)) + ) + let initialDeletedBranch = try #require( + AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .branch)) + ) + let initialDeletedTag = try #require( + AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .tag)) + ) + let pkg = try await savePackage(on: app.db, "1") + let new = [ + try Version(package: pkg, reference: .branch("main")), + try Version(package: pkg, reference: .tag(1, 2, 3)), + try Version(package: pkg, reference: .tag(2, 0, 0)), + ] + let del = [ + try Version(package: pkg, reference: .branch("main")), + try Version(package: pkg, reference: .tag(1, 0, 0)), + ] + try await del.save(on: app.db) + + // MUT + try await Analyze.applyVersionDelta(on: app.db, + delta: .init(toAdd: new, toDelete: del)) + + // validation + #expect( + AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .branch)) == initialAddedBranch + 1 + ) + #expect( + AppMetrics.analyzeVersionsAddedCount?.get(.versionLabels(kind: .tag)) == initialAddedTag + 2 + ) + #expect( + AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .branch)) == initialDeletedBranch + 1 + ) + #expect( + AppMetrics.analyzeVersionsDeletedCount?.get(.versionLabels(kind: .tag)) == initialDeletedTag + 1 + ) + } } - func test_reconcileDurationSeconds() async throws { + @Test func reconcileDurationSeconds() async throws { try await withDependencies { $0.packageListRepository.fetchPackageList = { @Sendable _ in ["1", "2", "3"].asURLs } $0.packageListRepository.fetchPackageDenyList = { @Sendable _ in [] } $0.packageListRepository.fetchCustomCollections = { @Sendable _ in [] } } operation: { - // MUT - try await reconcile(client: app.client, database: app.db) + try await withApp { app in + // MUT + try await reconcile(client: app.client, database: app.db) - // validation - XCTAssert((AppMetrics.reconcileDurationSeconds?.get()) ?? 0 > 0) + // validation + #expect((AppMetrics.reconcileDurationSeconds?.get()) ?? 0 > 0) + } } } - func test_ingestDurationSeconds() async throws { - // setup - let pkg = try await savePackage(on: app.db, "1") + @Test func ingestDurationSeconds() async throws { + try await withApp { app in + // setup + let pkg = try await savePackage(on: app.db, "1") - // MUT - try await Ingestion.ingest(client: app.client, database: app.db, mode: .id(pkg.id!)) + // MUT + try await Ingestion.ingest(client: app.client, database: app.db, mode: .id(pkg.id!)) - // validation - XCTAssert((AppMetrics.ingestDurationSeconds?.get()) ?? 0 > 0) + // validation + #expect((AppMetrics.ingestDurationSeconds?.get()) ?? 0 > 0) + } } - func test_analyzeDurationSeconds() async throws { + @Test func analyzeDurationSeconds() async throws { try await withDependencies { $0.fileManager.fileExists = { @Sendable _ in true } } operation: { - // setup - let pkg = try await savePackage(on: app.db, "1") + try await withApp { app in + // 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 + #expect((AppMetrics.analyzeDurationSeconds?.get()) ?? 0 > 0) + } } } - func test_triggerBuildsDurationSeconds() async throws { + @Test func triggerBuildsDurationSeconds() async throws { try await withDependencies { $0.environment.allowBuildTriggers = { true } } operation: { - // setup - let pkg = try await savePackage(on: app.db, "1") - - // MUT - try await triggerBuilds(on: app.db, mode: .packageId(pkg.id!, force: true)) - - // validation - XCTAssert((AppMetrics.buildTriggerDurationSeconds?.get()) ?? 0 > 0) - print(AppMetrics.buildTriggerDurationSeconds!.get()) + try await withApp { app in + // setup + let pkg = try await savePackage(on: app.db, "1") + + // MUT + try await triggerBuilds(on: app.db, mode: .packageId(pkg.id!, force: true)) + + // validation + #expect((AppMetrics.buildTriggerDurationSeconds?.get()) ?? 0 > 0) + print(AppMetrics.buildTriggerDurationSeconds!.get()) + } } } diff --git a/Tests/AppTests/MiscTests.swift b/Tests/AppTests/MiscTests.swift index 1d6abc4d9..1b644951a 100644 --- a/Tests/AppTests/MiscTests.swift +++ b/Tests/AppTests/MiscTests.swift @@ -12,45 +12,43 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + @testable import App -import XCTest +import Testing /// Tests for utilities and extesions that don't each need a full separate test class -class MiscTests: XCTestCase { +@Suite struct MiscTests { - func test_Array_queryString() throws { + @Test func Array_queryString() throws { // Single parameter - XCTAssertEqual([QueryParameter(key: "foo", value: "bar")].queryString(), "?foo=bar") + #expect([QueryParameter(key: "foo", value: "bar")].queryString() == "?foo=bar") // Multiple parameters - XCTAssertEqual([ + #expect([ QueryParameter(key: "foo", value: "bar"), QueryParameter(key: "baz", value: "erp") - ].queryString(), "?foo=bar&baz=erp") + ].queryString() == "?foo=bar&baz=erp") // Single parameter without separator - XCTAssertEqual([QueryParameter(key: "foo", value: "bar")].queryString(includeSeparator: false), "foo=bar") + #expect([QueryParameter(key: "foo", value: "bar")].queryString(includeSeparator: false) == "foo=bar") } - func test_Date_init_yyyyMMdd() throws { - XCTAssertEqual(Date("1970-01-01"), - Date(timeIntervalSince1970: 0)) - XCTAssertEqual(Date("foo"), nil) + @Test func Date_init_yyyyMMdd() throws { + #expect(Date("1970-01-01") == Date(timeIntervalSince1970: 0)) + #expect(Date("foo") == nil) } - func test_Date_iso8691() throws { - XCTAssertEqual(Date("1970-01-01T0:01:23Z"), - Date(timeIntervalSince1970: 83)) + @Test func Date_iso8691() throws { + #expect(Date("1970-01-01T0:01:23Z") == Date(timeIntervalSince1970: 83)) } - func test_Date_LosslessStringConvertible() throws { - XCTAssertEqual(Date("1970-01-01"), - Date(timeIntervalSince1970: 0)) - XCTAssertEqual(Date("1970-01-01T0:01:23Z"), - Date(timeIntervalSince1970: 83)) - XCTAssertEqual(Date("foo"), nil) + @Test func Date_LosslessStringConvertible() throws { + #expect(Date("1970-01-01") == Date(timeIntervalSince1970: 0)) + #expect(Date("1970-01-01T0:01:23Z") == Date(timeIntervalSince1970: 83)) + #expect(Date("foo") == nil) } }