Slide 1

Slide 1 text

Level up your CI for iOS and macOS Philipp Hofmann

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Photo by Sean Benesh on Unsplash

Slide 4

Slide 4 text

Photo by Patrick on Unsplash

Slide 5

Slide 5 text

Tips and Tricks 1. Better Test Logs 2. Flaky Tests 3. Flaky CI 4. SwiftLint & Clang-Format 5. Xcode Thread Sanitizer 6. Xcode Analyze 7. Validate your Library 8. Borrowing Tests 9. High Risk Files

Slide 6

Slide 6 text

Level Up Your CI. Photo by Denys Sudilkovsky on Unsplash

Slide 7

Slide 7 text

Photo by Wesley Tingey on Unsplash 1. Better Test Logs

Slide 8

Slide 8 text

Command line invocation: /Applications/Xcode_12.5.1.app/Contents/Developer/usr/bin/xcodebuild -workspace Sentry.xcworkspace -scheme Sentry -configuration Test GCC_GENERATE_TEST_COVERAGE_FILES=YES GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES -destination "platform=iOS Simulator,OS=latest,name=iPhone 8" test User defaults from command line: IDEPackageSupportUseBuiltinSCM = YES Build settings from command line: GCC_GENERATE_TEST_COVERAGE_FILES = YES GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES note: Using new build system note: Building targets in parallel note: Planning build note: Analyzing workspace note: Constructing build description note: Build preparation complete CreateBuildDirectory /Users/runner/Library/Developer/Xcode/DerivedData/Sentry-azcqyxgbmmrdtdegukmbgarszteu/Build/ Intermediates.noindex cd /Users/runner/work/sentry-cocoa/sentry-cocoa builtin-create-build-directory /Users/runner/Library/Developer/Xcode/DerivedData/Sentry-azcqyxgbmmrdtdegukmbgarszteu/Build/ Intermediates.noindex CreateBuildDirectory /Users/runner/Library/Developer/Xcode/DerivedData/Sentry-azcqyxgbmmrdtdegukmbgarszteu/Build/Products cd /Users/runner/work/sentry-cocoa/sentry-cocoa builtin-create-build-directory /Users/runner/Library/Developer/Xcode/DerivedData/Sentry-azcqyxgbmmrdtdegukmbgarszteu/Build/ Products CreateBuildDirectory /Users/runner/Library/Developer/Xcode/DerivedData/Sentry-azcqyxgbmmrdtdegukmbgarszteu/Build/Products/Test- iphonesimulator cd /Users/runner/work/sentry-cocoa/sentry-cocoa builtin-create-build-directory /Users/runner/Library/Developer/Xcode/DerivedData/Sentry-azcqyxgbmmrdtdegukmbgarszteu/Build/ Products/Test-iphonesimulator

Slide 9

Slide 9 text

Test Suite 'SentryCrashFileUtils_Tests' started at 2022-10-25 23:28:08.671 Test Case '-[SentryCrashFileUtils_Tests testLastPathEntry]' started. Test Case '-[SentryCrashFileUtils_Tests testLastPathEntry]' passed (0.002 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBuffered_EmptyFile]' started. Test Case '-[SentryCrashFileUtils_Tests testReadBuffered_EmptyFile]' passed (0.005 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBuffered_FileIsBigger]' started. Test Case '-[SentryCrashFileUtils_Tests testReadBuffered_FileIsBigger]' passed (0.004 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBuffered_ReadBufferIsMuchSmaller]' started. Test Case '-[SentryCrashFileUtils_Tests testReadBuffered_ReadBufferIsMuchSmaller]' passed (0.004 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBuffered_ReadBufferIsSmaller]' started. Test Case '-[SentryCrashFileUtils_Tests testReadBuffered_ReadBufferIsSmaller]' passed (0.003 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBuffered_SameSize]' started. Test Case '-[SentryCrashFileUtils_Tests testReadBuffered_SameSize]' passed (0.006 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_Beginning]' started. Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_Beginning]' passed (0.005 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_End]' started. Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_End]' passed (0.003 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_Halfway]' started. Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_Halfway]' passed (0.004 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_NotFound]' started. Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_NotFound]' passed (0.004 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_NotFound_LargeFile]' started. Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_NotFound_LargeFile]' passed (0.003 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_SmallDstBuffer]' started. Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_SmallDstBuffer]' passed (0.003 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_SmallReadBuffer]' started. Test Case '-[SentryCrashFileUtils_Tests testReadBufferedUntilChar_SmallReadBuffer]' passed (0.026 seconds). Test Case '-[SentryCrashFileUtils_Tests testReadBytesFromFD]' started.

Slide 10

Slide 10 text

Test Case '-[SentryTests.SentryCrashIntegrationTests testEndSessionAsCrashed_NoCrashLastLaunch]' started. Test Case '-[SentryTests.SentryCrashIntegrationTests testEndSessionAsCrashed_NoCrashLastLaunch]' passed (0.011 seconds). Test Case '-[SentryTests.SentryCrashIntegrationTests testEndSessionAsCrashed_NoCurrentSession]' started. Test Case '-[SentryTests.SentryCrashIntegrationTests testEndSessionAsCrashed_NoCurrentSession]' passed (0.004 seconds). Test Case '-[SentryTests.SentryCrashIntegrationTests testEndSessionAsCrashed_WhenOOM_WithCurrentSession]' started. Test Case '-[SentryTests.SentryCrashIntegrationTests testEndSessionAsCrashed_WhenOOM_WithCurrentSession]' passed (0.023 seconds). Test Case '-[SentryTests.SentryCrashIntegrationTests testEndSessionAsCrashed_WithCurrentSession]' started. Test Case '-[SentryTests.SentryCrashIntegrationTests testEndSessionAsCrashed_WithCurrentSession]' passed (0.008 seconds). Test Case '-[SentryTests.SentryCrashIntegrationTests testInstall_WhenStitchAsyncCallsDisabled_DoesNotCallInstallAsyncHooks]' started. Test Case '-[SentryTests.SentryCrashIntegrationTests testInstall_WhenStitchAsyncCallsDisabled_DoesNotCallInstallAsyncHooks]' passed (0.003 seconds). Test Case '-[SentryTests.SentryCrashIntegrationTests testInstall_WhenStitchAsyncCallsEnabled_CallsInstallAsyncHooks]' started. Test Case '-[SentryTests.SentryCrashIntegrationTests testInstall_WhenStitchAsyncCallsEnabled_CallsInstallAsyncHooks]' passed (0.003 seconds). Test Case '-[SentryTests.SentryCrashIntegrationTests testLocaleChanged_DifferentLocale_SetsCurrentLocale]' started. Test Case '-[SentryTests.SentryCrashIntegrationTests testLocaleChanged_DifferentLocale_SetsCurrentLocale]' passed (0.007 seconds). Test Case '-[SentryTests.SentryCrashIntegrationTests testLocaleChanged_NoDeviceContext_SetsCurrentLocale]' started. Test Case '-[SentryTests.SentryCrashIntegrationTests testLocaleChanged_NoDeviceContext_SetsCurrentLocale]' passed (0.003 seconds).

Slide 11

Slide 11 text

Test Suite 'Selected tests' failed at 2022-10-27 11:29:58.069. Executed 1564 tests, with 1 test skipped and 3 failures (0 unexpected) in 46.706 (47.486) seconds 2022-10-27 11:30:07.091 xcodebuild[48576:4363253] [MT] IDETestOperationsObserverDebug: 57.629 elapsed -- Testing started completed. 2022-10-27 11:30:07.091 xcodebuild[48576:4363253] [MT] IDETestOperationsObserverDebug: 0.000 sec, +0.000 sec -- start 2022-10-27 11:30:07.091 xcodebuild[48576:4363253] [MT] IDETestOperationsObserverDebug: 57.629 sec, +57.629 sec -- end Test session results, code coverage, and logs: /Users/philipphofmann/Library/Developer/Xcode/DerivedData/Sentry- gcimrafeikdpcwaanncxmwrieqhi/Logs/Test/Test-Sentry-2022.10.27_11-29-08-+0200.xcresult Failing tests: SentryClientTest.testCaptureCrash_Culture() -[SentryDeviceTests testDeviceModel] SentryNetworkTrackerIntegrationTests.testGetRequest_CompareSentryTraceHeader() ** TEST FAILED **

Slide 12

Slide 12 text

xcbeautify

Slide 13

Slide 13 text

xcodebuild [flags]

Slide 14

Slide 14 text

xcodebuild [flags] | xcbeautify

Slide 15

Slide 15 text

SentryTests.SentryNetworkTrackerIntegrationTests testGetRequest_CompareSentryTraceHeader, XCTAssertEqual failed: ("Optional("4f6049d2753e484f81ee859aa8c88c7e-bbc6d1f19e76479e-1")") is not equal to ("Optional("")") /Users/philipphofmann/git-repos/sentry-cocoa/Tests/SentryTests/Integrations/Performance/Network/ SentryNetworkTrackerIntegrationTests.swift:201 ``` let expectedTraceHeader = networkSpan.toTraceHeader().value() XCTAssertEqual(expectedTraceHeader, response) } ``` Executed 1567 tests, with 1 test skipped and 3 failures (0 unexpected) in 63.980 (64.542) seconds 2022-10-28 09:03:34.884 xcodebuild[5492:40161] [MT] IDETestOperationsObserverDebug: 70.839 elapsed -- Testing started completed. 2022-10-28 09:03:34.884 xcodebuild[5492:40161] [MT] IDETestOperationsObserverDebug: 0.000 sec, +0.000 sec -- start 2022-10-28 09:03:34.884 xcodebuild[5492:40161] [MT] IDETestOperationsObserverDebug: 70.839 sec, +70.839 sec -- end Failing tests: SentryClientTest.testCaptureCrash_Culture() -[SentryDeviceTests testDeviceModel] SentryNetworkTrackerIntegrationTests.testGetRequest_CompareSentryTraceHeader() ** TEST FAILED **

Slide 16

Slide 16 text

Fastlane uses xcbeautify.

Slide 17

Slide 17 text

Raw test output log

Slide 18

Slide 18 text

xcodebuild [flags] | xcbeautify

Slide 19

Slide 19 text

xcodebuild [flags] | tee raw-test-output.log | xcbeautify

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

Derived Data Logs

Slide 22

Slide 22 text

/Xcode/DerivedData/YourProject/Logs/**

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

YourTests.xcresult

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

2. Flaky Tests Photo by Jaakko Kemppainen on Unsplash

Slide 28

Slide 28 text

Reproduce them locally.

Slide 29

Slide 29 text

Run tests repeatedly.

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

XCTestObservationCenter

Slide 35

Slide 35 text

XCTestObservationCenter.shared .addTestObserver(TestObserver())

Slide 36

Slide 36 text

class TestObserver: XCTestObservation { }

Slide 37

Slide 37 text

class TestObserver : XCTestObservation { func testCase(_ testCase: XCTestCase, didRecord issue: XCTIssue) { } }

Slide 38

Slide 38 text

class TestObserver : XCTestObservation { func testCase(_ testCase: XCTestCase, didRecord issue: XCTIssue) { let exception = NSException(name: testCase.name, reason: issue.description) } }

Slide 39

Slide 39 text

class TestObserver : XCTestObservation { func testCase(_ testCase: XCTestCase, didRecord issue: XCTIssue) { let exception = NSException(name: testCase.name, reason: issue.description) SentrySDK.capture(exception: exception) } }

Slide 40

Slide 40 text

class TestObserver : XCTestObservation { func testCaseWillStart(_ testCase: XCTestCase) { } }

Slide 41

Slide 41 text

class TestObserver : XCTestObservation { func testCaseWillStart(_ testCase: XCTestCase) { let crumb = Breadcrumb(level: .debug, category: "test.started") } }

Slide 42

Slide 42 text

class TestObserver : XCTestObservation { func testCaseWillStart(_ testCase: XCTestCase) { let crumb = Breadcrumb(level: .debug, category: "test.started") crumb.message = testCase.name } }

Slide 43

Slide 43 text

class TestObserver : XCTestObservation { func testCaseWillStart(_ testCase: XCTestCase) { let crumb = Breadcrumb(level: .debug, category: "test.started") crumb.message = testCase.name SentrySDK.addBreadcrumb(crumb: crumb) } }

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

Use CI for validation.

Slide 47

Slide 47 text

matrix: ios-version: ["16.1", "15.7", "14.0"] device: ["iPhone", "iPad"]

Slide 48

Slide 48 text

iPhone 16.1 iPhone 15.7 iPhone 14.0 matrix: ios-version: ["16.1", "15.7", "14.0"] device: ["iPhone", "iPad"] iPad 16.1 iPad 15.7 iPad 14.0

Slide 49

Slide 49 text

matrix: a: [1,2,3,4,5] b: [1,2,3,4,5]

Slide 50

Slide 50 text

11 12 13 matrix: a: [1,2,3,4,5] b: [1,2,3,4,5] … 54 55

Slide 51

Slide 51 text

3. Flaky CI Photo by Pawel Czerwinski on Unsplash

Slide 52

Slide 52 text

Photo by Nathan Anderson on Unsplash

Slide 53

Slide 53 text

for i in {1..5}; do flaky-command && break ; done

Slide 54

Slide 54 text

4. SwiftLint & Clang-Format Photo by Cesar Carlevarino Aragon on Unsplash

Slide 55

Slide 55 text

5. Xcode Thread Sanitizer Photo by Nick Fewings on Unsplash

Slide 56

Slide 56 text

class Counter { }

Slide 57

Slide 57 text

class Counter { func increment() { } var count: Int { } }

Slide 58

Slide 58 text

class Counter { private var internalCount = 0 func increment() { internalCount += 1 } var count: Int { return internalCount } }

Slide 59

Slide 59 text

class CounterTests: XCTestCase { func testIncrementConcurrent() { } }

Slide 60

Slide 60 text

class CounterTests: XCTestCase { func testIncrementConcurrent() { let expectedCounts = 100 let counter = Counter() } }

Slide 61

Slide 61 text

class CounterTests: XCTestCase { func testIncrementConcurrent() { let expectedCounts = 100 let counter = Counter() let queue = DispatchQueue(label: "CounterTest") } }

Slide 62

Slide 62 text

class CounterTests: XCTestCase { func testIncrementConcurrent() { let expectedCounts = 100 let counter = Counter() let queue = DispatchQueue(label: "CounterTest") for _ in 0..

Slide 63

Slide 63 text

class CounterTests: XCTestCase { func testIncrementConcurrent() { let expectedCounts = 100 let counter = Counter() let queue = DispatchQueue(label: "CounterTest") for _ in 0..

Slide 64

Slide 64 text

class CounterTests: XCTestCase { func testIncrementConcurrent() { let expectedCounts = 100 let counter = Counter() let queue = DispatchQueue(label: "CounterTest") for _ in 0..

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

#!/bin/bash set -euo pipefail xcodebuild [flags] test

Slide 69

Slide 69 text

#!/bin/bash set -euo pipefail xcodebuild [flags] -enableThreadSanitizer YES test

Slide 70

Slide 70 text

================== WARNING: ThreadSanitizer: data race (pid=61245) Write of size 8 at 0x7b080029af50 by thread T8: #0 Counter.increment() Counter.swift:8 (MobileDevSummit:x86_64+0x100007052) #1 closure #1 in CounterTests.testIncrementConcurrent() CounterTests.swift:14 (MobileDevSummitTests:x86_64+0x1e7c) #2 partial apply for closure #1 in CounterTests.testIncrementConcurrent() (MobileDevSummitTests:x86_64+0x332d) #3 thunk for @escaping @callee_guaranteed () -> () (MobileDevSummitTests:x86_64+0x1ef2) #4 __tsan::invoke_and_release_block(void*) :2 (libclang_rt.tsan_iossim_dynamic.dylib:x86_64+0x7f3eb) #5 _dispatch_client_callout :2 (libdispatch.dylib:x86_64+0x2a39) Previous write of size 8 at 0x7b080029af50 by thread T9: #0 Counter.increment() Counter.swift:8 (MobileDevSummit:x86_64+0x100007052) #1 closure #1 in CounterTests.testIncrementConcurrent() CounterTests.swift:14 (MobileDevSummitTests:x86_64+0x1e7c) #2 partial apply for closure #1 in CounterTests.testIncrementConcurrent() (MobileDevSummitTests:x86_64+0x332d) #3 thunk for @escaping @callee_guaranteed () -> () (MobileDevSummitTests:x86_64+0x1ef2) #4 __tsan::invoke_and_release_block(void*) :2 (libclang_rt.tsan_iossim_dynamic.dylib:x86_64+0x7f3eb) #5 _dispatch_client_callout :2 (libdispatch.dylib:x86_64+0x2a39) Location is heap block of size 24 at 0x7b080029af40 allocated by main thread: #0 __sanitizer_mz_malloc :2 (libclang_rt.tsan_iossim_dynamic.dylib:x86_64+0x5583c) #1 _malloc_zone_malloc_instrumented_or_legacy :2 (libsystem_malloc.dylib:x86_64+0x1742f) #2 CounterTests.testIncrementConcurrent() CounterTests.swift:8 (MobileDevSummitTests:x86_64+0x18fa) #3 @objc CounterTests.testIncrementConcurrent() (MobileDevSummitTests:x86_64+0x2151) #4 __invoking___ :2 (CoreFoundation:x86_64+0x12c31b) #5 main MobileDevSummitApp.swift (MobileDevSummit:x86_64+0x100006db5) Thread T8 (tid=2045181, running) is a GCD worker thread Thread T9 (tid=2045180, running) is a GCD worker thread SUMMARY: ThreadSanitizer: data race Counter.swift:8 in Counter.increment() ================== Test session results, code coverage, and logs: /Users/philipphofmann/Library/Developer/Xcode/DerivedData/MobileDevSummit-cqfkhtwtenpewocwjipnmnsajuwd/Logs/Test/Test- MobileDevSummit-2022.11.03_15-35-43-+0100.xcresult ** TEST SUCCEEDED **

Slide 71

Slide 71 text

WARNING: ThreadSanitizer: data race (pid=61245) SUMMARY: ThreadSanitizer: data race Counter.swift:8 in Counter.increment() ** TEST SUCCEEDED **

Slide 72

Slide 72 text

#!/bin/bash set -euo pipefail xcodebuild [flags] -enableThreadSanitizer YES test

Slide 73

Slide 73 text

#!/bin/bash set -euo pipefail xcodebuild [flags] -enableThreadSanitizer YES test | \ tee thread-sanitizer.log

Slide 74

Slide 74 text

#!/bin/bash set -euo pipefail xcodebuild [flags] -enableThreadSanitizer YES test | \ tee thread-sanitizer.log if grep -Fq "WARNING: ThreadSanitizer:" thread-sanitizer.log ; then else fi

Slide 75

Slide 75 text

#!/bin/bash set -euo pipefail xcodebuild [flags] -enableThreadSanitizer YES test | \ tee thread-sanitizer.log if grep -Fq "WARNING: ThreadSanitizer:" thread-sanitizer.log ; then message="ThreadSanitizer found problems. Search for \"ThreadSanitizer\" in logs for more details." echo "$message" exit 1 else fi

Slide 76

Slide 76 text

#!/bin/bash set -euo pipefail xcodebuild [flags] -enableThreadSanitizer YES test | \ tee thread-sanitizer.log if grep -Fq "WARNING: ThreadSanitizer:" thread-sanitizer.log ; then message="ThreadSanitizer found problems. Search for \"ThreadSanitizer\" in logs for more details." echo "$message" exit 1 else echo "ThreadSanitizer didn't find problems." exit 0 fi

Slide 77

Slide 77 text

Adds a 2x to 20x slowdown of your code.

Slide 78

Slide 78 text

6. Xcode Analyze Photo by Markus Spiske on Unsplash

Slide 79

Slide 79 text

Works for Objective-C, C, and C++.

Slide 80

Slide 80 text

@implementation YourClass - (instancetype)initWithNSString:(NSString *)string { if ([super init]) { _string = string; } return self; } @end

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

xcodebuild analyze [flags]

Slide 83

Slide 83 text

xcodebuild analyze [flags] | xcbeautify

Slide 84

Slide 84 text

▸ Analyzing YourClass.m ⚠ /Users/philipphofmann/git-repos/sample/Sources/YourClass.m:11:5: Returning 'self' while it is not set to the result of '[(super or self) init...]' [osx.cocoa.SelfInit] return self; ^~~~~~~~~~~ ▸ Analyze Succeeded

Slide 85

Slide 85 text

▸ Analyzing YourClass.m ⚠ /Users/philipphofmann/git-repos/sample/Sources/YourClass.m:11:5: Returning 'self' while it is not set to the result of '[(super or self) init...]' [osx.cocoa.SelfInit] return self; ^~~~~~~~~~~ ▸ Analyze Succeeded

Slide 86

Slide 86 text

xcodebuild analyze [flags] | xcbeautify

Slide 87

Slide 87 text

xcodebuild analyze [flags] CLANG_ANALYZER_OUTPUT=html | xcbeautify

Slide 88

Slide 88 text

xcodebuild analyze [flags] CLANG_ANALYZER_OUTPUT=html CLANG_ANALYZER_OUTPUT_DIR=analyzer | xcpretty

Slide 89

Slide 89 text

xcodebuild analyze [flags] CLANG_ANALYZER_OUTPUT=html CLANG_ANALYZER_OUTPUT_DIR=analyzer | xcpretty && [[ -z `find analyzer ` ]]

Slide 90

Slide 90 text

xcodebuild analyze [flags] CLANG_ANALYZER_OUTPUT=html CLANG_ANALYZER_OUTPUT_DIR=analyzer | xcpretty && [[ -z `find analyzer -name "*.html"` ]]

Slide 91

Slide 91 text

▸ Analyzing YourClass.m ⚠ /Users/philipphofmann/git-repos/sample/Sources/YourClass.m:11:5: Returning 'self' while it is not set to the result of '[(super or self) init...]' [osx.cocoa.SelfInit] return self; ^~~~~~~~~~~ ▸ Analyze Succeeded Error 1

Slide 92

Slide 92 text

7. Validate your Library Photo by Gabriel Sollmann on Unsplash

Slide 93

Slide 93 text

CocoaPods

Slide 94

Slide 94 text

pod lib lint

Slide 95

Slide 95 text

12 minutes

Slide 96

Slide 96 text

pod lib lint -—platforms=iOS pod lib lint -—platforms=macOS pod lib lint -—platforms=tvOS pod lib lint --platforms=watchos Run in parallel

Slide 97

Slide 97 text

3 minutes

Slide 98

Slide 98 text

Swift Package Manager

Slide 99

Slide 99 text

Add a sample app.

Slide 100

Slide 100 text

let package = Package( name: "macOS-SPM-CommandLine", dependencies: [ .package( name: "Sentry", url: "https://github.com/getsentry/sentry-cocoa", .branch(“main") ) ], targets: [ .target( name: "macOS-SPM-CommandLine", dependencies: ["Sentry"], swiftSettings: [ .unsafeFlags(["-warnings-as-errors"]) ]) ] )

Slide 101

Slide 101 text

dependencies: [ .package( name: "Sentry", url: "https://github.com/getsentry/sentry-cocoa", .branch("main") ) ]

Slide 102

Slide 102 text

dependencies: [ .package( name: "Sentry", url: "https://github.com/getsentry/sentry-cocoa", .revision("4e037c") ) ]

Slide 103

Slide 103 text

sed

Slide 104

Slide 104 text

sed -i '' \ ’s/.branch("main")/

Slide 105

Slide 105 text

sed -i '' \ ’s/.branch("main")/.revision("${{ github.sha }}")/g'

Slide 106

Slide 106 text

sed -i '' \ ’s/.branch("main")/.revision("${{ github.sha }}")/g' \ Samples/macOS-SPM-CommandLine/Package.swift

Slide 107

Slide 107 text

dependencies: [ .package( name: "Sentry", url: "https://github.com/getsentry/sentry-cocoa", .branch("main") ) ]

Slide 108

Slide 108 text

dependencies: [ .package( name: "Sentry", url: "https://github.com/getsentry/sentry-cocoa", .revision("4e037c") ) ]

Slide 109

Slide 109 text

swift build

Slide 110

Slide 110 text

Carthage

Slide 111

Slide 111 text

github "getsentry/sentry-cocoa" "main" Cartfile

Slide 112

Slide 112 text

github "getsentry/sentry-cocoa" "4e037c" Cartfile

Slide 113

Slide 113 text

Prebuilt XCFramework

Slide 114

Slide 114 text

8. Borrowing Tests Photo by Louis Reed on Unsplash

Slide 115

Slide 115 text

Tests from VLC, Alamofire, HomeKit.

Slide 116

Slide 116 text

git clone https://github.com/other/repo.git

Slide 117

Slide 117 text

Add your library to tests.

Slide 118

Slide 118 text

Create a git patch.

Slide 119

Slide 119 text

+ repositoryURL = "https://github.com/org/repo"; + requirement = { + kind = revision; + revision = __GITHUB_REVISION_PLACEHOLDER__; + }; Patchfile

Slide 120

Slide 120 text

git clone https://github.com/other/repo.git CI

Slide 121

Slide 121 text

git clone https://github.com/other/repo.git git checkout 4e23cas CI

Slide 122

Slide 122 text

git clone https://github.com/other/repo.git git checkout 4e23cas curl “https://raw.githubusercontent.com/org/repo/${SHA}/your.patch" --output your.patch CI

Slide 123

Slide 123 text

git clone https://github.com/other/repo.git git checkout 4e23cas curl “https://raw.githubusercontent.com/org/repo/${SHA}/your.patch" --output your.patch # Replace revision with SHA REPLACE="s/__GITHUB_REVISION_PLACEHOLDER__/${SHA}/g" sed -i '' $REPLACE your.patch CI

Slide 124

Slide 124 text

git clone https://github.com/other/repo.git git checkout 4e23cas curl “https://raw.githubusercontent.com/org/repo/${SHA}/your.patch" --output your.patch # Replace revision with SHA REPLACE="s/__GITHUB_REVISION_PLACEHOLDER__/${SHA}/g" sed -i '' $REPLACE your.patch git apply your.patch CI

Slide 125

Slide 125 text

git clone https://github.com/other/repo.git git checkout 4e23cas curl “https://raw.githubusercontent.com/org/repo/${SHA}/your.patch" --output your.patch # Replace revision with SHA REPLACE="s/__GITHUB_REVISION_PLACEHOLDER__/${SHA}/g" sed -i '' $REPLACE your.patch git apply your.patch xcodebuild [flags] test CI

Slide 126

Slide 126 text

9. High-Risk Files

Slide 127

Slide 127 text

// WARNING START // This code is bulletproof. // Don't think you are smart and can // improve it. … // WARNING END

Slide 128

Slide 128 text

./no-changes-in-high-risk-files.sh

Slide 129

Slide 129 text

#!/bin/bash set -euo pipefail ACTUAL=$(shasum -a 256 ./Sources/BulletProof.swift)

Slide 130

Slide 130 text

#!/bin/bash set -euo pipefail ACTUAL=$(shasum -a 256 ./Sources/BulletProof.swift) EXPECTED="54a41f19... ./Sources/BulletProof.swift"

Slide 131

Slide 131 text

#!/bin/bash set -euo pipefail ACTUAL=$(shasum -a 256 ./Sources/BulletProof.swift) EXPECTED="54a41f19... ./Sources/BulletProof.swift" if [ "$ACTUAL" = "$EXPECTED" ]; then else fi

Slide 132

Slide 132 text

#!/bin/bash set -euo pipefail ACTUAL=$(shasum -a 256 ./Sources/BulletProof.swift) EXPECTED="54a41f19... ./Sources/BulletProof.swift" if [ "$ACTUAL" = "$EXPECTED" ]; then echo "No changes in high risk files." exit 0 else fi

Slide 133

Slide 133 text

#!/bin/bash set -euo pipefail ACTUAL=$(shasum -a 256 ./Sources/BulletProof.swift) EXPECTED="54a41f19... ./Sources/BulletProof.swift" if [ "$ACTUAL" = "$EXPECTED" ]; then echo "No changes in high risk files." exit 0 else text="Changes in high risk files. If your changes are intended please update the sha in ./no-changes-in-high-risk-files.sh." echo "$text" exit 1 fi

Slide 134

Slide 134 text

Tips and Tricks 1. Better Test Logs 2. Flaky Tests 3. Flaky CI 4. SwiftLint & Clang-Format 5. Xcode Thread Sanitizer 6. Xcode Analyze 7. Validate your Library 8. Borrowing Tests 9. High-Risk Files

Slide 135

Slide 135 text

Checkout sentry-cocoa for seeing the CI tips in action. Level up your CI for iOS and macOS Philipp Hofmann 1. Better Test Logs 2. Flaky Tests 3. Flaky CI 4. SwiftLint & Clang-Format 5. Xcode Thread Sanitizer 6. Xcode Analyze 7. Validate your Library 8. Borrowing Tests 9. High-Risk Files