16
16
17
17
#import " RunTestsAction.h"
18
18
19
+ #import " BufferedReporter.h"
19
20
#import " OCUnitIOSAppTestRunner.h"
20
21
#import " OCUnitIOSLogicTestRunner.h"
21
22
#import " OCUnitOSXAppTestRunner.h"
26
27
#import " XcodeSubjectInfo.h"
27
28
#import " XCToolUtil.h"
28
29
29
- @implementation RunTestsAction
30
+ @implementation RunTestsAction {
31
+ NSLock *_appTestLock;
32
+ }
30
33
31
34
+ (NSString *)name
32
35
{
@@ -57,20 +60,26 @@ + (NSArray *)options
57
60
description:
58
61
@" Use clean install of TEST_HOST for every app test run"
59
62
setFlag: @selector (setFreshInstall: )],
63
+ [Action actionOptionWithName: @" parallelize"
64
+ aliases: nil
65
+ description: @" Parallelize execution of logic tests"
66
+ setFlag: @selector (setParallelize: )],
60
67
];
61
68
}
62
69
63
70
- (id )init
64
71
{
65
72
if (self = [super init ]) {
66
73
self.onlyList = [NSMutableArray array ];
74
+ self->_appTestLock = [[NSLock alloc ] init ];
67
75
}
68
76
return self;
69
77
}
70
78
71
79
- (void )dealloc {
72
80
self.onlyList = nil ;
73
81
self.testSDK = nil ;
82
+ [self ->_appTestLock release ];
74
83
[super dealloc ];
75
84
}
76
85
@@ -220,17 +229,26 @@ - (NSDictionary *)enviornmentWithMacrosExpanded:(NSDictionary *)dict
220
229
return result;
221
230
}
222
231
223
- - (BOOL )runTestable : (NSDictionary *)testable
224
- reporters : (NSArray *)reporters
225
- objRoot : (NSString *)objRoot
226
- symRoot : (NSString *)symRoot
227
- sharedPrecompsDir : (NSString *)sharedPrecompsDir
228
- xcodeArguments : (NSArray *)xcodeArguments
229
- testSDK : (NSString *)testSDK
230
- freshSimulator : (BOOL )freshSimulator
231
- freshInstall : (BOOL )freshInstall
232
- senTestList : (NSString *)senTestList
233
- senTestInvertScope : (BOOL )senTestInvertScope
232
+ static NSString * const kTestableBlock = @" block" ;
233
+ static NSString * const kTestableMustRunInMainThread = @" mustRunInMainThread" ;
234
+
235
+ /* !
236
+ Retrieves build params of the test and prepares a block that will run the test.
237
+
238
+ @return an NSDicitonary of the block that runs the test, and whether the test
239
+ must be run on the main thread
240
+ */
241
+ - (NSDictionary *) blockForTestable : (NSDictionary *)testable
242
+ reporters : (NSArray *)reporters
243
+ objRoot : (NSString *)objRoot
244
+ symRoot : (NSString *)symRoot
245
+ sharedPrecompsDir : (NSString *)sharedPrecompsDir
246
+ xcodeArguments : (NSArray *)xcodeArguments
247
+ testSDK : (NSString *)testSDK
248
+ freshSimulator : (BOOL )freshSimulator
249
+ freshInstall : (BOOL )freshInstall
250
+ senTestList : (NSString *)senTestList
251
+ senTestInvertScope : (BOOL )senTestInvertScope
234
252
{
235
253
NSString *testableProjectPath = testable[@" projectPath" ];
236
254
NSString *testableTarget = testable[@" target" ];
@@ -305,54 +323,65 @@ - (BOOL)runTestable:(NSDictionary *)testable
305
323
NSAssert (NO , @" Unexpected SDK: %@ " , sdkName);
306
324
}
307
325
308
- BOOL succeeded = YES ;
309
-
310
- for (NSArray *testConfiguration in testConfigurations) {
311
- Class testRunnerClass = testConfiguration[0 ];
312
- BOOL garbageCollectionEnabled = [testConfiguration[1 ] boolValue ];
313
-
314
- OCUnitTestRunner *testRunner = [[[testRunnerClass alloc ]
315
- initWithBuildSettings: testableBuildSettings
316
- senTestList: senTestList
317
- senTestInvertScope: senTestInvertScope
318
- arguments: arguments
319
- environment: environment
320
- garbageCollection: garbageCollectionEnabled
321
- freshSimulator: freshSimulator
322
- freshInstall: freshInstall
323
- standardOutput: nil
324
- standardError: nil
325
- reporters: reporters] autorelease ];
326
-
327
- NSDictionary *commonEventInfo = @{kReporter_BeginOCUnit_BundleNameKey : testableBuildSettings[@" FULL_PRODUCT_NAME" ],
328
- kReporter_BeginOCUnit_SDKNameKey : testableBuildSettings[@" SDK_NAME" ],
329
- kReporter_BeginOCUnit_TestTypeKey : isApplicationTest ? @" application-test" : @" logic-test" ,
330
- kReporter_BeginOCUnit_GCEnabledKey : @(garbageCollectionEnabled),
331
- };
332
-
333
- NSMutableDictionary *beginEvent = [NSMutableDictionary dictionaryWithDictionary: @{
334
- @" event" : kReporter_Events_BeginOCUnit ,
335
- }];
336
- [beginEvent addEntriesFromDictionary: commonEventInfo];
337
- [reporters makeObjectsPerformSelector: @selector (handleEvent: ) withObject: beginEvent];
326
+ BOOL (^action)() = ^() {
327
+ BOOL succeeded = YES ;
328
+
329
+ for (NSArray *testConfiguration in testConfigurations) {
330
+ Class testRunnerClass = testConfiguration[0 ];
331
+ BOOL garbageCollectionEnabled = [testConfiguration[1 ] boolValue ];
332
+
333
+ OCUnitTestRunner *testRunner = [[[testRunnerClass alloc ]
334
+ initWithBuildSettings: testableBuildSettings
335
+ senTestList: senTestList
336
+ senTestInvertScope: senTestInvertScope
337
+ arguments: arguments
338
+ environment: environment
339
+ garbageCollection: garbageCollectionEnabled
340
+ freshSimulator: freshSimulator
341
+ freshInstall: freshInstall
342
+ standardOutput: nil
343
+ standardError: nil
344
+ reporters: reporters] autorelease ];
345
+
346
+ NSDictionary *commonEventInfo = @{kReporter_BeginOCUnit_BundleNameKey : testableBuildSettings[@" FULL_PRODUCT_NAME" ],
347
+ kReporter_BeginOCUnit_SDKNameKey : testableBuildSettings[@" SDK_NAME" ],
348
+ kReporter_BeginOCUnit_TestTypeKey : isApplicationTest ? @" application-test" : @" logic-test" ,
349
+ kReporter_BeginOCUnit_GCEnabledKey : @(garbageCollectionEnabled),
350
+ };
351
+
352
+ NSMutableDictionary *beginEvent = [NSMutableDictionary dictionaryWithDictionary: @{
353
+ @" event" : kReporter_Events_BeginOCUnit ,
354
+ }];
355
+ [beginEvent addEntriesFromDictionary: commonEventInfo];
356
+ [reporters makeObjectsPerformSelector: @selector (handleEvent: ) withObject: beginEvent];
357
+
358
+ NSString *error = nil ;
359
+ BOOL configurationSucceeded = [testRunner runTestsWithError: &error];
360
+
361
+ if (!configurationSucceeded) {
362
+ succeeded = NO ;
363
+ }
338
364
339
- NSString *error = nil ;
340
- BOOL configurationSucceeded = [testRunner runTestsWithError: &error];
365
+ NSMutableDictionary *endEvent = [NSMutableDictionary dictionaryWithDictionary: @{
366
+ @" event" : kReporter_Events_EndOCUnit ,
367
+ kReporter_EndOCUnit_SucceededKey : @(succeeded),
368
+ kReporter_EndOCUnit_FailureReasonKey : (error ? error : [NSNull null ]),
369
+ }];
370
+ [endEvent addEntriesFromDictionary: commonEventInfo];
371
+ [reporters makeObjectsPerformSelector: @selector (handleEvent: ) withObject: endEvent];
372
+ }
341
373
342
- if (!configurationSucceeded) {
343
- succeeded = NO ;
374
+ for (id reporter in reporters) {
375
+ if ([reporter respondsToSelector: @selector (flush )]) {
376
+ [reporter flush ];
377
+ }
344
378
}
345
379
346
- NSMutableDictionary *endEvent = [NSMutableDictionary dictionaryWithDictionary: @{
347
- @" event" : kReporter_Events_EndOCUnit ,
348
- kReporter_EndOCUnit_SucceededKey : @(succeeded),
349
- kReporter_EndOCUnit_FailureReasonKey : (error ? error : [NSNull null ]),
350
- }];
351
- [endEvent addEntriesFromDictionary: commonEventInfo];
352
- [reporters makeObjectsPerformSelector: @selector (handleEvent: ) withObject: endEvent];
353
- }
380
+ return succeeded;
381
+ };
354
382
355
- return succeeded;
383
+ return @{kTestableBlock : [[action copy ] autorelease ],
384
+ kTestableMustRunInMainThread : @(isApplicationTest)};
356
385
}
357
386
358
387
- (BOOL )runTestables : (NSArray *)testables
@@ -362,24 +391,67 @@ - (BOOL)runTestables:(NSArray *)testables
362
391
options : (Options *)options
363
392
xcodeSubjectInfo : (XcodeSubjectInfo *)xcodeSubjectInfo
364
393
{
365
- BOOL succeeded = YES ;
366
-
367
- for (NSDictionary *testable in testables) {
368
- BOOL senTestInvertScope = [testable[@" senTestInvertScope" ] boolValue ];
369
- NSString *senTestList = testable[@" senTestList" ];
370
-
371
- if (![self runTestable: testable
372
- reporters: options.reporters
373
- objRoot: xcodeSubjectInfo.objRoot
374
- symRoot: xcodeSubjectInfo.symRoot
375
- sharedPrecompsDir: xcodeSubjectInfo.sharedPrecompsDir
376
- xcodeArguments: [options commonXcodeBuildArgumentsIncludingSDK: NO ]
377
- testSDK: testSDK
378
- freshSimulator: freshSimulator
379
- freshInstall: freshInstall
380
- senTestList: senTestList
381
- senTestInvertScope: senTestInvertScope]) {
382
- succeeded = NO ;
394
+ NSObject *succeededLock = [[[NSObject alloc ] init ] autorelease ];
395
+ __block BOOL succeeded = YES ;
396
+
397
+ @autoreleasepool {
398
+ NSMutableArray *blocksToRunInMainThread = [NSMutableArray array ];
399
+ NSOperationQueue *operationQueue = [[[NSOperationQueue alloc ] init ] autorelease ];
400
+ if (!self.parallelize ) {
401
+ operationQueue.maxConcurrentOperationCount = 1 ;
402
+ }
403
+
404
+ for (NSDictionary *testable in testables) {
405
+ NSOperation *operation = [NSBlockOperation blockOperationWithBlock: ^{
406
+ BOOL senTestInvertScope = [testable[@" senTestInvertScope" ] boolValue ];
407
+ NSString *senTestList = testable[@" senTestList" ];
408
+
409
+ NSArray *reporters = options.reporters ;
410
+ if (self.parallelize ) {
411
+ // parallel execution will cause reporters to be buffered and flushed
412
+ // atomically at end of test to prevent interleaved output.
413
+ NSMutableArray *bufferedReporters = [NSMutableArray arrayWithCapacity: reporters.count];
414
+ for (Reporter *reporter in reporters) {
415
+ [bufferedReporters addObject: [BufferedReporter bufferedReporterWithReporter: reporter]];
416
+ }
417
+ reporters = bufferedReporters;
418
+ }
419
+
420
+ NSDictionary *action = [self blockForTestable: testable
421
+ reporters: reporters
422
+ objRoot: xcodeSubjectInfo.objRoot
423
+ symRoot: xcodeSubjectInfo.symRoot
424
+ sharedPrecompsDir: xcodeSubjectInfo.sharedPrecompsDir
425
+ xcodeArguments: [options commonXcodeBuildArgumentsIncludingSDK: NO ]
426
+ testSDK: testSDK
427
+ freshSimulator: freshSimulator
428
+ freshInstall: freshInstall
429
+ senTestList: senTestList
430
+ senTestInvertScope: senTestInvertScope];
431
+
432
+ BOOL (^block)() = action[kTestableBlock ];
433
+ if ([action[kTestableMustRunInMainThread ] boolValue ]) {
434
+ @synchronized (blocksToRunInMainThread) {
435
+ [blocksToRunInMainThread addObject: block];
436
+ }
437
+ } else {
438
+ if (!block ()) {
439
+ @synchronized (succeededLock) {
440
+ succeeded = NO ;
441
+ }
442
+ }
443
+ }
444
+ }];
445
+
446
+ [operationQueue addOperation: operation];
447
+ }
448
+
449
+ [operationQueue waitUntilAllOperationsAreFinished ];
450
+
451
+ for (BOOL (^block)() in blocksToRunInMainThread) {
452
+ if (!block ()) {
453
+ succeeded = NO ;
454
+ }
383
455
}
384
456
}
385
457
0 commit comments