Swift Xcode Asynchronous Testing

A while back I posted my tweak to some Xcode testing for asynchronous calls. Here’s an update I did in Swift for XCTestCase.

extension XCTestCase {
  /**
   For use testing async functions.
   Will wait for completion of the function.
   You need to provide a test function for when the call is completed.
   The ultimate timeout should be provided for cases of there not
   being a response.
   
   let _ = self.waitUntilReady(completionTest: { () -> Bool in
   return remoteInterface?.isBusy == false
   })
   
   or as a trailing closure:
   
   let _ = self.waitUntilReady() {
   return remoteInterface?.isBusy == false
   }
   
   optionally using your own timeout:
   
   let _ = self.waitUntilReady(timeout: 20) {
   return remoteInterface?.isBusy == false
   }
   
   */
  func waitUntilReady(timeout seconds:TimeInterval = 30, completionTest: () -> Bool) -> TimeInterval {
    let start = Date.timeIntervalSinceReferenceDate
    let loopUntil = Date(timeIntervalSinceNow: seconds)
    
    while completionTest() == false && loopUntil.timeIntervalSinceNow > 0 {
      RunLoop.current.run(mode: .defaultRunLoopMode, before: loopUntil)
    }
    
    let finish = NSDate.timeIntervalSinceReferenceDate
    let runtime = finish - start
    return runtime
  }
}

Mac Black Screen of Death

After a power outage, my iMac (27″, Late 2012) with macOS Sierra (10.12.2) booted into a black screen with the mouse in the upper left. Notifications seemed to be showing up (as they do on the login screen).

Several leads didn’t pan out. Ones that were basically different kind of resets.

Mark Gibbs tipped me off that it might be a problem with a corrupted /Library/Preferences/com.apple.loginwindow.plist file.

Single User Mode

Booting into single user mode (Command-S during boot) drops you into a terminal, which allowed me to look at the file. Not knowing what it’s supposed to look like, it was hard to tell if the (likely digitally stored) file should look like.

bplist00€	


_RetriesUntilHint\GuestEnabled_OptimizerLastRunForSystem\lastUserName]autoLoginUser_OptimizerLastRunForBuild_HiddenUsersList[AccountInfoXlastUser_autoLoginUserScreenLocked^lastLoginPanic	
XbshirleyXbshirley`°Y_conveyor”\MaximumUsersYAllLoginsYOnConsole—Xbshirley—XloggedIn	#AæFt?È`2?[hvë£Ø?‘„ÂÊÎÙ?",68;DGPQZ

The beginning of that file indicates it’s a property list stored in binary form, so it’s hard to interpret in textual form.

The filesystem is read-only when you boot into single user mode, so by default you can’t delete (or preferably move) the file.

Remount Filesystem

The answer to that I found on Stack Overflow. You can use the UNIX mount command to remount the existing filesystem in a writable state.

sudo /sbin/mount -uw /

Rename File

Once that was the case, I rename the file (incase I needed it for something) and then

cd /Library/Preferences
mv com.apple.loginwindow.plist com.apple.loginwindow.plist.XXX

Once that was done, exiting single user mode and rebooting is as simple as

reboot

A few items were reset from my previous login, but otherwise all was well.

Cocos Builder generating links to subcomponents

I’ve been playing with CocosBuilder 2.0 and it’s definitely an improvement over hand coding layout for Cocos2D menus.

It does have a bit of a learning curve, especially with the lack of documentation (which will likely linger for a while).

Much of the tool is non-intuitive in the world of wysiwyg, but if you are writing or are maintaining hundreds of lines of code for this regard it’s worth it.  Just like Interface Builder is to Objective-C presentation layers, knowing the object design behind it will help you understand it.

That said, here’s a few gotchas I’ve run into.

If you are trying to connect to a property that you’ve created in the header file and are allowing the new Objective-C to generate the ivar for, beware that the ivar name is preceded with an underscore.

@property (nonatomic, assign) CCLabelTTF *myLabel;

leads to an instance variable named _myLabel unless you provide your own (no longer needed) @synthesis line.

I also noticed after following the code that they’ve provided a nice method that gets invoked after the CCB file is loaded.

- (void)didLoadFromCCB;

I would have preferred they follow Apple’s NeXT’s naming convention. They defined an awakeFromNib method. This one should have been awakeFromCCB.

I definitely would have liked a menu to open files (not just projects).  The only way i’ve found is open the project, find the file in the listing, double click it.  If it’s not listed, finagle the setting to see it.

Asynchronous Testing with SenTestingKit

Xcode includes a testing library which is useful for unit testing.  Simple unit tests are easy to manage, but it tends to be (A) perform operation, and (B) test the state of your objects.

I was recently doing some asynchronous programming and wanted to build some tests against my code.  A little searching led me to a good idea by akisute on Github.  But I thought it could use some simplification.

It boils down to a single method that takes a block to determine if the expected action is completed. Simply implement it to return YES when it is.

- (NSTimeInterval)waitTillReady:(BOOL (^)())isReady timeout:(NSTimeInterval)seconds {
  NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
  NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:seconds];

  while (isReady() == NO && [loopUntil timeIntervalSinceNow] > 0) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:loopUntil];
  }

  NSTimeInterval finish = [NSDate timeIntervalSinceReferenceDate];
  return (finish - start);
}

In line 6 above, NSRunLoop performs the magic. In any iOS or OS X there is a run loop at the top of the call stack that gets things running in the event-driven applications. Instead of allowing it to drop out of our call stack back to the top level, we are creating a secondary run loop that allows anything the user causes or external events cause to be processed.

Once our event occurs or the timeout is reached, we return and let the normal execution of the main run loop eventually take back over.

And here’s an example use of it. Creating a message object which is simply loading a URL from the web. Here’s the code to test that it starts loading within 5 seconds, and that it completes within 20 seconds.

- (void)testMessage {
  PPPostMessage *message = [[PPPostMessage alloc] init];
  STAssertTrue(message.isRequesting, @"should automatically start requesting (currently)");

  NSTimeInterval delay = [self waitTillReady:^BOOL{
    return message.isLoading;
  } timeout:5];
  STAssertTrue(message.isLoading, @"expecting it to start loading in 5 second");
  NSLog(@"[%@ %@] load statup delay = %f", NSStringFromClass([self class]), NSStringFromSelector(_cmd), delay );

  delay = [self waitTillReady:^BOOL{
      return message.isLoaded;
    } timeout:20 - delay];
  
  STAssertTrue(message.isLoaded, @"expecting it to be loaded in 20 seconds");
  NSLog(@"[%@ %@] load time = %f", NSStringFromClass([self class]), NSStringFromSelector(_cmd), delay );
}

You’ll notice that the waitTillReady: method returns the time that it actually took and we are using that value to adjust the time we allow on the second test.