aboutsummaryrefslogtreecommitdiff
path: root/UnitTesting/GTMIPhoneUnitTestDelegate.m
blob: a112a99489071ef1c62d44fe7865a44df8d73436 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
//
//  GTMIPhoneUnitTestDelegate.m
//
//  Copyright 2008 Google Inc.
//
//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
//  use this file except in compliance with the License.  You may obtain a copy
//  of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
//  License for the specific language governing permissions and limitations under
//  the License.
//

#import "GTMIPhoneUnitTestDelegate.h"

#import "GTMCodeCoverageApp.h"
#import "GTMDefines.h"
#if !GTM_IPHONE_SDK
#error GTMIPhoneUnitTestDelegate for iPhone only
#endif
#import <objc/runtime.h>
#import <stdio.h>
#import <UIKit/UIKit.h>
#import "GTMSenTestCase.h"

@interface UIApplication (GTMIPhoneUnitTestDelegate)

// SPI that we need to exit cleanly with a value.
- (void)_terminateWithStatus:(int)status;

@end

@interface GTMIPhoneUnitTestDelegate ()
// We have cases where we are created in UIApplicationMain, but then the
// user accidentally/intentionally replaces us as a delegate in their xib file
// which means that we never get the applicationDidFinishLaunching: message.
// We can register for the notification, but when the applications delegate
// is reset, it releases us, and we get dealloced. Therefore we have retainer
// which is responsible for retaining us until we get the notification.
// We do it through this slightly roundabout route (instead of just an extra
// retain in the init) so that clang doesn't complain about a leak.
// We also check to make sure we aren't called twice with the
// applicationDidFinishLaunchingCalled flag.
@property (readwrite, retain, nonatomic) GTMIPhoneUnitTestDelegate *retainer;

- (void)runTestsAndExit:(UIApplication *)application;

@end

@implementation GTMIPhoneUnitTestDelegate

@synthesize retainer = retainer_;

- (id)init {
  if ((self = [super init])) {
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self
           selector:@selector(handleApplicationDidFinishLaunchingNotification:)
               name:UIApplicationDidFinishLaunchingNotification
             object:[UIApplication sharedApplication]];
    [self setRetainer:self];
  }
  return self;
}

// If running in the mode were we get the notification, bridge it over to
// the method we'd get if we are the app delegate.
- (void)handleApplicationDidFinishLaunchingNotification:(NSNotification *)note {
  [self applicationDidFinishLaunching:[note object]];
}

// Run through all the registered classes and run test methods on any
// that are subclasses of SenTestCase. Terminate the application upon
// test completion.
- (void)applicationDidFinishLaunching:(UIApplication *)application {

  // We could get called twice once from our notification registration, and
  // once if we actually still are the delegate of the application after
  // it has finished launching. So we'll just return if we've been called once.
  if (applicationDidFinishLaunchingCalled_) return;
  applicationDidFinishLaunchingCalled_ = YES;

  NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  [nc removeObserver:self
                name:UIApplicationDidFinishLaunchingNotification
              object:application];

  // To permit UI-based tests, run the tests and exit app in a delayed selector
  // call. This ensures tests are run outside of applicationDidFinishLaunching:.
  [self performSelector:@selector(runTestsAndExit:)
             withObject:application
             afterDelay:0];
}

// Run through all tests and then exit application if required.
- (void)runTestsAndExit:(UIApplication *)application {
  [self runTests];

  if ([application respondsToSelector:@selector(gtm_gcov_flush)]) {
    [application performSelector:@selector(gtm_gcov_flush)];
  }

  if (!getenv("GTM_DISABLE_TERMINATION")) {
    // To help using xcodebuild, make the exit status 0/1 to signal the tests
    // success/failure.
    int exitStatus = (([self totalFailures] == 0U) ? 0 : 1);
    // Alternative to exit(status); so it cleanly terminates the UIApplication
    // and classes that depend on this signal to exit cleanly.
    NSMethodSignature * terminateSignature
      = [UIApplication instanceMethodSignatureForSelector:@selector(_terminateWithStatus:)];
    if (terminateSignature != nil) {
      NSInvocation * terminateInvocation
        = [NSInvocation invocationWithMethodSignature:terminateSignature];
      [terminateInvocation setTarget:application];
      [terminateInvocation setSelector:@selector(_terminateWithStatus:)];
      [terminateInvocation setArgument:&exitStatus atIndex:2];
      [terminateInvocation invoke];
    } else {
      exit(exitStatus);
    }
  }

  // Release ourself now that we're done. If we really are the application
  // delegate, it will have retained us, so we'll stick around if necessary.
  [self setRetainer:nil];
}

static int ClassSort(const void *a, const void *b) {
  Class *classA = (Class *)a;
  Class *classB = (Class *)b;
  const char *nameA = class_getName(*classA);
  const char *nameB = class_getName(*classB);
  return strcmp(nameA, nameB);
}

// Run through all the registered classes and run test methods on any
// that are subclasses of SenTestCase. Print results and run time to
// the default output.
- (void)runTests {
  int count = objc_getClassList(NULL, 0);
  NSMutableData *classData
    = [NSMutableData dataWithLength:sizeof(Class) * count];
  Class *classes = (Class*)[classData mutableBytes];
  _GTMDevAssert(classes, @"Couldn't allocate class list");
  objc_getClassList(classes, count);

  // Follow SenTest's lead and sort the classes.  (This may only be a change
  // in the iOS 5 runtime, but make sure it is always sorted just incase)
  mergesort(classes, count, sizeof(Class), ClassSort);

  totalFailures_ = 0;
  totalSuccesses_ = 0;
  NSString *suiteName = [[NSBundle mainBundle] bundlePath];
  NSDate *suiteStartDate = [NSDate date];
  NSString *suiteStartString
    = [NSString stringWithFormat:@"Test Suite '%@' started at %@\n",
                                 suiteName, suiteStartDate];
  fputs([suiteStartString UTF8String], stderr);
  fflush(stderr);
  for (int i = 0; i < count; ++i) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    Class currClass = classes[i];
    if (class_respondsToSelector(currClass, @selector(conformsToProtocol:)) &&
        [currClass conformsToProtocol:@protocol(SenTestCase)]) {
      NSArray *invocations = [currClass testInvocations];
      if ([invocations count]) {
        NSDate *fixtureStartDate = [NSDate date];
        NSString *fixtureName = NSStringFromClass(currClass);
        NSString *fixtureStartString
          = [NSString stringWithFormat:@"Test Suite '%@' started at %@\n",
                                       fixtureName, fixtureStartDate];
        int fixtureSuccesses = 0;
        int fixtureFailures = 0;
        fputs([fixtureStartString UTF8String], stderr);
        fflush(stderr);
        NSInvocation *invocation;
        for (invocation in invocations) {
          GTMTestCase *testCase
            = [[currClass alloc] initWithInvocation:invocation];
          BOOL failed = NO;
          NSDate *caseStartDate = [NSDate date];
          NSString *selectorName = NSStringFromSelector([invocation selector]);
          NSString *caseStartString
            = [NSString stringWithFormat:@"Test Case '-[%@ %@]' started.\n",
               fixtureName, selectorName];
          fputs([caseStartString UTF8String], stderr);
          fflush(stderr);
          @try {
            [testCase performTest];
          } @catch (NSException *exception) {
            failed = YES;
          }
          if (failed) {
            fixtureFailures += 1;
          } else {
            fixtureSuccesses += 1;
          }
          NSTimeInterval caseEndTime
            = [[NSDate date] timeIntervalSinceDate:caseStartDate];
          NSString *caseEndString
            = [NSString stringWithFormat:@"Test Case '-[%@ %@]' %@ (%0.3f "
               "seconds).\n",
               fixtureName, selectorName,
               failed ? @"failed" : @"passed",
               caseEndTime];
          fputs([caseEndString UTF8String], stderr);
          fflush(stderr);
          [testCase release];
        }
        NSDate *fixtureEndDate = [NSDate date];
        NSTimeInterval fixtureEndTime
          = [fixtureEndDate timeIntervalSinceDate:fixtureStartDate];
        NSString *fixtureEndString
          = [NSString stringWithFormat:@"Test Suite '%@' finished at %@.\n"
             "Executed %d tests, with %d failures (%d "
             "unexpected) in %0.3f (%0.3f) seconds\n\n",
             fixtureName, fixtureEndDate,
             fixtureSuccesses + fixtureFailures,
             fixtureFailures, fixtureFailures,
             fixtureEndTime, fixtureEndTime];
        fputs([fixtureEndString UTF8String], stderr);
        fflush(stderr);
        totalSuccesses_ += fixtureSuccesses;
        totalFailures_ += fixtureFailures;
      }
    }
    [pool release];
  }
  NSDate *suiteEndDate = [NSDate date];
  NSTimeInterval suiteEndTime
    = [suiteEndDate timeIntervalSinceDate:suiteStartDate];
  NSString *suiteEndString
    = [NSString stringWithFormat:@"Test Suite '%@' finished at %@.\n"
                                 "Executed %tu tests, with %tu failures (%tu "
                                 "unexpected) in %0.3f (%0.3f) seconds\n\n",
                                 suiteName, suiteEndDate,
                                 totalSuccesses_ + totalFailures_,
                                 totalFailures_, totalFailures_,
                                 suiteEndTime, suiteEndTime];
  fputs([suiteEndString UTF8String], stderr);
  fflush(stderr);
}

- (NSUInteger)totalSuccesses {
  return totalSuccesses_;
}

- (NSUInteger)totalFailures {
  return totalFailures_;
}

@end