Skip to content

Commit

Permalink
fix mac os sandbox check slowness (#3879)
Browse files Browse the repository at this point in the history
Alternative approach to #3875, also meant to address
#3871

We're currently looking at a field called Environment in the local
receipt to check whether the app is running in sandbox on macOS.

However, since it's an undocumented field, we can't trust that it will
always be there, so if the value is production then we check the
bundle's signature, which can be slow to run.

This PR changes that behavior so that we only perform that check if the
value for that variable isn't available, which would cause a receipt
parsing failure since it's marked as non-optional.

Still need to write tests for this, but it should be significantly
easier than for the other PR.
  • Loading branch information
aboedo authored and nyeu committed Oct 1, 2024
1 parent b16c157 commit 1768d31
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 34 deletions.
17 changes: 13 additions & 4 deletions Sources/Misc/SandboxEnvironmentDetector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,14 @@ final class BundleSandboxEnvironmentDetector: SandboxEnvironmentDetector {
}

#if os(macOS) || targetEnvironment(macCatalyst)
return !self.isProductionReceipt || !self.isMacAppStore
// this relies on an undocumented field in the receipt that provides the Environment.
// if it's not present, we go to a secondary check.
if let isProductionReceipt = self.isProductionReceipt {
return !isProductionReceipt
} else {
return !self.isMacAppStore
}

#else
return path.contains("sandboxReceipt")
#endif
Expand All @@ -73,12 +80,14 @@ extension BundleSandboxEnvironmentDetector: Sendable {}

private extension BundleSandboxEnvironmentDetector {

var isProductionReceipt: Bool {
var isProductionReceipt: Bool? {
do {
return try self.receiptFetcher.fetchAndParseLocalReceipt().environment == .production
let receiptEnvironment = try self.receiptFetcher.fetchAndParseLocalReceipt().environment
guard receiptEnvironment != .unknown else { return nil } // don't make assumptions if we're not sure
return receiptEnvironment == .production
} catch {
Logger.error(Strings.receipt.parse_receipt_locally_error(error: error))
return false
return nil
}
}

Expand Down
110 changes: 80 additions & 30 deletions Tests/UnitTests/Misc/SandboxEnvironmentDetectorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class SandboxEnvironmentDetectorTests: TestCase {
// `macOS` sandbox detection does not rely on receipt path
class SandboxEnvironmentDetectorTests: TestCase {

func testIsNotSandboxIfReceiptIsProductionAndMAS() throws {
func testIsNotSandboxIfReceiptIsProduction() throws {
expect(
SystemInfo.with(
macAppStore: true,
Expand All @@ -54,41 +54,85 @@ class SandboxEnvironmentDetectorTests: TestCase {
) == false
}

func testIsSandboxIfReceiptIsProductionAndNotMAS() throws {
func testIsSandboxIfReceiptIsNotProduction() throws {
expect(
SystemInfo.with(
macAppStore: false,
receiptEnvironment: .production
receiptEnvironment: .sandbox
).isSandbox
) == true
}

func testIsSandboxIfReceiptIsNotProductionAndNotMAS() throws {
expect(
SystemInfo.with(
macAppStore: false,
receiptEnvironment: .sandbox
).isSandbox
) == true
func testIsSandboxWhenReceiptEnvironmentIsUnknownDefaultToMacAppStoreDetector() throws {
var isSandbox = false
var macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: !isSandbox)
var detector = SystemInfo.with(
macAppStore: !isSandbox,
receiptEnvironment: .unknown,
macAppStoreDetector: macAppStoreDetector
)

expect(detector.isSandbox) == isSandbox
expect(macAppStoreDetector.isMacAppStoreCalled) == true

isSandbox = !isSandbox

macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: !isSandbox)
detector = SystemInfo.with(
macAppStore: !isSandbox,
receiptEnvironment: .unknown,
macAppStoreDetector: macAppStoreDetector
)

expect(detector.isSandbox) == isSandbox
}

func testIsSandboxIfReceiptIsNotProductionAndMAS() throws {
expect(
SystemInfo.with(
macAppStore: true,
receiptEnvironment: .sandbox
).isSandbox
) == true
func testIsSandboxWhenReceiptParsingFailsDefaultsToMacAppStoreDetector() throws {
var isSandbox = false
var macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: !isSandbox)
var detector = SystemInfo.with(
macAppStore: !isSandbox,
failReceiptParsing: true,
macAppStoreDetector: macAppStoreDetector
)

expect(detector.isSandbox) == isSandbox
expect(macAppStoreDetector.isMacAppStoreCalled) == true

isSandbox = !isSandbox

macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: !isSandbox)
detector = SystemInfo.with(
macAppStore: !isSandbox,
failReceiptParsing: true,
macAppStoreDetector: macAppStoreDetector
)

expect(detector.isSandbox) == isSandbox
}

func testIsSandboxIfReceiptParsingFailsAndBundleSignatureIsNotMAS() throws {
expect(
SystemInfo.with(
macAppStore: false,
receiptEnvironment: .production,
failReceiptParsing: true
).isSandbox
) == true
func testIsSandboxWhenReceiptIsProductionReturnsProductionAndDoesntHitMacAppStoreDetector() throws {
let macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: false)
let detector = SystemInfo.with(
macAppStore: false,
receiptEnvironment: .production,
macAppStoreDetector: macAppStoreDetector
)

expect(detector.isSandbox) == false
expect(macAppStoreDetector.isMacAppStoreCalled) == false
}

func testIsSandboxWhenReceiptIsSandboxReturnsSandboxAndDoesntHitMacAppStoreDetector() throws {
let macAppStoreDetector = MockMacAppStoreDetector(isMacAppStore: false)
let detector = SystemInfo.with(
macAppStore: false,
receiptEnvironment: .sandbox,
macAppStoreDetector: macAppStoreDetector
)

expect(detector.isSandbox) == true
expect(macAppStoreDetector.isMacAppStoreCalled) == false
}

}
Expand All @@ -104,7 +148,8 @@ private extension SandboxEnvironmentDetector {
inSimulator: Bool = false,
macAppStore: Bool = false,
receiptEnvironment: AppleReceipt.Environment = .production,
failReceiptParsing: Bool = false
failReceiptParsing: Bool = false,
macAppStoreDetector: MockMacAppStoreDetector? = nil
) -> SandboxEnvironmentDetector {
let bundle = MockBundle()
bundle.receiptURLResult = result
Expand All @@ -126,7 +171,7 @@ private extension SandboxEnvironmentDetector {
isRunningInSimulator: inSimulator,
receiptFetcher: MockLocalReceiptFetcher(mockReceipt: mockReceipt,
failReceiptParsing: failReceiptParsing),
macAppStoreDetector: MockMacAppStoreDetector(isMacAppStore: macAppStore)
macAppStoreDetector: macAppStoreDetector ?? MockMacAppStoreDetector(isMacAppStore: macAppStore)
)
}

Expand All @@ -151,12 +196,17 @@ private final class MockLocalReceiptFetcher: LocalReceiptFetcherType {

}

private struct MockMacAppStoreDetector: MacAppStoreDetector {
private final class MockMacAppStoreDetector: MacAppStoreDetector, @unchecked Sendable {

let isMacAppStore: Bool
let isMacAppStoreValue: Bool
private(set) var isMacAppStoreCalled = false

init(isMacAppStore: Bool) {
self.isMacAppStore = isMacAppStore
self.isMacAppStoreValue = isMacAppStore
}

var isMacAppStore: Bool {
isMacAppStoreCalled = true
return isMacAppStoreValue
}
}

0 comments on commit 1768d31

Please sign in to comment.