From e6adb8c0d22a7beeaf98ea1f3ce07b76c364c815 Mon Sep 17 00:00:00 2001 From: James Cooper Date: Wed, 29 Oct 2014 17:23:18 -0400 Subject: [PATCH 1/8] James C - added tests for invariants --- Source/CDRExampleGroup.m | 3 + Source/CDRSpec.m | 5 ++ Source/Headers/Public/CDRExampleGroup.h | 3 +- Source/Headers/Public/CDRSpec.h | 1 + Spec/CDRExampleGroupSpec.mm | 40 +++++++++++++ Spec/SpecSpec.mm | 75 +++++++++++++++++++++++++ 6 files changed, 126 insertions(+), 1 deletion(-) diff --git a/Source/CDRExampleGroup.m b/Source/CDRExampleGroup.m index 34e771ef..69cc98d6 100644 --- a/Source/CDRExampleGroup.m +++ b/Source/CDRExampleGroup.m @@ -74,6 +74,9 @@ - (void)addAfter:(CDRSpecBlock)block { [blockCopy release]; } +- (void)addInvariant:(CDRExampleBase *)inv { +} + #pragma mark CDRExampleBase - (CDRExampleState)state { if (0 == [examples_ count]) { diff --git a/Source/CDRSpec.m b/Source/CDRSpec.m index 501ff406..c02350b9 100644 --- a/Source/CDRSpec.m +++ b/Source/CDRSpec.m @@ -18,6 +18,11 @@ void afterEach(CDRSpecBlock block) { [CDR_currentSpec.currentGroup addAfter:block]; } +void invariant(NSString *text, CDRSpecBlock block) { + NSString * invName = [NSString stringWithFormat:@"%@ (invariant in %@)", text, CDR_currentSpec.rootGroup]; + [CDR_currentSpec.currentGroup addInvariant:[CDRExample exampleWithText:invName andBlock:block]]; +} + #define with_stack_address(b) \ ((b.stackAddress = CDRCallerStackAddress()), b) diff --git a/Source/Headers/Public/CDRExampleGroup.h b/Source/Headers/Public/CDRExampleGroup.h index fdfe2d4c..005870ad 100644 --- a/Source/Headers/Public/CDRExampleGroup.h +++ b/Source/Headers/Public/CDRExampleGroup.h @@ -4,7 +4,7 @@ NS_ASSUME_NONNULL_BEGIN @interface CDRExampleGroup : CDRExampleBase { - NSMutableArray *beforeBlocks_, *examples_, *afterBlocks_; + NSMutableArray *beforeBlocks_, *examples_, *afterBlocks_, *invariants_; BOOL isRoot_; CDRSpecBlock subjectActionBlock_; } @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)add:(CDRExampleBase *)example; - (void)addBefore:(CDRSpecBlock)block; - (void)addAfter:(CDRSpecBlock)block; +- (void)addInvariant:(CDRExampleBase *)inv; @end diff --git a/Source/Headers/Public/CDRSpec.h b/Source/Headers/Public/CDRSpec.h index c58227e4..ae210b79 100644 --- a/Source/Headers/Public/CDRSpec.h +++ b/Source/Headers/Public/CDRSpec.h @@ -18,6 +18,7 @@ extern "C" { #endif void beforeEach(CDRSpecBlock); void afterEach(CDRSpecBlock); +void invariant(NSString *, CDRSpecBlock); CDRExampleGroup * describe(NSString *, __nullable CDRSpecBlock); extern CDRExampleGroup* __nonnull (*__nonnull context)(NSString *, __nullable CDRSpecBlock); diff --git a/Spec/CDRExampleGroupSpec.mm b/Spec/CDRExampleGroupSpec.mm index 523cc962..40342f68 100644 --- a/Spec/CDRExampleGroupSpec.mm +++ b/Spec/CDRExampleGroupSpec.mm @@ -198,6 +198,46 @@ blockInvocationCount should equal(3); }); }); + + describe(@"invariant", ^{ + __block NSInteger blockInvocationCount; + + beforeEach(^{ + CDRSpecBlock invariantBlock = ^{ ++blockInvocationCount; }; + [group addInvariant:[CDRExample exampleWithText:@"inv" andBlock:invariantBlock]]; + }); + + describe(@"for a single block", ^{ + beforeEach(^{ + blockInvocationCount = 0; + [group add:errorExample]; + [group add:failingExample]; + [group add:passingExample]; + [group runWithDispatcher:dispatcher]; + }); + + it(@"should be called once, regardless of failures or errors", ^{ + blockInvocationCount should equal(1); + }); + }); + + describe(@"for nested block", ^{ + beforeEach(^{ + blockInvocationCount = 0; + CDRExampleGroup * innerGroup = [[[CDRExampleGroup alloc] initWithText:groupText] autorelease]; + [innerGroup add:errorExample]; + [innerGroup add:failingExample]; + [group add:passingExample]; + [group add:innerGroup]; + + [group runWithDispatcher:dispatcher]; + }); + + it(@"should be called twice, once as it pases through each block", ^{ + blockInvocationCount should equal(2); + }); + }); + }); describe(@"state", ^{ describe(@"for a group containing no examples", ^{ diff --git a/Spec/SpecSpec.mm b/Spec/SpecSpec.mm index 62be21de..81de8917 100644 --- a/Spec/SpecSpec.mm +++ b/Spec/SpecSpec.mm @@ -25,6 +25,10 @@ void expectFailure(CDRSpecBlock block) { afterEach(^{ // NSLog(@"=====================> I should run after all specs."); }); + + invariant(@"an invariant run in multiple places", ^{ + // NSLog(@"=====================> Invariant was run here."); + }); describe(@"a nested spec", ^{ beforeEach(^{ @@ -42,6 +46,10 @@ void expectFailure(CDRSpecBlock block) { it(@"should also also run", ^{ // NSLog(@"=====================> Another nested spec"); }); + + it(@"should run the invariant below here", ^{ + // NSLog(@"vvvvvvvvvvvvvvvvvvvvvv Invariant below"); + }); }); context(@"a nested spec (context)", ^{ @@ -60,11 +68,29 @@ void expectFailure(CDRSpecBlock block) { it(@"should also also run", ^{ // NSLog(@"=====================> Another nested spec"); }); + + context(@"a doubly nested spec", ^{ + it(@"should also run", ^{ + // NSLog(@"=====================> Nested spec"); + }); + + it(@"should run the invariant below here", ^{ + // NSLog(@"vvvvvvvvvvvvvvvvvvvvvv Invariant below"); + }); + }); + + it(@"should run the invariant below here", ^{ + // NSLog(@"vvvvvvvvvvvvvvvvvvvvvv Invariant below"); + }); }); it(@"should run", ^{ // NSLog(@"=====================> Spec"); }); + + it(@"should run the invariant below here", ^{ + // NSLog(@"vvvvvvvvvvvvvvvvvvvvvv Invariant below"); + }); it(@"should be pending", PENDING); it(@"should also be pending", nil); @@ -222,6 +248,55 @@ void expectFailure(CDRSpecBlock block) { [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Should have thrown an exception" userInfo:nil]; }); +describe(@"an invariant", ^{ + __block NSInteger x; + __block NSInteger y; + __block BOOL ran; + + beforeEach(^{ + x = 0; + y = 0; + ran = NO; + }); + + invariant(@"invariant equates the two variables", ^{ + expect(x).to(equal(y)); + ran = YES; + }); + + context(@"in a context block", ^{ + beforeEach(^{ + x = 5; + y = 5; + }); + + context(@"in a nested context block", ^{ + beforeEach(^{ + x = -2; + y = -2; + }); + + afterEach(^{ + it(@"should run the invariant", ^{ + expect(ran).to(be_truthy); + }); + }); + }); + + afterEach(^{ + it(@"should run the invariant", ^{ + expect(ran).to(be_truthy); + }); + }); + }); + + afterEach(^{ + it(@"should run the invariant", ^{ + expect(ran).to(be_truthy); + }); + }); +}); + SPEC_END From 1776edcc077923c5b9501ec5f0f831d633a7ace6 Mon Sep 17 00:00:00 2001 From: James Cooper Date: Wed, 29 Oct 2014 22:17:21 -0400 Subject: [PATCH 2/8] James C - invariants now work (first draft) --- Source/CDRExample.m | 4 ++++ Source/CDRExampleGroup.m | 20 ++++++++++++++++++++ Spec/SpecSpec.mm | 28 ++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/Source/CDRExample.m b/Source/CDRExample.m index 585cba2e..0565c833 100644 --- a/Source/CDRExample.m +++ b/Source/CDRExample.m @@ -30,6 +30,10 @@ - (void)dealloc { [super dealloc]; } +- (id)copy { + return [[[CDRExample alloc] initWithText:text_ andBlock:block_] autorelease]; +} + #pragma mark CDRExampleBase - (CDRExampleState)state { return state_; diff --git a/Source/CDRExampleGroup.m b/Source/CDRExampleGroup.m index 69cc98d6..5a49552a 100644 --- a/Source/CDRExampleGroup.m +++ b/Source/CDRExampleGroup.m @@ -1,5 +1,6 @@ #import "CDRExampleGroup.h" #import "CDRReportDispatcher.h" +#import "CDRExample.h" @interface CDRExampleGroup (Private) - (void)startObservingExamples; @@ -24,6 +25,7 @@ - (id)initWithText:(NSString *)text isRoot:(BOOL)isRoot { beforeBlocks_ = [[NSMutableArray alloc] init]; examples_ = [[NSMutableArray alloc] init]; afterBlocks_ = [[NSMutableArray alloc] init]; + invariants_ = [[NSMutableArray alloc] init]; isRoot_ = isRoot; } return self; @@ -33,6 +35,7 @@ - (void)dealloc { [afterBlocks_ release]; [examples_ release]; [beforeBlocks_ release]; + [invariants_ release]; self.subjectActionBlock = nil; [super dealloc]; } @@ -60,6 +63,12 @@ - (CDRSpecBlock)subjectActionBlock { - (void)add:(CDRExampleBase *)example { example.parent = self; [examples_ addObject:example]; + + if ([example isKindOfClass:[CDRExampleGroup class]]) { + for (id inv in invariants_) { + [(CDRExampleGroup*)example addInvariant:[inv copy]]; + } + } } - (void)addBefore:(CDRSpecBlock)block { @@ -75,6 +84,16 @@ - (void)addAfter:(CDRSpecBlock)block { } - (void)addInvariant:(CDRExampleBase *)inv { + for (id example in examples_) { + if ([example isKindOfClass:[CDRExampleGroup class]]) { + [example addInvariant:[inv copy]]; + } + } + + CDRExampleBase * exampleInstance = [inv copy]; + exampleInstance.parent = self; + [examples_ addObject: exampleInstance]; + [invariants_ addObject: [inv copy]]; } #pragma mark CDRExampleBase @@ -124,6 +143,7 @@ - (void)runWithDispatcher:(CDRReportDispatcher *)dispatcher { [beforeBlocks_ release]; beforeBlocks_ = nil; [afterBlocks_ release]; afterBlocks_ = nil; + [invariants_ release]; invariants_ = nil; self.subjectActionBlock = nil; } diff --git a/Spec/SpecSpec.mm b/Spec/SpecSpec.mm index 81de8917..1d46cf96 100644 --- a/Spec/SpecSpec.mm +++ b/Spec/SpecSpec.mm @@ -297,6 +297,34 @@ void expectFailure(CDRSpecBlock block) { }); }); +describe(@"a failing invariant", ^{ + __block BOOL tried; + __block BOOL ran; + + beforeEach(^{ + tried = NO; + ran = NO; + }); + + invariant(@"invariant tries to do the impossible", ^{ + expectFailure(^{ + tried = YES; + expect(true).to(be_falsy); + ran = YES; + }); + }); + + afterEach(^{ + it(@"should run the invariant", ^{ + expect(tried).to(be_truthy); + }); + + it(@"should not complete running the invariant", ^{ + expect(ran).to(be_falsy); + }); + }); +}); + SPEC_END From 5bb503fb9c46f6085c41c8539d30761262e1ec5b Mon Sep 17 00:00:00 2001 From: James Cooper Date: Wed, 29 Oct 2014 22:35:19 -0400 Subject: [PATCH 3/8] James C - added a few more tests --- Spec/CDRExampleGroupSpec.mm | 61 +++++++++++++++++++++++++++++++++++++ Spec/SpecSpec.mm | 12 ++++---- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/Spec/CDRExampleGroupSpec.mm b/Spec/CDRExampleGroupSpec.mm index 40342f68..e6775c2b 100644 --- a/Spec/CDRExampleGroupSpec.mm +++ b/Spec/CDRExampleGroupSpec.mm @@ -111,6 +111,67 @@ expect(hasChildren).to(be_truthy()); }); }); + + describe(@"for a group with an invariant", ^{ + beforeEach(^{ + [group addInvariant:incompleteExample]; + NSUInteger count = group.examples.count; + expect(count).to_not(equal(0)); + }); + + it(@"should return true", ^{ + BOOL hasChildren = group.hasChildren; + expect(hasChildren).to(be_truthy()); + }); + + describe(@"and a nested group", ^{ + __block CDRExampleGroup *innerGroup; + + beforeEach(^{ + innerGroup = [[[CDRExampleGroup alloc] initWithText:groupText] autorelease]; + [group add:innerGroup]; + }); + + it(@"should return true for the inner group", ^{ + BOOL hasChildren = innerGroup.hasChildren; + expect(hasChildren).to(be_truthy()); + }); + }); + }); + + describe(@"for a group with a nested group", ^{ + __block CDRExampleGroup *innerGroup; + + beforeEach(^{ + innerGroup = [[[CDRExampleGroup alloc] initWithText:groupText] autorelease]; + [group add:innerGroup]; + NSUInteger count = group.examples.count; + expect(count).to_not(equal(0)); + }); + + it(@"should return true", ^{ + BOOL hasChildren = group.hasChildren; + expect(hasChildren).to(be_truthy()); + }); + + it(@"should return false for the inner group", ^{ + BOOL hasChildren = innerGroup.hasChildren; + expect(hasChildren).to(be_falsy()); + }); + + describe(@"and an invariant", ^{ + __block CDRExampleGroup *innerGroup; + + beforeEach(^{ + [group addInvariant:incompleteExample]; + }); + + it(@"should return true for the inner group", ^{ + BOOL hasChildren = innerGroup.hasChildren; + expect(hasChildren).to(be_truthy()); + }); + }); + }); }); describe(@"isFocused", ^{ diff --git a/Spec/SpecSpec.mm b/Spec/SpecSpec.mm index 1d46cf96..60dc89ab 100644 --- a/Spec/SpecSpec.mm +++ b/Spec/SpecSpec.mm @@ -278,21 +278,21 @@ void expectFailure(CDRSpecBlock block) { afterEach(^{ it(@"should run the invariant", ^{ - expect(ran).to(be_truthy); + expect(ran).to(be_truthy()); }); }); }); afterEach(^{ it(@"should run the invariant", ^{ - expect(ran).to(be_truthy); + expect(ran).to(be_truthy()); }); }); }); afterEach(^{ it(@"should run the invariant", ^{ - expect(ran).to(be_truthy); + expect(ran).to(be_truthy()); }); }); }); @@ -309,18 +309,18 @@ void expectFailure(CDRSpecBlock block) { invariant(@"invariant tries to do the impossible", ^{ expectFailure(^{ tried = YES; - expect(true).to(be_falsy); + expect(true).to(be_falsy()); ran = YES; }); }); afterEach(^{ it(@"should run the invariant", ^{ - expect(tried).to(be_truthy); + expect(tried).to(be_truthy()); }); it(@"should not complete running the invariant", ^{ - expect(ran).to(be_falsy); + expect(ran).to(be_falsy()); }); }); }); From e18c06c8c9735e3629de1061542bdd0b29ce74bb Mon Sep 17 00:00:00 2001 From: James Cooper Date: Thu, 30 Oct 2014 11:47:14 -0400 Subject: [PATCH 4/8] James C - made invariants not fire in pending blocks, nor count towards being pending --- Source/CDRExample.m | 2 +- Source/CDRExampleGroup.m | 48 +++++++++++++++++++++++-------------- Spec/CDRExampleGroupSpec.mm | 45 +++++++++++++++++++++++++++------- Spec/SpecSpec.mm | 6 +++++ 4 files changed, 73 insertions(+), 28 deletions(-) diff --git a/Source/CDRExample.m b/Source/CDRExample.m index 0565c833..d47f11c0 100644 --- a/Source/CDRExample.m +++ b/Source/CDRExample.m @@ -31,7 +31,7 @@ - (void)dealloc { } - (id)copy { - return [[[CDRExample alloc] initWithText:text_ andBlock:block_] autorelease]; + return [[CDRExample alloc] initWithText:text_ andBlock:block_]; } #pragma mark CDRExampleBase diff --git a/Source/CDRExampleGroup.m b/Source/CDRExampleGroup.m index 5a49552a..2fa65847 100644 --- a/Source/CDRExampleGroup.m +++ b/Source/CDRExampleGroup.m @@ -1,6 +1,5 @@ #import "CDRExampleGroup.h" #import "CDRReportDispatcher.h" -#import "CDRExample.h" @interface CDRExampleGroup (Private) - (void)startObservingExamples; @@ -63,12 +62,6 @@ - (CDRSpecBlock)subjectActionBlock { - (void)add:(CDRExampleBase *)example { example.parent = self; [examples_ addObject:example]; - - if ([example isKindOfClass:[CDRExampleGroup class]]) { - for (id inv in invariants_) { - [(CDRExampleGroup*)example addInvariant:[inv copy]]; - } - } } - (void)addBefore:(CDRSpecBlock)block { @@ -84,16 +77,10 @@ - (void)addAfter:(CDRSpecBlock)block { } - (void)addInvariant:(CDRExampleBase *)inv { - for (id example in examples_) { - if ([example isKindOfClass:[CDRExampleGroup class]]) { - [example addInvariant:[inv copy]]; - } - } - - CDRExampleBase * exampleInstance = [inv copy]; - exampleInstance.parent = self; - [examples_ addObject: exampleInstance]; - [invariants_ addObject: [inv copy]]; + CDRExampleBase * invCopy = [inv copy]; + invCopy.parent = self; + [invariants_ addObject: invCopy]; + [invCopy release]; } #pragma mark CDRExampleBase @@ -118,7 +105,10 @@ - (float)progress { for (CDRExampleBase *example in examples_) { aggregateProgress += [example progress]; } - return aggregateProgress / [examples_ count]; + for (CDRExampleBase *example in invariants_) { + aggregateProgress += [example progress]; + } + return aggregateProgress / ([examples_ count] + [invariants_ count]); } @@ -129,12 +119,17 @@ - (void)runWithDispatcher:(CDRReportDispatcher *)dispatcher { userInfo:nil] raise]; } + [self collectInvariants]; + [dispatcher runWillStartExampleGroup:self]; [startDate_ release]; startDate_ = [[NSDate alloc] init]; [self startObservingExamples]; [examples_ makeObjectsPerformSelector:@selector(runWithDispatcher:) withObject:dispatcher]; + if ([examples_ count] > 0) { + [invariants_ makeObjectsPerformSelector:@selector(runWithDispatcher:) withObject:dispatcher]; + } [self stopObservingExamples]; [endDate_ release]; @@ -196,12 +191,29 @@ - (void)startObservingExamples { for (id example in examples_) { [example addObserver:self forKeyPath:@"state" options:0 context:NULL]; } + for (id example in invariants_) { + [example addObserver:self forKeyPath:@"state" options:0 context:NULL]; + } } - (void)stopObservingExamples { for (id example in examples_) { [example removeObserver:self forKeyPath:@"state"]; } + for (id example in invariants_) { + [example removeObserver:self forKeyPath:@"state"]; + } +} + +- (void)collectInvariants { + //Because of recursive call order for runWithDispatcher: all grandparent invariants have been propagated down to parent by the time [self collectInvariants] is called + //So no recursive call is necessary + if ([self.parent isKindOfClass:[CDRExampleGroup class]]) { + for (id inv in ((CDRExampleGroup*)self.parent)->invariants_) { + [self addInvariant: ((CDRExampleBase*)inv)]; + } + } + } @end diff --git a/Spec/CDRExampleGroupSpec.mm b/Spec/CDRExampleGroupSpec.mm index e6775c2b..ddeacb03 100644 --- a/Spec/CDRExampleGroupSpec.mm +++ b/Spec/CDRExampleGroupSpec.mm @@ -116,12 +116,12 @@ beforeEach(^{ [group addInvariant:incompleteExample]; NSUInteger count = group.examples.count; - expect(count).to_not(equal(0)); + expect(count).to(equal(0)); }); - it(@"should return true", ^{ + it(@"should return false", ^{ BOOL hasChildren = group.hasChildren; - expect(hasChildren).to(be_truthy()); + expect(hasChildren).to(be_falsy()); }); describe(@"and a nested group", ^{ @@ -132,10 +132,15 @@ [group add:innerGroup]; }); - it(@"should return true for the inner group", ^{ - BOOL hasChildren = innerGroup.hasChildren; + it(@"should return true for the group", ^{ + BOOL hasChildren = group.hasChildren; expect(hasChildren).to(be_truthy()); }); + + it(@"should return false for the inner group", ^{ + BOOL hasChildren = innerGroup.hasChildren; + expect(hasChildren).to(be_falsy()); + }); }); }); @@ -160,15 +165,13 @@ }); describe(@"and an invariant", ^{ - __block CDRExampleGroup *innerGroup; - beforeEach(^{ [group addInvariant:incompleteExample]; }); - it(@"should return true for the inner group", ^{ + it(@"should return false for the inner group", ^{ BOOL hasChildren = innerGroup.hasChildren; - expect(hasChildren).to(be_truthy()); + expect(hasChildren).to(be_falsy()); }); }); }); @@ -282,6 +285,17 @@ }); }); + describe(@"for a pending block", ^{ + beforeEach(^{ + blockInvocationCount = 0; + [group runWithDispatcher:dispatcher]; + }); + + it(@"should not be called", ^{ + blockInvocationCount should equal(0); + }); + }); + describe(@"for nested block", ^{ beforeEach(^{ blockInvocationCount = 0; @@ -651,6 +665,19 @@ expect(progress).to(be_close_to(2.0 / 3.0)); }); }); + + describe(@"when the group contains an invariant", ^{ + beforeEach(^{ + [group add:passingExample]; + [group addInvariant:incompleteExample]; + [passingExample runWithDispatcher:dispatcher]; + }); + + it(@"should count the invariant like a regular example", ^{ + float progress = group.progress; + expect(progress).to(be_close_to(1.0 / 2.0)); + }); + }); }); describe(@"message", ^{ diff --git a/Spec/SpecSpec.mm b/Spec/SpecSpec.mm index 60dc89ab..a785cd78 100644 --- a/Spec/SpecSpec.mm +++ b/Spec/SpecSpec.mm @@ -276,6 +276,8 @@ void expectFailure(CDRSpecBlock block) { y = -2; }); + it(@"force the invariant", ^{expect(true).to(be_truthy());}); + afterEach(^{ it(@"should run the invariant", ^{ expect(ran).to(be_truthy()); @@ -290,6 +292,8 @@ void expectFailure(CDRSpecBlock block) { }); }); + context(@"in a pending context block should be pending", ^{}); + afterEach(^{ it(@"should run the invariant", ^{ expect(ran).to(be_truthy()); @@ -314,6 +318,8 @@ void expectFailure(CDRSpecBlock block) { }); }); + it(@"force the invariant", ^{expect(true).to(be_truthy());}); + afterEach(^{ it(@"should run the invariant", ^{ expect(tried).to(be_truthy()); From 74ffaa43aeb9ea103c05319d4da35f0e70d2e6f8 Mon Sep 17 00:00:00 2001 From: James Cooper Date: Thu, 30 Oct 2014 15:12:17 -0400 Subject: [PATCH 5/8] James C - Added test to ensure inheritance of invariants is transitive --- Spec/CDRExampleGroupSpec.mm | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Spec/CDRExampleGroupSpec.mm b/Spec/CDRExampleGroupSpec.mm index ddeacb03..66008ef5 100644 --- a/Spec/CDRExampleGroupSpec.mm +++ b/Spec/CDRExampleGroupSpec.mm @@ -296,20 +296,22 @@ }); }); - describe(@"for nested block", ^{ + describe(@"for nested blocks", ^{ beforeEach(^{ blockInvocationCount = 0; + CDRExampleGroup * innerInnerGroup = [[[CDRExampleGroup alloc] initWithText:groupText] autorelease]; + [innerInnerGroup add:errorExample]; + [innerInnerGroup add:failingExample]; CDRExampleGroup * innerGroup = [[[CDRExampleGroup alloc] initWithText:groupText] autorelease]; - [innerGroup add:errorExample]; - [innerGroup add:failingExample]; + [innerGroup add:innerInnerGroup]; [group add:passingExample]; [group add:innerGroup]; [group runWithDispatcher:dispatcher]; }); - it(@"should be called twice, once as it pases through each block", ^{ - blockInvocationCount should equal(2); + it(@"should be called three times, once as it pases through each block", ^{ + blockInvocationCount should equal(3); }); }); }); From 7b7fd643911e0ef74e136b707248da36d3597909 Mon Sep 17 00:00:00 2001 From: James Cooper Date: Fri, 31 Oct 2014 13:16:03 -0400 Subject: [PATCH 6/8] James C - added a useful invariant just as an example --- Spec/CDRExampleGroupSpec.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Spec/CDRExampleGroupSpec.mm b/Spec/CDRExampleGroupSpec.mm index 66008ef5..1ed9ae97 100644 --- a/Spec/CDRExampleGroupSpec.mm +++ b/Spec/CDRExampleGroupSpec.mm @@ -27,6 +27,11 @@ errorExample = [[[CDRExample alloc] initWithText:@"I should raise an error" andBlock:^{ @throw @"wibble"; }] autorelease]; nonFocusedExample = [[[CDRExample alloc] initWithText:@"I should not be focused" andBlock:^{}] autorelease]; }); + + invariant(@"progress between 0 and 1", ^{ + group.progress should be_greater_than_or_equal_to(0); + group.progress should be_less_than_or_equal_to(1); + }); describe(@"runWithDispatcher:", ^{ beforeEach(^{ From e5d76d309297d11cf9c7d8937537e5ad3f429df2 Mon Sep 17 00:00:00 2001 From: James Cooper Date: Wed, 5 Nov 2014 10:56:41 -0500 Subject: [PATCH 7/8] James C - added invariant synonyms, invariant subtypes (finv and ximv) but have not made invariant focusing work yet --- Source/CDRExample.m | 4 ++- Source/CDRSpec.m | 30 +++++++++++++--- Source/Headers/Public/CDRSpec.h | 8 ++++- Spec/CDRExampleGroupSpec.mm | 12 +++++++ Spec/SpecSpec.mm | 63 +++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 7 deletions(-) diff --git a/Source/CDRExample.m b/Source/CDRExample.m index d47f11c0..a197f2d2 100644 --- a/Source/CDRExample.m +++ b/Source/CDRExample.m @@ -31,7 +31,9 @@ - (void)dealloc { } - (id)copy { - return [[CDRExample alloc] initWithText:text_ andBlock:block_]; + CDRExample* example = [[CDRExample alloc] initWithText:text_ andBlock:block_]; + example.focused = self.focused; + return example; } #pragma mark CDRExampleBase diff --git a/Source/CDRSpec.m b/Source/CDRSpec.m index c02350b9..caad91dd 100644 --- a/Source/CDRSpec.m +++ b/Source/CDRSpec.m @@ -18,13 +18,17 @@ void afterEach(CDRSpecBlock block) { [CDR_currentSpec.currentGroup addAfter:block]; } -void invariant(NSString *text, CDRSpecBlock block) { - NSString * invName = [NSString stringWithFormat:@"%@ (invariant in %@)", text, CDR_currentSpec.rootGroup]; - [CDR_currentSpec.currentGroup addInvariant:[CDRExample exampleWithText:invName andBlock:block]]; +#define with_stack_address(b) \ +((b.stackAddress = CDRCallerStackAddress()), b) + +CDRExample * invariant(NSString *text, CDRSpecBlock block) { + NSString * invName = [NSString stringWithFormat:@"should always %@", text]; + CDRExample * example = [CDRExample exampleWithText:invName andBlock:block]; + [CDR_currentSpec.currentGroup addInvariant:example]; + return with_stack_address(example); } -#define with_stack_address(b) \ - ((b.stackAddress = CDRCallerStackAddress()), b) +CDRExample* (*it_should_always)(NSString *, CDRSpecBlock) = &invariant; CDRExampleGroup * describe(NSString *text, CDRSpecBlock block) { CDRExampleGroup *group = nil; @@ -79,6 +83,14 @@ void subjectAction(CDRSpecBlock block) { return with_stack_address(example); } +CDRExample * xinvariant(NSString *text, CDRSpecBlock block) { + NSString * invName = [NSString stringWithFormat:@"should always %@", text]; + CDRExample *example = [CDRExample exampleWithText:invName andBlock:PENDING]; + return with_stack_address(example); +} + +CDRExample* (*xit_should_always)(NSString *, CDRSpecBlock) = &xinvariant; + #pragma mark - Focused CDRExampleGroup * fdescribe(NSString *text, CDRSpecBlock block) { @@ -95,6 +107,14 @@ void subjectAction(CDRSpecBlock block) { return with_stack_address(example); } +CDRExample * finvariant(NSString *text, CDRSpecBlock block) { + CDRExample *example = invariant(text, block); + example.focused = YES; + return with_stack_address(example); +} + +CDRExample * (*fit_should_always)(NSString *, CDRSpecBlock) = &finvariant; + void fail(NSString *reason) { [[CDRSpecFailure specFailureWithReason:[NSString stringWithFormat:@"Failure: %@", reason]] raise]; } diff --git a/Source/Headers/Public/CDRSpec.h b/Source/Headers/Public/CDRSpec.h index ae210b79..d4f0812f 100644 --- a/Source/Headers/Public/CDRSpec.h +++ b/Source/Headers/Public/CDRSpec.h @@ -18,7 +18,9 @@ extern "C" { #endif void beforeEach(CDRSpecBlock); void afterEach(CDRSpecBlock); -void invariant(NSString *, CDRSpecBlock); + +CDRExample * invariant(NSString *, __nullable CDRSpecBlock); +extern CDRExample * __nonnull (*__nonnull it_should_always)(NSString *, __nullable CDRSpecBlock); CDRExampleGroup * describe(NSString *, __nullable CDRSpecBlock); extern CDRExampleGroup* __nonnull (*__nonnull context)(NSString *, __nullable CDRSpecBlock); @@ -29,10 +31,14 @@ CDRExampleGroup * xdescribe(NSString *, __nullable CDRSpecBlock); extern CDRExampleGroup* __nonnull (*__nonnull xcontext)(NSString *, __nullable CDRSpecBlock); void subjectAction(CDRSpecBlock); CDRExample * xit(NSString *, __nullable CDRSpecBlock); +CDRExample * xinvariant(NSString *, __nullable CDRSpecBlock); +extern CDRExample* __nonnull (*__nonnull xit_should_always)(NSString *, __nullable CDRSpecBlock); CDRExampleGroup * fdescribe(NSString *, __nullable CDRSpecBlock); extern CDRExampleGroup* __nonnull (*__nonnull fcontext)(NSString *, __nullable CDRSpecBlock); CDRExample * fit(NSString *, __nullable CDRSpecBlock); +CDRExample * finvariant(NSString *, __nullable CDRSpecBlock); +extern CDRExample* __nonnull (*__nonnull fit_should_always)(NSString *, __nullable CDRSpecBlock); void fail(NSString *); #ifdef __cplusplus diff --git a/Spec/CDRExampleGroupSpec.mm b/Spec/CDRExampleGroupSpec.mm index 1ed9ae97..07679e0d 100644 --- a/Spec/CDRExampleGroupSpec.mm +++ b/Spec/CDRExampleGroupSpec.mm @@ -229,6 +229,18 @@ expect([group hasFocusedExamples]).to(be_truthy()); }); }); + + context(@"and has at least one focused invariant", ^{ + beforeEach(^{ + [group add:failingExample]; + [group addInvariant:passingExample]; + passingExample.focused = YES; + }); + + it(@"should return true", ^{ + expect([group hasFocusedExamples]).to(be_truthy()); + }); + }); context(@"and has at least one focused group", ^{ beforeEach(^{ diff --git a/Spec/SpecSpec.mm b/Spec/SpecSpec.mm index a785cd78..12f56203 100644 --- a/Spec/SpecSpec.mm +++ b/Spec/SpecSpec.mm @@ -96,6 +96,14 @@ void expectFailure(CDRSpecBlock block) { it(@"should also be pending", nil); xit(@"should also be pending (xit)", ^{}); + describe(@"invariants", ^{ + it_should_always(@"be pending", PENDING); + it_should_always(@"also be pending", nil); + xit_should_always(@"also be pending (xit_should_always)", ^{}); + + it(@"should force invariants", ^{}); + }); + describe(@"described specs should be pending", PENDING); describe(@"described specs should also be pending", nil); xdescribe(@"xdescribed specs should be pending", ^{}); @@ -331,6 +339,61 @@ void expectFailure(CDRSpecBlock block) { }); }); +describe(@"an invariant and a subject action block", ^{ + __block NSInteger x; + __block NSInteger y; + __block BOOL ran; + + beforeEach(^{ + x = 5; + y = 0; + ran = NO; + }); + + invariant(@"invariant equates the two variables", ^{ + expect(x).to(equal(y)); + ran = YES; + }); + + subjectAction(^{ y = 5; }); + + context(@"in a context block", ^{ + beforeEach(^{ + x = 5; + y = 0; + }); + + context(@"in a nested context block", ^{ + beforeEach(^{ + x = 5; + y = 0; + }); + + it(@"force the invariant", ^{expect(true).to(be_truthy());}); + + afterEach(^{ + it(@"should run the invariant after the subject action", ^{ + expect(ran).to(be_truthy()); + }); + }); + }); + + afterEach(^{ + it(@"should run the invariant after the subject action", ^{ + expect(ran).to(be_truthy()); + }); + }); + }); + + context(@"in a pending context block should be pending", ^{}); + + afterEach(^{ + it(@"should run the invariant after the subject action", ^{ + expect(ran).to(be_truthy()); + }); + }); +}); + SPEC_END From 2ec625a79d547ed186bfec0305b3490eb0b7cac9 Mon Sep 17 00:00:00 2001 From: James Cooper Date: Wed, 5 Nov 2014 11:21:50 -0500 Subject: [PATCH 8/8] James C - invariants now treat focus properly --- Source/CDRExampleGroup.m | 25 ++++++++++++++++++++++++- Source/CDRSpec.m | 2 +- Spec/CDRExampleGroupSpec.mm | 26 ++++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/Source/CDRExampleGroup.m b/Source/CDRExampleGroup.m index 2fa65847..01c11c29 100644 --- a/Source/CDRExampleGroup.m +++ b/Source/CDRExampleGroup.m @@ -145,7 +145,12 @@ - (void)runWithDispatcher:(CDRReportDispatcher *)dispatcher { - (BOOL)hasFocusedExamples { if (self.isFocused) { return YES; + + } else if ([self hasFocusedInvariant]) { + [self forceFocus]; + return YES; } + for (CDRExampleBase *example in examples_) { if ([example hasFocusedExamples]) { return YES; @@ -213,7 +218,25 @@ - (void)collectInvariants { [self addInvariant: ((CDRExampleBase*)inv)]; } } - +} + +- (BOOL)hasFocusedInvariant { + for (CDRExampleBase *example in invariants_) { + if ([example isFocused]) { + return YES; + } + } + return NO; +} + +//An invariant is focused, so we need to propagate focus *down* the tree +- (void)forceFocus { + self.focused = YES; + for (id child in examples_) { + if ([child isKindOfClass:[CDRExampleGroup class]]) { + [child forceFocus]; + } + } } @end diff --git a/Source/CDRSpec.m b/Source/CDRSpec.m index caad91dd..fdb09f39 100644 --- a/Source/CDRSpec.m +++ b/Source/CDRSpec.m @@ -19,7 +19,7 @@ void afterEach(CDRSpecBlock block) { } #define with_stack_address(b) \ -((b.stackAddress = CDRCallerStackAddress()), b) + ((b.stackAddress = CDRCallerStackAddress()), b) CDRExample * invariant(NSString *text, CDRSpecBlock block) { NSString * invName = [NSString stringWithFormat:@"should always %@", text]; diff --git a/Spec/CDRExampleGroupSpec.mm b/Spec/CDRExampleGroupSpec.mm index 07679e0d..8dda8a2b 100644 --- a/Spec/CDRExampleGroupSpec.mm +++ b/Spec/CDRExampleGroupSpec.mm @@ -231,15 +231,37 @@ }); context(@"and has at least one focused invariant", ^{ + __block CDRExampleGroup *anotherInnerGroup; + __block CDRExampleGroup *innerGroup; + beforeEach(^{ - [group add:failingExample]; - [group addInvariant:passingExample]; passingExample.focused = YES; + [group addInvariant:passingExample]; + [group add:failingExample]; + + innerGroup = [[CDRExampleGroup alloc] initWithText:@"Inner group"]; + [group add:innerGroup]; + + anotherInnerGroup = [[CDRExampleGroup alloc] initWithText:@"Another inner group"]; + [innerGroup add:anotherInnerGroup]; + + [innerGroup release]; + [anotherInnerGroup release]; }); it(@"should return true", ^{ expect([group hasFocusedExamples]).to(be_truthy()); }); + + it(@"should have a focused inner group", ^{ + [group hasFocusedExamples]; + expect([innerGroup hasFocusedExamples]).to(be_truthy()); + }); + + it(@"should have a focused inner inner group", ^{ + [group hasFocusedExamples]; + expect([anotherInnerGroup hasFocusedExamples]).to(be_truthy()); + }); }); context(@"and has at least one focused group", ^{