/dev/trouble
Eric Roller's Development Blog

When creating screenshots with fastlane, there is support for checking at runtime within the app whether fastlane is being used by looking at a user default:

if UserDefaults.standard.bool(forKey: "FASTLANE_SNAPSHOT") {
    // runtime check that we are in snapshot mode
}

Unfortunately, that does not work from within the test code since the tests are run in a separate helper app!

In the test code, I would like to use something like the following such that I can test and tweak the code within Xcode before running fastlane on it:

#if FASTLANE
    snapshot("1_init", timeWaitingForIdle: 0)
#else
    let attach = XCTAttachment(screenshot: XCUIScreen.main.screenshot())
    attach.lifetime = .keepAlways
    add(attach)
#endif

One would think the FASTLANE_SNAPSHOT=YES build setting would be suitable for this task, but I have not found any way to detect it from within Swift, possibly because values like YES are not supported by the compiler.

This, however, seems to do the trick: Add this line to your Snapfile:

# Add a define for xcodebuild
xcargs "SWIFT_ACTIVE_COMPILATION_CONDITIONS=FASTLANE"

While creating UI testing scripts to create screenshots, it became necessary to detect which iOS device is currently loaded in the Simulator, specifically whether its is an iPhone or an iPad. This can be done by inspecting environment variables like SIMULATOR_DEVICE_NAME:

// Assume iPad as default
var deviceIsPhone = false

override func setUp() {
    super.setup()

    // The variable is only present in the simulator
    if let simDeviceName = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"] {
        print("Testing Device: \(simDeviceName)")
        deviceIsPhone = simDeviceName.hasPrefix("iPhone")
    }
}

Other helpful variables are:

"SIMULATOR_MODEL_IDENTIFIER": "iPad5,4"
"SIMULATOR_DEVICE_NAME": "iPad Air 2"
"SIMULATOR_MAINSCREEN_HEIGHT": "2048"
"SIMULATOR_MAINSCREEN_WIDTH": "1536"
"SIMULATOR_MAINSCREEN_SCALE": "2.000000"

Also, to list all variables in your simulator log, try this:

print(ProcessInfo().environment.debugDescription.split(separator: ",").joined(separator: "\n"))

Re-running my unit tests on Xcode 9.4, I got stuck on an uncaught exception without any useful debug messages. All I got in the log was:

[...]
Test Case '-[AppTests.AppScriptTests testScriptFiles]' started.
libc++abi.dylib: terminating with uncaught exception of type NSException

and nothing more!

Certainly, I tried re-writing my Swift do-catch-blocks to be able to catch an NSException (Objective-C), but whichever catch line I added, it was never called.

I also tried stepping though the code in the debugger but since the test commands are read from a separate script, and code is spawned on separate threads, this is rather difficult. After trying for a couple of hours yesterday, I gave up.

Today, I remembered a different trick:

Adding an exception breakpoint.

In Xcode,

  1. open the Breakpoint Navigator (Cmd-8);
  2. scroll to the bottom and click "+";
  3. select "Exception Breakpoint…";
  4. since it was an NSException, I chose Exception: "Objective-C", Break: "On Throw", Action: (none);

With that exception breakpoint active, I could re-run my unit tests. There are quite a few legitimate exception throws in my project. For these, it was just a question of selecting "continue" in debugger until I finally landed on a line that was not meant to crash (the last command shown here):

if let allSelectedIndexPaths = self.tableView.indexPathsForSelectedRows {
    for selectedPath in allSelectedIndexPaths {
        self.tableView.deselectRow(at: selectedPath, animated: true)
    }

    self.tableView.reloadRows(at: [ allSelectedIndexPaths ], with: .automatic)
}

So what is wrong with this code?

Answer: A different thread had removed rows from the table view, resulting in me trying to refresh non-existing rows.

MacOS: Internet Recovery

- Posted in macOS by

Starting off with a replaced internal SSD on a MacBook Air, it doesn’t boot, obviously. It is necessary to boot into “Internet Recovery Mode” by selecting Command-R at startup, or, as I did, Option-Command-R to recover onto High Sierra.

I erase the disk in Disk Utility and select to install macOS High Sierra. After a few seconds I am greeted with the error:

The Recovery Server Could Not Be Contacted

A number of possible solutions are listed on the Mac OS X Blog. I used the first one, except that using sudo was not possible (command not found ?); I ran date in the Terminal which confirmed that the date was wrong, then I updated it using Apple’s time server (without sudo):

% ntpdate -u time.euro.apple.com

The run date once more to check that the date & time has been updated.

Back in the High Sierra installer, I get asked to select a destination Disk, but my internal SSD cannot be selected, because:

This disk doesn’t use the GUID Partition Table Scheme

The solution is to use Disk Utility to erase the drive, but in the High-Sierra version of Disk Utility, I had no such option to select the partitioning scheme!!! The Partition button is disabled.

I eventually solved it by rebooting into the old recovery mode (Command-R) and using Lion’s Disk Utility to reformat the drive, before returning to the new recovery mode (Option-Command-R). Checking again in Disk Utility looks fine.

Setup

The following steps are:

  • Create a default (admin) user as part of the installation, but don’t use the usual name to avoid conflicts later. Try "Migrator".
  • Do NOT create a standard user.
  • Connect the Time Machine Backup drive and enter its password.
  • Launch Migration Assistant and restore from the Time Machine backup.
  • Restore all files, including those of our standard user (write down the temporary password). Promote the admin user to be admin.
  • Wait for an hour...
  • Re-install applications (which have not been backed-up).
  • Login, change password, re-authenticate iCloud.
  • Correct file ownership of the Things database, from the admin account:

    % sudo chown gollum "/Users/gollum/Library/Containers/com.culturedcode.things/Data/Library/Application Support/Cultured Code/Things/ThingsLibrary.db"
    
  • Delete the Migrator user.