Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add matcher to check whether an assertion was failed #248

Merged
merged 40 commits into from Oct 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a97053a
Added throwAssertion() matcher, which uses @mattgallagher's CwlPrecon…
abbeycode Feb 4, 2016
03184ab
Updated to later CwlPreconditionTesting revision
abbeycode Feb 11, 2016
72c7d93
Ignoring xccheckout and xcscmblueprint files
abbeycode Feb 11, 2016
998353b
Fixed compiler warning by defining the closure type
abbeycode Feb 11, 2016
1560d28
Synced upstream changes
abbeycode Feb 11, 2016
05b7670
Moved new ThrowAssertion and ThrowAssertionTest files into correct (u…
abbeycode Feb 11, 2016
6ff496b
Updated framework reference to be relative to built products dir
abbeycode Feb 11, 2016
917be3b
Switched to file-inclusion rather than framework dependency. OS X bui…
abbeycode Feb 11, 2016
0b46d0d
Fixed iOS build issue by excluding non-x86_64 platforms from being ab…
abbeycode Feb 12, 2016
1920a0d
Excluded throwAssertion() from tvOS, since it isn't currently working
abbeycode Feb 12, 2016
1c32a83
Removed unnecessary whitespace change
abbeycode Feb 12, 2016
2f35015
Excluded Linux from throwAssertion()
abbeycode Feb 18, 2016
597be6d
Synced upstream changes
abbeycode Feb 18, 2016
4d29c6d
Excluded ThrowAssertionTest from Linux
abbeycode Feb 18, 2016
53cfbd9
Movies #if/#endif outside the ThrowAssertionTest class, to remove it …
abbeycode Feb 18, 2016
8da151e
Updated tvOS target and tests for throwAssertion() matcher to use Cwl…
abbeycode Feb 18, 2016
8885267
Trying the ObjC runtime check for ThrowAssertionTest to the top of th…
abbeycode Feb 18, 2016
c4b9d66
Synced upstream changes
abbeycode Feb 29, 2016
e07d867
Moved ThrowAssertionTest.swift into the correct path
abbeycode Feb 29, 2016
03fb2c1
Migrated CwlPreconditionTesting from submodule to source-inclusion, a…
abbeycode Feb 29, 2016
dbc2540
Added throwAssertion() matcher documentation to readme
abbeycode Feb 29, 2016
a4b73ce
Added more info to fatalError raised for incompatible client binaries
abbeycode Feb 29, 2016
3af3652
Removed submodule checkout from build scripts
abbeycode Feb 29, 2016
fb98d0c
Edited readme
abbeycode Feb 29, 2016
5b597ec
Added newline after #endif
abbeycode Mar 14, 2016
c9ee9d3
Updated CONTRIBUTING.md to include instructions on how to run the swi…
abbeycode Mar 15, 2016
2521ac5
Moved new Lib directory inside of Sources, so Swift Package Manager w…
abbeycode Mar 15, 2016
b761519
Removed throwAssertion() support from tvOS
abbeycode Mar 18, 2016
bc192d5
Changed closure signature in test case to fix non-SwiftPM builds on C…
abbeycode Mar 20, 2016
75210ac
Updated closure signature again, making it Void? instead of Void, sti…
abbeycode Mar 20, 2016
a083ef6
Updated testPositiveMatch to (hopefully) both (a) build on Circle's n…
abbeycode Mar 20, 2016
bcb5adb
Removed ThrowAssertionTest from tvOS test target
abbeycode Mar 20, 2016
3174365
Breaking out the closure didn't actually get rid of the warning in Xc…
abbeycode Mar 20, 2016
ee16380
Updated CONTRIBUTING file to instruct use of swiftenv instead of inst…
abbeycode Mar 20, 2016
47e12cf
Updated package to exclude the Lib directory and the ThrowAssertion file
abbeycode Mar 20, 2016
4eb7ce7
Removed ThrowAssertionTest from package, to see if it fixes the Swift…
abbeycode Mar 20, 2016
e8e8f10
Synced upstream changes
abbeycode Mar 24, 2016
9cf0432
Reverted change to tvOS scheme
abbeycode Mar 28, 2016
d92b92b
Synced upstream changes
abbeycode May 6, 2016
72ee85d
Merged upstream changes, including adding the -fobjc-arc setting to n…
abbeycode Jun 21, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
@@ -1,5 +1,7 @@
.DS_Store
xcuserdata/
**/xcuserdata/*
**/*.xccheckout
**/*.xcscmblueprint
build/
.idea
DerivedData/
Expand Down
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Expand Up @@ -46,6 +46,14 @@ Be sure to include in your issue:

- Use `Nimble.xcodeproj` to work on Nimble.

## Running the Swift Package Manager tests

1. Install `swiftenv` by running a line from the build script (`.travis.yml`):

eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/02090c7ede5a637b76e6df1710e83cd0bbe7dcdf/swiftenv-install.sh)"

2. Run `./test swiftpm`

## Pull Requests

- Nothing is trivial. Submit pull requests for anything: typos,
Expand Down
114 changes: 114 additions & 0 deletions Nimble.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Package.swift
@@ -1,5 +1,8 @@
import PackageDescription

let package = Package(
name: "Nimble"
name: "Nimble",
exclude: ["Sources/Lib",
"Sources/Nimble/Matchers/ThrowAssertion.swift",
"Tests/Nimble/Matchers/ThrowAssertionTest.swift"]
)
25 changes: 21 additions & 4 deletions README.md
Expand Up @@ -681,6 +681,23 @@ expect(actual).to(beFalse());
expect(actual).to(beNil());
```

## Swift Assertions

If you're using Swift, you can use the `throwAssertion` matcher to check if an assertion is thrown (e.g. `fatalError()`). This is made possible by [@mattgallagher](https://github.com/mattgallagher)'s [CwlPreconditionTesting](https://github.com/mattgallagher/CwlPreconditionTesting) library

```swift
// Swift

// Passes if somethingThatThrows() fails an assertion, such as calling fatalError():
expect{ try somethingThatThrows() }.to(throwAssertion())
```

Notes:

* This feature is only available in Swift.
* It is only supported for `x86_64` binaries, meaning _you cannot run this matcher on iOS devices, only simulators_.
* The tvOS simulator is supported, but using a different mechanism, requiring you to turn off the `Debug executable` scheme setting for your tvOS scheme's Test configuration.

## Swift Error Handling

If you're using Swift 2.0+, you can use the `throwError` matcher to check if an error is thrown.
Expand Down Expand Up @@ -948,10 +965,10 @@ expect(actual).to(satisfyAnyOf(beLessThan(@10), beGreaterThan(@20)))
expect(@6).to(satisfyAnyOf(equal(@2), equal(@3), equal(@4), equal(@5), equal(@6), equal(@7)))
```

Note: This matcher allows you to chain any number of matchers together. This provides flexibility,
but if you find yourself chaining many matchers together in one test, consider whether you
could instead refactor that single test into multiple, more precisely focused tests for
better coverage.
Note: This matcher allows you to chain any number of matchers together. This provides flexibility,
but if you find yourself chaining many matchers together in one test, consider whether you
could instead refactor that single test into multiple, more precisely focused tests for
better coverage.

# Writing Your Own Matchers

Expand Down
@@ -0,0 +1,29 @@
//
// CwlCatchException.h
// CwlCatchException
//
// Created by Matt Gallagher on 2016/01/10.
// Copyright © 2016 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved.
//
// Permission to use, copy, modify, and/or distribute this software for any purpose with or without
// fee is hereby granted, provided that the above copyright notice and this permission notice
// appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
// SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
// OF THIS SOFTWARE.
//

#import <Foundation/Foundation.h>

//! Project version number for CwlCatchException.
FOUNDATION_EXPORT double CwlCatchExceptionVersionNumber;

//! Project version string for CwlCatchException.
FOUNDATION_EXPORT const unsigned char CwlCatchExceptionVersionString[];

__attribute__((visibility("hidden")))
NSException* __nullable catchExceptionOfKind(Class __nonnull type, __attribute__((noescape)) void (^ __nonnull inBlock)());
@@ -0,0 +1,34 @@
//
// CwlCatchException.m
// CwlAssertionTesting
//
// Created by Matt Gallagher on 2016/01/10.
// Copyright © 2016 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved.
//
// Permission to use, copy, modify, and distribute this software for any purpose with or without
// fee is hereby granted, provided that the above copyright notice and this permission notice
// appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
// SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
// OF THIS SOFTWARE.
//

#import "CwlCatchException.h"

__attribute__((visibility("hidden")))
NSException* catchExceptionOfKind(Class __nonnull type, __attribute__((noescape)) void (^ __nonnull inBlock)()) {
@try {
inBlock();
} @catch (NSException *exception) {
if ([exception isKindOfClass:type]) {
return exception;
} else {
@throw;
}
}
return nil;
}
@@ -0,0 +1,31 @@
//
// CwlCatchException.swift
// CwlAssertionTesting
//
// Created by Matt Gallagher on 2016/01/10.
// Copyright © 2016 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved.
//
// Permission to use, copy, modify, and/or distribute this software for any purpose with or without
// fee is hereby granted, provided that the above copyright notice and this permission notice
// appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
// SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
// OF THIS SOFTWARE.
//

import Foundation

// We can't simply cast to Self? in the catchInBlock method so we need this generic function wrapper to do the conversion for us. Mildly annoying.
private func catchReturnTypeConverter<T: NSException>(t: T.Type, @noescape block: () -> Void) -> T? {
return catchExceptionOfKind(t, block) as? T
}

extension NSException {
public static func catchException(@noescape block: () -> Void) -> Self? {
return catchReturnTypeConverter(self, block: block)
}
}
@@ -0,0 +1,69 @@
//
// CwlBadInstructionException.swift
// CwlPreconditionTesting
//
// Created by Matt Gallagher on 2016/01/10.
// Copyright © 2016 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved.
//
// Permission to use, copy, modify, and distribute this software for any purpose with or without
// fee is hereby granted, provided that the above copyright notice and this permission notice
// appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
// SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
// OF THIS SOFTWARE.
//

import Foundation

#if arch(x86_64)

private func raiseBadInstructionException() {
BadInstructionException().raise()
}

/// A simple NSException subclass. It's not required to subclass NSException (since the exception type is represented in the name) but this helps for identifying the exception through runtime type.
@objc public class BadInstructionException: NSException {
static var name: String = "com.cocoawithlove.BadInstruction"

init() {
super.init(name: BadInstructionException.name, reason: nil, userInfo: nil)
}

required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

/// An Objective-C callable function, invoked from the `mach_exc_server` callback function `catch_mach_exception_raise_state` to push the `raiseBadInstructionException` function onto the stack.
public class func catch_mach_exception_raise_state(exception_port: mach_port_t, exception: exception_type_t, code: UnsafePointer<mach_exception_data_type_t>, codeCnt: mach_msg_type_number_t, flavor: UnsafeMutablePointer<Int32>, old_state: UnsafePointer<natural_t>, old_stateCnt: mach_msg_type_number_t, new_state: thread_state_t, new_stateCnt: UnsafeMutablePointer<mach_msg_type_number_t>) -> kern_return_t {

// Make sure we've been given enough memory
if old_stateCnt != x86_THREAD_STATE64_COUNT || new_stateCnt.memory < x86_THREAD_STATE64_COUNT {
return KERN_INVALID_ARGUMENT
}

// Read the old thread state
var state = UnsafePointer<x86_thread_state64_t>(old_state).memory

// 1. Decrement the stack pointer
state.__rsp -= __uint64_t(sizeof(Int))

// 2. Save the old Instruction Pointer to the stack.
UnsafeMutablePointer<__uint64_t>(bitPattern: UInt(state.__rsp)).memory = state.__rip

// 3. Set the Instruction Pointer to the new function's address
var f: @convention(c) () -> Void = raiseBadInstructionException
withUnsafePointer(&f) { state.__rip = UnsafePointer<__uint64_t>($0).memory }

// Write the new thread state
UnsafeMutablePointer<x86_thread_state64_t>(new_state).memory = state
new_stateCnt.memory = x86_THREAD_STATE64_COUNT

return KERN_SUCCESS
}
}

#endif
@@ -0,0 +1,60 @@
//
// CwlCatchBadInstruction.h
// CwlPreconditionTesting
//
// Created by Matt Gallagher on 2016/01/10.
// Copyright © 2016 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved.
//
// Permission to use, copy, modify, and distribute this software for any purpose with or without
// fee is hereby granted, provided that the above copyright notice and this permission notice
// appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
// SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
// OF THIS SOFTWARE.
//

#if defined(__x86_64__)

#import <Foundation/Foundation.h>
#import <mach/mach.h>

NS_ASSUME_NONNULL_BEGIN

// The request_mach_exception_raise_t struct is passed to mach_msg which assumes its exact layout. To avoid problems with different layouts, we keep the definition in C rather than Swift.
typedef struct
{
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
int64_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[224];
} request_mach_exception_raise_t;

// The reply_mach_exception_raise_state_t struct is passed to mach_msg which assumes its exact layout. To avoid problems with different layouts, we keep the definition in C rather than Swift.
typedef struct
{
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[224];
} reply_mach_exception_raise_state_t;

extern boolean_t mach_exc_server(mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP);

NS_ASSUME_NONNULL_END

#endif
@@ -0,0 +1,49 @@
//
// CwlCatchBadInstruction.m
// CwlPreconditionTesting
//
// Created by Matt Gallagher on 2016/01/10.
// Copyright © 2016 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved.
//
// Permission to use, copy, modify, and distribute this software for any purpose with or without
// fee is hereby granted, provided that the above copyright notice and this permission notice
// appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
// SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
// OF THIS SOFTWARE.
//

#if defined(__x86_64__)

#import "CwlCatchBadInstruction.h"

// Assuming the "PRODUCT_NAME" macro is defined, this will create the name of the Swift generated header file
#define STRINGIZE_NO_EXPANSION(A) #A
#define STRINGIZE_WITH_EXPANSION(A) STRINGIZE_NO_EXPANSION(A)
#define SWIFT_INCLUDE STRINGIZE_WITH_EXPANSION(PRODUCT_NAME-Swift.h)

// Include the Swift generated header file
#import SWIFT_INCLUDE

/// A basic function that receives callbacks from mach_exc_server and relays them to the Swift implemented BadInstructionException.catch_mach_exception_raise_state.
kern_return_t catch_mach_exception_raise_state(mach_port_t exception_port, exception_type_t exception, const mach_exception_data_t code, mach_msg_type_number_t codeCnt, int *flavor, const thread_state_t old_state, mach_msg_type_number_t old_stateCnt, thread_state_t new_state, mach_msg_type_number_t *new_stateCnt) {
return [BadInstructionException catch_mach_exception_raise_state:exception_port exception:exception code:code codeCnt:codeCnt flavor:flavor old_state:old_state old_stateCnt:old_stateCnt new_state:new_state new_stateCnt:new_stateCnt];
}

// The mach port should be configured so that this function is never used.
kern_return_t catch_mach_exception_raise(mach_port_t exception_port, mach_port_t thread, mach_port_t task, exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt) {
assert(false);
return KERN_FAILURE;
}

// The mach port should be configured so that this function is never used.
kern_return_t catch_mach_exception_raise_state_identity(mach_port_t exception_port, mach_port_t thread, mach_port_t task, exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt, int *flavor, thread_state_t old_state, mach_msg_type_number_t old_stateCnt, thread_state_t new_state, mach_msg_type_number_t *new_stateCnt) {
assert(false);
return KERN_FAILURE;
}

#endif