Skip to content

Commit

Permalink
CI: added workaround for Snapshots in Xcode Cloud (#2857)
Browse files Browse the repository at this point in the history
  • Loading branch information
NachoSoto authored Oct 21, 2023
1 parent a9ae7bd commit 4d95c03
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 26 deletions.
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
orbs:
macos: circleci/macos@2.0.1
slack: circleci/slack@4.10.1
codecov: codecov/codecov@3.2.5
codecov: codecov/codecov@3.3.0

version: 2.1

Expand Down Expand Up @@ -454,7 +454,7 @@ jobs:
environment:
SCAN_DEVICE: iPhone 14 (16.4)
- codecov/upload:
xtra_args: "-v --xc --xp fastlane/test_output/xctest/ios/RevenueCat.xcresult"
xtra_args: "-v --xc --xp fastlane/test_output/xctest/ios/RevenueCat.xcresult --preventSymbolicLinks=true"
- compress_result_bundle:
directory: fastlane/test_output/xctest/ios
bundle_name: RevenueCat
Expand Down
2 changes: 2 additions & 0 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,7 @@
4FD368B52AA7D09C00F63354 /* PaywallEventSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallEventSerializer.swift; sourceTree = "<group>"; };
4FD7E8652AABC4470055406F /* PurchasesPaywallEventsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchasesPaywallEventsTests.swift; sourceTree = "<group>"; };
4FDA13662A33D13700C45CFE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
4FDE95A92A769E9E006E7D2D /* XC-AllTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = "XC-AllTests.xctestplan"; path = "Tests/TestPlans/XC-AllTests.xctestplan"; sourceTree = "<group>"; };
4FDF10E62A725EA6004F3680 /* ExternalPurchasesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalPurchasesManager.swift; sourceTree = "<group>"; };
4FDF10E92A726269004F3680 /* ObserverModeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObserverModeManager.swift; sourceTree = "<group>"; };
4FDF10EC2A726291004F3680 /* SK1ProductFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SK1ProductFetcher.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2566,6 +2567,7 @@
B3DB3EA828862898008EB151 /* CI-BackendIntegration.xctestplan */,
57910CBD29C393A6006209D5 /* CI-RevenueCat.xctestplan */,
2C646C282A0EBD0300E5936E /* CI-Snapshots.xctestplan */,
4FDE95A92A769E9E006E7D2D /* XC-AllTests.xctestplan */,
570896B427595C8100296F1C /* Coverage.xctestplan */,
570896B327595C8100296F1C /* RevenueCat.xctestplan */,
570896B527595C8100296F1C /* UnitTests.xctestplan */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@
<TestPlanReference
reference = "container:Tests/TestPlans/CI-Snapshots.xctestplan">
</TestPlanReference>
<TestPlanReference
reference = "container:Tests/TestPlans/XC-AllTests.xctestplan">
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ enum EnvironmentKey: String {
case RCRunningTests = "RCRunningTests"
case RCRunningIntegrationTests = "RCRunningIntegrationTests"
case RCMockAdServicesToken = "RCMockAdServicesToken"
case XCCloud = "XCODE_CLOUD"

}

Expand Down Expand Up @@ -54,6 +55,10 @@ extension ProcessInfo {
return token
}

static var isXcodeCloud: Bool {
return self[.XCCloud] == "1"
}

}

#endif
4 changes: 1 addition & 3 deletions Tests/StoreKitUnitTests/Support/DebugViewSwiftUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@

import Nimble
@testable import RevenueCat
import SnapshotTesting
import XCTest

import SwiftUI
import XCTest

@MainActor
@available(iOS 16.0, *)
Expand Down
58 changes: 58 additions & 0 deletions Tests/TestPlans/XC-AllTests.xctestplan
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"configurations" : [
{
"id" : "62C79EF4-8071-439C-B693-34F1E57211FC",
"name" : "Configuration 1",
"options" : {
"testTimeoutsEnabled" : true
}
}
],
"defaultOptions" : {
"codeCoverage" : false,
"environmentVariableEntries" : [
{
"key" : "RCRunningTests",
"value" : "1"
},
{
"key" : "XCODE_CLOUD",
"value" : "1"
}
],
"maximumTestExecutionTimeAllowance" : 180,
"maximumTestRepetitions" : 3,
"targetForVariableExpansion" : {
"containerPath" : "container:RevenueCat.xcodeproj",
"identifier" : "2DEAC2D926EFE46E006914ED",
"name" : "UnitTestsHostApp"
},
"testExecutionOrdering" : "random",
"testRepetitionMode" : "retryOnFailure",
"testTimeoutsEnabled" : true
},
"testTargets" : [
{
"target" : {
"containerPath" : "container:RevenueCat.xcodeproj",
"identifier" : "2DC5621D24EC63430031F69B",
"name" : "UnitTests"
}
},
{
"target" : {
"containerPath" : "container:RevenueCat.xcodeproj",
"identifier" : "2DAC5F7126F13C9800C5258F",
"name" : "StoreKitUnitTests"
}
},
{
"target" : {
"containerPath" : "container:RevenueCat.xcodeproj",
"identifier" : "5759B330296DF65D002472D5",
"name" : "ReceiptParserTests"
}
}
],
"version" : 1
}
1 change: 0 additions & 1 deletion Tests/UnitTests/Misc/AnyEncodableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
// Created by Nacho Soto on 3/2/22.

import Nimble
import SnapshotTesting
import XCTest

@testable import RevenueCat
Expand Down
39 changes: 19 additions & 20 deletions Tests/UnitTests/Purchasing/ProductRequestDataTests.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Nimble
import SnapshotTesting
import XCTest

@testable import RevenueCat
Expand All @@ -10,88 +9,88 @@ class ProductRequestDataTests: TestCase {
let productIdentifier = "cool_product"
let productData: ProductRequestData = .createMockProductData(productIdentifier: productIdentifier)

assertSnapshot(productData)
self.snapshot(productData)
}

func testAsDictionaryConvertsPaymentModeCorrectly() throws {
var paymentMode: StoreProductDiscount.PaymentMode?
var productData: ProductRequestData = .createMockProductData(paymentMode: paymentMode)

assertSnapshot(productData)
self.snapshot(productData)

paymentMode = .payAsYouGo
productData = .createMockProductData(paymentMode: paymentMode)

assertSnapshot(productData)
self.snapshot(productData)

paymentMode = .freeTrial
productData = .createMockProductData(paymentMode: paymentMode)

assertSnapshot(productData)
self.snapshot(productData)

paymentMode = .payUpFront
productData = .createMockProductData(paymentMode: paymentMode)

assertSnapshot(productData)
self.snapshot(productData)
}

func testAsDictionaryConvertsCurrencyCodeCorrectly() throws {
let currencyCode = "USD"
let productData: ProductRequestData = .createMockProductData(currencyCode: currencyCode)

assertSnapshot(productData)
self.snapshot(productData)
}

func testAsDictionaryConvertsPriceCorrectly() throws {
let price: NSDecimalNumber = 9.99
let productData: ProductRequestData = .createMockProductData(price: price as Decimal)

assertSnapshot(productData)
self.snapshot(productData)
}

func testAsDictionaryConvertsNormalDurationCorrectly() throws {
let normalDuration = "P3Y"
let productData: ProductRequestData = .createMockProductData(normalDuration: normalDuration)

assertSnapshot(productData)
self.snapshot(productData)
}

func testAsDictionaryConvertsIntroDurationCorrectlyForFreeTrial() throws {
let trialDuration = "P3M"
let productData: ProductRequestData = .createMockProductData(introDuration: trialDuration,
introDurationType: .freeTrial)

assertSnapshot(productData)
self.snapshot(productData)
}

func testAsDictionaryConvertsIntroDurationCorrectlyForIntroPrice() throws {
let introDuration = "P3M"
let productData: ProductRequestData = .createMockProductData(introDuration: introDuration,
introDurationType: .payUpFront)

assertSnapshot(productData)
self.snapshot(productData)
}

func testAsDictionaryDoesntAddIntroDurationIfDurationTypeUnknown() throws {
let introDuration = "P3M"
let productData: ProductRequestData = .createMockProductData(introDuration: introDuration,
introDurationType: nil)

assertSnapshot(productData)
self.snapshot(productData)
}

func testAsDictionaryConvertsIntroPriceCorrectly() throws {
let introPrice: NSDecimalNumber = 6.99
let productData: ProductRequestData = .createMockProductData(introPrice: introPrice as Decimal)

assertSnapshot(productData)
self.snapshot(productData)
}

func testAsDictionaryConvertsSubscriptionGroupCorrectly() {
let subscriptionGroup = "cool_group"
let productData: ProductRequestData = .createMockProductData(subscriptionGroup: subscriptionGroup)

assertSnapshot(productData)
self.snapshot(productData)
}

func testAsDictionaryConvertsDiscountsCorrectly() throws {
Expand Down Expand Up @@ -124,7 +123,7 @@ class ProductRequestDataTests: TestCase {

let productData: ProductRequestData = .createMockProductData(discounts: [discount1, discount2, discount3])

assertSnapshot(productData)
self.snapshot(productData)
}

func testEncoding() throws {
Expand Down Expand Up @@ -166,7 +165,7 @@ class ProductRequestDataTests: TestCase {
subscriptionGroup: "cool_group",
discounts: [discount1, discount2, discount3])

assertSnapshot(productData)
self.snapshot(productData)
}

func testCacheKey() throws {
Expand Down Expand Up @@ -218,10 +217,10 @@ class ProductRequestDataTests: TestCase {

private extension ProductRequestDataTests {

func assertSnapshot(_ data: ProductRequestData,
testName: String = #function,
line: UInt = #line) {
SnapshotTesting.assertSnapshot(
func snapshot(_ data: ProductRequestData,
testName: String = #function,
line: UInt = #line) {
assertSnapshot(
matching: data,
as: .formattedJson,
testName: testName,
Expand Down
68 changes: 68 additions & 0 deletions Tests/UnitTests/TestHelpers/SnapshotTesting+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,81 @@
import Foundation
import Nimble
import SnapshotTesting
import XCTest

#if swift(>=5.8) && canImport(SwiftUI)
import SwiftUI
#endif

@testable import RevenueCat

// MARK: - Overloads for Xcode Cloud support

func assertSnapshot<Value, Format>(
matching value: @autoclosure () throws -> Value,
as snapshotting: Snapshotting<Value, Format>,
named name: String? = nil,
record recording: Bool = false,
timeout: TimeInterval = 5,
file: StaticString = #file,
testName: String = #function,
line: UInt = #line
) {
let failure = verifySnapshot(
matching: try value(),
as: snapshotting,
named: name,
record: recording,
timeout: timeout,
file: file,
testName: testName,
line: line
)

if let message = failure {
XCTFail(message, file: file, line: line)
}
}

func verifySnapshot<Value, Format>(
matching value: @autoclosure () throws -> Value,
as snapshotting: Snapshotting<Value, Format>,
named name: String? = nil,
record recording: Bool = false,
timeout: TimeInterval = 5,
file: StaticString = #file,
testName: String = #function,
line: UInt = #line
) -> String? {
let snapshotDirectory: String?

if ProcessInfo.isXcodeCloud {
let ciPathPrefix = "/Volumes/workspace/repository/ci_scripts/"
let components = URL(string: file.description)!.pathComponents
let projectIndex = components.firstIndex(of: "repository")!
let folders = components[(projectIndex + 1)..<components.endIndex - 1].joined(separator: "/")
let fileName = (components[components.endIndex - 1] as NSString).deletingPathExtension

snapshotDirectory = "\(ciPathPrefix)\(folders)/__Snapshots__/\(fileName)"
} else {
snapshotDirectory = nil
}

return SnapshotTesting.verifySnapshot(
matching: try value(),
as: snapshotting,
named: name,
record: recording,
snapshotDirectory: snapshotDirectory,
timeout: timeout,
file: file,
testName: testName,
line: line
)
}

// MARK: - Snapshotting extensions

extension Snapshotting where Value == Encodable, Format == String {

/// Equivalent to `.json`, but with `JSONEncoder.KeyEncodingStrategy.convertToSnakeCase`
Expand Down
1 change: 1 addition & 0 deletions ci_scripts/Tests

0 comments on commit 4d95c03

Please sign in to comment.