How do I trap OCUnit test pass/failure messages/events - objective-c

I'm trying to use xcodebuild and OCUnit with my Continuous Integration server (TeamCity).
JetBrains offers test observer implementations for boost::test and CppUnit that format test output in a way that TeamCity can interpret. I need to do something similar for OCUnit if I want to use it.
There appears to be a SenTestObserver class in OCUnit but I'm ignorant of how exactly it should be used, and the OCUnit homepage doesn't seem to provide any documentation on the matter.

You can write your own observer by extending the SenTestObserver class and implementing the notification listeners
(void) testSuiteDidStart:(NSNotification *) aNotification
(void) testSuiteDidStop:(NSNotification *) aNotification
(void) testCaseDidStart:(NSNotification *) aNotification
(void) testCaseDidStop:(NSNotification *) aNotification
(void) testCaseDidFail:(NSNotification *) aNotification
then add an entry to the info.plist SenTestObserverClass with the name of your class.
At least in the version of OCUnit i'm familiar with SenTestObserver is equal parts useful/equal parts broken. I just skip it altogether and register for the notifications myself in my own class. (see SenTestSuiteRun.h and SenTestCaseRun.h for the defines of the notification names).
You can use the test and run properties of the notification to access the SenTestSuite and SenTestSuiteRun instances, and the run instance contains the info needed on the actual results.

I have implemented a simple Teamcity Adapter, you can view the gist here. SenTestObserver isn't exactly broken, it simply doesn't adhere to the best practices:
This is what you need to call in your Observer subclass to have it properly registered:
+(void)initialize
{
[[NSUserDefaults standardUserDefaults] setValue:#"TeamCityAdapter" forKey:#"SenTestObserverClass"];
// we need to force SenTestObserver to register us as a handler
// SenTestObserver is properly guarding against this invocation so nothing bad will hapen
// but this is required (bad design on SenTestObserver's side)...
[super initialize];
}
because SenTestObserver's initialize looks like this:
+ (void) initialize
{
if ([self class] == [SenTestObserver class]) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *registeredDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
#"SenTestLog" , #"SenTestObserverClass",
nil];
[defaults registerDefaults:registeredDefaults];
[NSClassFromString ([defaults objectForKey:#"SenTestObserverClass"]) class]; // make sure default observer is loaded
}
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"SenTestObserverClass"] isEqualToString:NSStringFromClass(self)]) {
[self setCurrentObserver:self];
}
}
I hope this will help others out there looking for a teamcity adapter for OCUnit / SenTestingKit.

Related

NSFilePresenter methods never get called

I'm trying to write a simple (toy) program that uses the NSFilePresenter and NSFileCoordinator methods to watch a file for changes.
The program consists of a text view that loads a (hardcoded) text file and a button that will save the file with any changes. The idea is that I have two instances running and saving in one instance will cause the other instance to reload the changed file.
Loading and saving the file works fine but the NSFilePresenter methods are never called. It is all based around a class called FileManager which implements the NSFilePresenter protocol. The code is as follows:
Interface:
#interface FileManager : NSObject <NSFilePresenter>
#property (unsafe_unretained) IBOutlet NSTextView *textView;
- (void) saveFile;
- (void) reloadFile;
#end
Implementation:
#implementation FileManager
{
NSOperationQueue* queue;
NSURL* fileURL;
}
- (id) init {
self = [super init];
if (self) {
self->queue = [NSOperationQueue new];
self->fileURL = [NSURL URLWithString:#"/Users/Jonathan/file.txt"];
[NSFileCoordinator addFilePresenter:self];
}
return self;
}
- (NSURL*) presentedItemURL {
NSLog(#"presentedItemURL");
return self->fileURL;
}
- (NSOperationQueue*) presentedItemOperationQueue {
NSLog(#"presentedItemOperationQueue");
return self->queue;
}
- (void) saveFile {
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
[coordinator coordinateWritingItemAtURL:self->fileURL options:NSFileCoordinatorWritingForMerging error:&error byAccessor:^(NSURL* url) {
NSString* content = [self.textView string];
[content writeToFile:[url path] atomically:YES encoding:NSUTF8StringEncoding error:NULL];
}];
}
- (void) reloadFile {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
__block NSData* content;
[coordinator coordinateReadingItemAtURL:self->fileURL options:0 error:&error byAccessor:^(NSURL* url) {
if ([fileManager fileExistsAtPath:[url path]]) {
content = [fileManager contentsAtPath:[url path]];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setString:[[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding]];
});
}
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
#end
Note, reloadFile is called in applicationDidFinishLaunching and saveFile gets called every time the save button is click (via the app delegate).
The only NSFilePresenter method that ever gets called (going by the logs) is presentedItemURL (which gets called four times when the program starts and loads the file and three times whenever save is clicked. Clicking save in a second instance has no noticeable effect on the first instance.
Can anyone tell me what I'm doing wrong here?
I was struggling with this exact issue for quite a while. For me, the only method that would be called was -presentedSubitemDidChangeAtURL: (I was monitoring a directory rather than a file). I opened a technical support issue with Apple, and their response was that this is a bug, and the only thing we can do right now is to do everything through -presentedSubitemDidChangeAtURL: if you're monitoring a directory. Not sure what can be done when monitoring a file.
I would encourage anyone encountering this issue to file a bug (https://bugreport.apple.com) to encourage Apple to get this problem fixed as soon as possible.
(I realize that this is an old question, but... :) )
First of all, I notice you don't have [NSFileCoordinator removeFilePresenter:self]; anywhere (it should be in dealloc).
Secondly, you wrote:
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
You're right: it's the incorrect implementation! And you're wrong: it's not good enough, because it's essential for methods like accommodatePresentedItemDeletionWithCompletionHandler: which take a completion block as a parameter, that you actually call this completion block whenever you implement them, e.g.
- (void) savePresentedItemChangesWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler
{
// implement your save routine here, but only if you need to!
if ( dataHasChanged ) [self save]; // <-- meta code
//
NSError * err = nil; // <-- = no error, in this simple implementation
completionHandler(err); // <-- essential!
}
I don't know whether this is the reason your protocol methods are not being called, but it's certainly a place to start. Well, assuming you haven't already worked out what was wrong in the past three years! :-)

Class method and instance method with the same name in Objective-C

I have a solution for a notification problem which works well, but I'm afraid might be a bad idea.
I have a notification that needs to be handled by each instance of a class and by the class itself. To handle this, I'm registering for a notification by both the class and instances of the class. Because it's the exact same notification, I've named the class and instance method the same. This follows the standard we've set for how notification handlers are named.
Is this a bad idea? Is there some hidden got'ca that I'm missing. Will I be confusing the heck out of future developers?
+ (void)initialize
{
if (self == [SICOHTTPClient class]) {
[[self notificationCenter] addObserver:self
selector:#selector(authorizationDidChangeNotification:)
name:SICOJSONRequestOperationAuthorizationDidChangeNotification
object:nil];
}
}
- (id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (self) {
self.parameterEncoding = AFJSONParameterEncoding;
[self registerHTTPOperationClass:[SICOJSONRequestOperation class]];
[self setDefaultHeader:#"Accept" value:#"application/json"];
if ([[self class] defaultAuthorization])
[self setDefaultHeader:#"Authorization" value:[[self class] defaultAuthorization]];
[[[self class] notificationCenter] addObserver:self
selector:#selector(authorizationDidChangeNotification:)
name:SICOJSONRequestOperationAuthorizationDidChangeNotification
object:nil];
}
return self;
}
- (void)dealloc
{
[[[self class] notificationCenter] removeObserver:self
name:SICOJSONRequestOperationAuthorizationDidChangeNotification
object:nil];
}
#pragma mark Notifications
- (void)authorizationDidChangeNotification:(NSNotification *)notification
{
NSString *authorization = notification.userInfo[SICOJSONRequestOperationAuthorizationKey];
if ([authorization isKindOfClass:[NSString class]]) {
[self setDefaultHeader:#"Authorization" value:authorization];
} else {
[self clearAuthorizationHeader];
}
}
+ (void)authorizationDidChangeNotification:(NSNotification *)notification
{
NSString *authorization = notification.userInfo[SICOJSONRequestOperationAuthorizationKey];
if ([authorization isKindOfClass:[NSString class]]) {
[self setDefaultAuthorization:authorization];
} else {
[self setDefaultAuthorization:nil];
}
}
This is what code comments are for :)
There's no problem in Objective C with a class method and instance method having the same name.
I would suggest either:
amend your notification method name spec to handle this (and then handle the class notification with a different appropriately named method), or
add appropriate comment to explain what's happening for benefit of future potentially confused developers
The language itself and the runtime will see no ambiguity in what you're doing. So you're safe on that front.
In terms of confusing future maintainers I guess you needn't be too concerned with silly autocomplete mistakes because it's not a method you intend to make manual calls to.
That said, I'd be tempted to move the class stuff into an artificial category. That'll not only give separation on the page but make it explicit that the class intends to respond as a separate tranche of functionality from the instance responses.

Check for first launch of my application

How would I check if it is the first launch of of my application using NSUserDefaults and running some code for the first time my app opens?
This should point you in the right direction:
static NSString* const hasRunAppOnceKey = #"hasRunAppOnceKey";
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:hasRunAppOnceKey] == NO)
{
// Some code you want to run on first use...
[defaults setBool:YES forKey:hasRunAppOnceKey];
}
The NSUserDefaults answer is the first thing that popped in my head, but upon reflection I will make another suggestion. A bit more work, but it's worth considering. The motive is: sometimes when troubleshooting an app, Apple recommends deleting that app's plist file. It's a fairly ubiquitous troubleshooting technique. I would recommend storing your boolean in your plist file instead of NSUserDefaults.
Disclaimer: I only do iOS development, so I'm not sure how NSUserDefaults and plists interact on the Mac, and I don't know what all is involved in getting your plist to live in ~/Library/Application\ Support/Preferences/com.mycompany.MyAppName.plist
Anyway, I imagine what this requires is having some code which can actually author a "fresh" plist (probably a copy from a template file in your bundle), and you app does this if it launches and does not see a plist. The default plist should not include the flag which lets your users skip the 'first time' code, but if they have opened the app before, and then delete the plist, they should get default behavior back.
This is an important behavior to support where possible, to aide our users if our app ever gives them trouble.
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"hasBeenLaunched"]) {
// Run code on the first launch only ...
[defaults setBool:YES forKey:#"hasBeenLaunched"];
}
You can use NSUserDefaults to save bools, integers, objects into the program and have them available whenever you open it. You can use 'boolForKey' to set a flag called "hasBeenLaunched". By default, this value will be NO when not set. Once you change it to YES, the code in the if condition will never be executed again.
In your main controller class, implement something like this:
static NSString * const MDFirstRunKey = #"MDFirstRun";
#implementation MDAppController
+ (void)initialize {
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:MDFirstRunKey];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
// the following if on Mac and is necessary:
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:defaults];
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
BOOL firstRun = [[[NSUserDefaults standardUserDefaults]
objectForKey:MDFirstRunKey] boolValue];
if (firstRun) {
// do something
[[NSUserDefaults standardUserDefaults] setObject:
[NSNumber numberWithBool:NO] forKey:MDFirstRunKey];
} else {
// do something else
}
}
#end
The +initialize class method is called before an instance of the class it's found in is created; in other words, it is called very early on, and is a good place to set up your default values.
See Preferences and Settings Programming Guide: Registering Your App's Default Preferences for more info.

Delaying an -(id)init instance; is it possible?

I've been trying to get a PDF from an NSURL that is changed during a
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
The change in NSURL logs perfectly, but the view is loaded before the app has a chance to act upon that change. Is there a way to delay the reading of the change in URL by simply moving the code to the
viewDidLoad
section, or do I have to drastically change everything? Here's my -(id)init method:
- (id)init {
if (self = [super init]) {
CFURLRef pdfURL = (CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:appDelegate.baseURL ofType:#"pdf"]];
pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
}
return self;
}
When you need to work with network the proven approach is to use asynchronous calls. This is because of the nature of a network connection; it is unpredictable, not always reliable, the time you need to spend to get the result from the server can vary from millisecond to minutes.
I would make a data model class, MyPDFModel, with an asynchronous method, that should run a thread to get the file from the server:
- (void)requestPDFWithURL:(NSURL*)fileURL
{
[NSThread detachNewThreadSelector:#selector(requestPDFWithURLThreaded:) toTarget:self fileURL];
}
- (void)requestPDFWithURLThreaded:(NSURL*)fileURL
{
NSAutoreleasePool* pool = [NSAutoreleasePool new];
// do whatever you need to get either the file or an error
if (isTheFileValid)
[_delegate performSelectorOnMainThread:#selector(requestDidGetPDF:) withObject:PDFFile waitUntilDone:NO];
else
[_delegate performSelectorOnMainThread:#selector(requestDidFailWithError:) withObject:error waitUntilDone:NO];
[pool release];
}
Meanwhile the UI should display an activity indicator.
The MyPDFModelDelegate protocol should have two methods:
- (void)requestDidGetPDF:(YourPDFWrapperClass*)PDFDocument;
- (void)requestDidFailWithError:(NSError*)error;
YourPDFWrapperClass is used to return an autoreleased document.
The delegate can let the UI know that the data has been updated, for example by posting a notification if the delegate is a part of the data model.
This is just an example, the implementation can be different depending on your needs, but I think you will get the idea.
P.S. Delaying an init is a very bad idea.

Cocoa/WebKit, having "window.open()" JavaScript links opening in an instance of Safari

I am building a really basic Cocoa application using WebKit, to display a Flash/Silverlight application within it. Very basic, no intentions for it to be a browser itself.
So far I have been able to get it to open basic html links (<a href="..." />) in a new instance of Safari using
[[NSWorkspace sharedWorkspace] openURL:[request URL]];
Now my difficulty is opening a link in a new instance of Safari when window.open() is used in JavaScript. I "think" (and by this, I have been hacking away at the code and am unsure if i actually did or not) I got this kind of working by setting the WebView's policyDelegate and implementing its
-webView:decidePolicyForNavigationAction:request:frame:decisionListener:
delegate method. However this led to some erratic behavior.
So the simple question, what do I need to do so that when window.open() is called, the link is opened in a new instance of Safari.
Thanks
Big point, I am normally a .NET developer, and have only been working with Cocoa/WebKit for a few days.
I made from progress last night and pinned down part of my problem.
I am already using webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener: and I have gotten it to work with anchor tags, however the method never seems to get called when JavaScript is invoked.
However when window.open() is called webView:createWebViewWithRequest:request is called, I have tried to force the window to open in Safari here, however request is always null. So I can never read the URL out.
I have done some searching around, and this seems to be a known "misfeature" however I have not been able to find a way to work around it.
From what I understand createWebViewWithRequest gives you the ability to create the new webview, the the requested url is then sent to the new webView to be loaded. This is the best explanation I have been able to find so far.
So while many people have pointed out this problem, I have yet to see any solution which fits my needs. I will try to delve a little deeper into the decidePolicyForNewWindowAction again.
Thanks!
Well, I'm handling it by creating a dummy webView, setting it's frameLoad delegate to a custom class that handles
- (void)webView:decidePolicyForNavigationAction:actionInformation :request:frame:decisionListener:
and opens a new window there.
code :
- (WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request {
//this is a hack because request URL is null here due to a bug in webkit
return [newWindowHandler webView];
}
and NewWindowHandler :
#implementation NewWindowHandler
-(NewWindowHandler*)initWithWebView:(WebView*)newWebView {
webView = newWebView;
[webView setUIDelegate:self];
[webView setPolicyDelegate:self];
[webView setResourceLoadDelegate:self];
return self;
}
- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener {
[[NSWorkspace sharedWorkspace] openURL:[actionInformation objectForKey:WebActionOriginalURLKey]];
}
-(WebView*)webView {
return webView;
}
There seems to be a bug with webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener: in that the request is always nil, but there is a robust solution that works with both normal target="_blank" links as well as javascript ones.
Basically I use another ephemeral WebView to handle the new page load in. Similar to Yoni Shalom but with a little more syntactic sugar.
To use it first set a delegate object for your WebView, in this case I'm setting myself as the delegate:
webView.UIDelegate = self;
Then just implement the webView:createWebViewWithRequest: delegate method and use my block based API to do something when a new page is loaded, in this case I'm opening the page in an external browser:
-(WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request {
return [GBWebViewExternalLinkHandler riggedWebViewWithLoadHandler:^(NSURL *url) {
[[NSWorkspace sharedWorkspace] openURL:url];
}];
}
That's pretty much it. Here's the code for my class. Header:
// GBWebViewExternalLinkHandler.h
// TabApp2
//
// Created by Luka Mirosevic on 13/03/2013.
// Copyright (c) 2013 Goonbee. All rights reserved.
//
#import <Foundation/Foundation.h>
#class WebView;
typedef void(^NewWindowCallback)(NSURL *url);
#interface GBWebViewExternalLinkHandler : NSObject
+(WebView *)riggedWebViewWithLoadHandler:(NewWindowCallback)handler;
#end
Implemetation:
// GBWebViewExternalLinkHandler.m
// TabApp2
//
// Created by Luka Mirosevic on 13/03/2013.
// Copyright (c) 2013 Goonbee. All rights reserved.
//
#import "GBWebViewExternalLinkHandler.h"
#import <WebKit/WebKit.h>
#interface GBWebViewExternalLinkHandler ()
#property (strong, nonatomic) WebView *attachedWebView;
#property (strong, nonatomic) GBWebViewExternalLinkHandler *retainedSelf;
#property (copy, nonatomic) NewWindowCallback handler;
#end
#implementation GBWebViewExternalLinkHandler
-(id)init {
if (self = [super init]) {
//create a new webview with self as the policyDelegate, and keep a ref to it
self.attachedWebView = [WebView new];
self.attachedWebView.policyDelegate = self;
}
return self;
}
-(void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener {
//execute handler
if (self.handler) {
self.handler(actionInformation[WebActionOriginalURLKey]);
}
//our job is done so safe to unretain yourself
self.retainedSelf = nil;
}
+(WebView *)riggedWebViewWithLoadHandler:(NewWindowCallback)handler {
//create a new handler
GBWebViewExternalLinkHandler *newWindowHandler = [GBWebViewExternalLinkHandler new];
//store the block
newWindowHandler.handler = handler;
//retain yourself so that we persist until the webView:decidePolicyForNavigationAction:request:frame:decisionListener: method has been called
newWindowHandler.retainedSelf = newWindowHandler;
//return the attached webview
return newWindowHandler.attachedWebView;
}
#end
Licensed as Apache 2.
You don't mention what kind of erratic behaviour you are seeing. A quick possibility, is that when implementing the delegate method you forgot to tell the webview you are ignoring the click by calling the ignore method of the WebPolicyDecisionListener that was passed to your delegate, which may have put things into a weird state.
If that is not the issue, then how much control do you have over the content you are displaying? The policy delegate gives you easy mechanisms to filter all resource loads (as you have discovered), and all new window opens via webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:. All window.open calls should funnel through that, as will anything else that triggers a new window.
If there are other window opens you want to keep inside your app, you will to do a little more work. One of the arguments passed into the delegate is a dictionary containing information about the event. Insie that dictionary the WebActionElementKey will have a dictionary containing a number of details, including the original dom content of the link. If you want to poke around in there you can grab the actual DOM element, and check the text of the href to see if it starts with window.open. That is a bit heavy weight, but if you want fine grained control it will give it to you.
By reading all posts, i have come up with my simple solution, all funcs are in same class,here it is, opens a link with browser.
- (WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request {
return [self externalWebView:sender];
}
- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
{
[[NSWorkspace sharedWorkspace] openURL:[actionInformation objectForKey:WebActionOriginalURLKey]];
}
-(WebView*)externalWebView:(WebView*)newWebView
{
WebView *webView = newWebView;
[webView setUIDelegate:self];
[webView setPolicyDelegate:self];
[webView setResourceLoadDelegate:self];
return webView;
}
Explanation:
Windows created from JavaScript via window.open go through createWebViewWithRequest.
All window.open calls result in a createWebViewWithRequest: with a null request, then later a location change on that WebView.
For further information, see this old post on the WebKit mailing list.
An alternative to returning a new WebView and waiting for its loadRequest: method to be called, I ended up overwriting the window.open function in the WebView's JSContext:
First, I set my controller to be the WebFrameLoadDelegate of the WebView:
myWebView.frameLoadDelegate = self;
Then, in the delegate method, I overwrote the window.open function, and I can process the URL there instead.
- (void)webView:(WebView *)webView didCreateJavaScriptContext:(JSContext *)context forFrame:(WebFrame *)frame{
context[#"window"][#"open"] = ^(id url){
NSLog(#"url to load: %#", url);
};
}
This let me handle the request however I needed to without the awkward need to create additional WebViews.