diff --git a/.gitignore b/.gitignore index 38a2d5a4..569f38d8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,10 @@ projects/VS2010/TestCatch/_UpgradeReport_Files/ projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj.filters projects/VisualStudio/TestCatch/UpgradeLog.XML UpgradeLog.XML +projects/XCode4/iOSTest/Build/Intermediates/PrecompiledHeaders +projects/XCode4/iOSTest/Build/Products/Debug-iphonesimulator/iOSTest.app.dSYM/Contents/Resources/DWARF +projects/XCode4/iOSTest/Build projects/XCode4/CatchSelfTest/DerivedData projects/XCode4/OCTest/DerivedData -projects/XCode4/iOSTest/Build +*.pyc projects/XCode4/iOSTest/DerivedData diff --git a/README b/README deleted file mode 100644 index 9383d753..00000000 --- a/README +++ /dev/null @@ -1,7 +0,0 @@ -CATCH is an automated test framework for C, C++ and Objective-C. - -The latest stable version can be found here: https://raw.github.com/philsquared/Catch/master/single_include/catch.hpp - -For documentation see the wiki at: https://github.com/philsquared/Catch/wiki - -Maintainers: Please now work off the Integration branch and target any pull requests there. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..1835ca8d --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +![catch logo](catch-logo-small.png) + +*v1.0 build 1 (master branch)* + +# New release with significant changes + +[Please see this page for details - including some breaking changes](docs/whats-changed.md) + +## What's the Catch? + +Catch stands for C++ Automated Test Cases in Headers and is a multi-paradigm automated test framework for C++ and Objective-C (and, maybe, C). It is implemented entirely in a set of header files, but is packaged up as a single header for extra convenience. + +## How to use it +This documentation comprises these three parts: + +* [Why do we need yet another C++ Test Framework?](docs/why-catch.md) +* [Tutorial](docs/tutorial.md) - getting started +* [Reference section](docs/reference-index.md) - all the details + +The documentation will continue until morale improves + +## More +* Issues and bugs can be raised on the [Issue tracker on GitHub](https://github.com/philsquared/Catch/issues) +* For discussion or questions please use [the dedicated Google Groups forum](https://groups.google.com/forum/?fromgroups#!forum/catch-forum) diff --git a/catch-logo-small.png b/catch-logo-small.png new file mode 100644 index 00000000..a4e74e8a Binary files /dev/null and b/catch-logo-small.png differ diff --git a/docs/assertions.md b/docs/assertions.md new file mode 100644 index 00000000..114c841b --- /dev/null +++ b/docs/assertions.md @@ -0,0 +1,59 @@ +# Assertion Macros + +Most test frameworks have a large collection of assertion macros to capture all possible conditional forms (_EQUALS, _NOTEQUALS, _GREATER_THAN etc). + +Catch is different. Because it decomposes natural C-style conditional expressions most of these forms are reduced to one or two that you will use all the time. That said there are a rich set of auxilliary macros as well. We'll describe all of these here. + +Most of these macros come in two forms: + +## Natural Expressions + +The REQUIRE family of macros tests an expression and aborts the test case if it fails. +The CHECK family are equivalent but execution continues in the same test case even if the assertion fails. This is useful if you have a series of essentially orthoginal assertions and it is useful to see all the results rather than stopping at the first failure. + +**REQUIRE(** _expression_ **)** and +**CHECK(** _expression_ **)** +Evaluates the expression and records the result. If an exception is thrown it is caught, reported, and counted as a failure. These are the macros you will use most of the time + +Examples: + +```c++ +CHECK( str == "string value" ); +CHECK( thisReturnsTrue() ); +REQUIRE( i == 42 ); +``` +**REQUIRE_FALSE(** _expression_ **)** and +**CHECK_FALSE(** _expression_ **)** +Evaluates the expression and records the _logical NOT_ of the result. If an exception is thrown it is caught, reported, and counted as a failure. +(these forms exist as a workaround for the fact that ! prefixed expressions cannot be decomposed). + +Example: +```c++ +REQUIRE_FALSE( thisReturnsFalse() ); +``` + +## Exceptions + +**REQUIRE_THROWS(** _expression_ **)** and +**CHECK_THROWS(** _expression_ **)** +Expects that an exception (of any type) is be thrown during evaluation of the expression. + +**REQUIRE_THROWS_AS(** _expression_ and _exception type_ **)** and +**CHECK_THROWS_AS(** _expression_, _exception type_ **)** +Expects that an exception of the _specified type_ is thrown during evaluation of the expression. + +**REQUIRE_NOTHROW(** _expression_ **)** and +**CHECK_NOTHROW(** _expression_ **)** +Expects that no exception is thrown during evaluation of the expression. + +## Matcher expressions + +To support Matchers a slightly different form is used. Matchers will be more fully documented elsewhere. *Note that Matchers are still at early stage development and are subject to change.* + +**REQUIRE_THAT(** _lhs_, __matcher call__ **)** and +**CHECK_THAT(** _lhs_, __matcher call__ **)** + + +--- + +[Home](../README.md) \ No newline at end of file diff --git a/docs/command-line.md b/docs/command-line.md new file mode 100644 index 00000000..ce489070 --- /dev/null +++ b/docs/command-line.md @@ -0,0 +1,137 @@ +CATCH works quite nicely without any command line options at all - but for those times when you want greater control the following options are available. +Note that options are described according to the following pattern: + + ` ...`
+ ` -r, --reporter`
+ ` -b, --break`
+ ` -s, --success`
+ ` -a, --abort`
+ ` -l, --list`
+ ` -o, --out`
+ ` -n, --name`
+ ` -e, --nothrow`
+ ` -h, -?, --help`
+ + +## Specifying which tests to run + +
<test-spec> ...
+ +Test cases, wildcarded test cases, tags and tag expressions are all passed directly as arguments. Tags are distinguished by being enclosed in square brackets. + +If no test specs are supplied then all test cases, except "hidden" tests (tagged ```[hide]``` or, in the legacy case, prefixed by `'./'`) are run + +Specs must be enclosed in quotes if they contain spaces. If they do not contain spaces the quotes are optional. + +Wildcards consist of the `*` character at the beginning and/or end of test case names and can substitute for any number of any characters (including none). + +Test specs are case insensitive. + +If a spec is prefixed with `exclude:` or the `~` character then the pattern matches an exclusion. This means that tests matching the pattern are excluded from the set - even if a prior inclusion spec included them. Subsequent inclusion specs will take precendence, however. +Inclusions and exclusions are evaluated in left-to-right order. + +Test case examples: + +
thisTestOnly            Matches the test case called, 'thisTestOnly'
+"this test only"        Matches the test case called, 'this test only'
+these*                  Matches all cases starting with 'these'
+exclude:notThis         Matches all tests except, 'notThis'
+~notThis                Matches all tests except, 'notThis'
+~*private*              Matches all tests except those that contain 'private'
+a* ~ab* abc             Matches all tests that start with 'a', except those that
+                        start with 'ab', except 'abc', which is included
+
+ +Names within square brackets are interpreted as tags. +A series of tags form an AND expression wheras a comma seperated sequence forms an OR expression. e.g.: + +
[one][two],[three]
+This matches all tests tagged `[one]` and `[two]`, as well as all tests tagged `[three]` + + +
+## Choosing a reporter to use + +
-r, --reporter <reporter>
+ +A reporter is an object that formats and structures the output of running tests, and potentially summarises the results. By default a console reporter is used that writes, IDE friendly, textual output. Catch comes bundled with some alternative reporters, but more can be added in client code.
+The bundled reporters are: + +
-r console
+-r xml
+-r junit
+
+ +The JUnit reporter is an xml format that follows the structure of the JUnit XML Report ANT task, as consumed by a number of third-party tools, including Continuous Integration servers such as Hudson. If not otherwise needed, the standard XML reporter is preferred as this is a streaming reporter, whereas the Junit reporter needs to hold all its results until the end so it can write the overall results into attributes of the root node. + +
+## Breaking into the debugger +
-b, --break
+ +In some IDEs (currently XCode and Visual Studio) it is possible for Catch to break into the debugger on a test failure. This can be very helpful during debug sessions - especially when there is more than one path through a particular test. +In addition to the command line option, ensure you have built your code with the DEBUG preprocessor symbol + +
+## Showing results for successful tests +
-s, --success
+ +Usually you only want to see reporting for failed tests. Sometimes it's useful to see *all* the output (especially when you don't trust that that test you just added worked first time!). +To see successul, as well as failing, test results just pass this option. Note that each reporter may treat this option differently. The Junit reporter, for example, logs all results regardless. + +
+## Aborting after a certain number of failures +
-a, --abort
+-x, --abortx [<failure threshold>]
+
+ +If a ```REQUIRE``` assertion fails the test case aborts, but subsequent test cases are still run. +If a ```CHECK``` assertion fails even the current test case is not aborted. + +Sometimes this results in a flood of failure messages and you'd rather just see the first few. Specifying ```-a``` or ```--abort``` on its own will abort the whole test run on the first failed assertion of any kind. Use ```-x``` or ```--abortx``` followed by a number to abort after that number of assertion failures. + +
+## Listing available tests, tags or reporters +
-l, --list-tests
+-t, --list-tags
+--list-reporters
+
+ +```-l``` or ```--list-tests`` will list all registered tests, along with any tags. +If one or more test-specs have been supplied too then only the matching tests will be listed. + +```-t``` or ```--list-tags``` lists all available tags, along with the number of test cases they match. Again, supplying test specs limits the tags that match. + +```--list-reporters``` lists the available reporters. + +
+## Sending output to a file +
-o, --out <filename>
+
+ +Use this option to send all output to a file. By default output is sent to stdout (note that uses of stdout and stderr *from within test cases* are redirected and included in the report - so even stderr will effectively end up on stdout). + +
+## Naming a test run +
-n, --name <name for test run>
+ +If a name is supplied it will be used by the reporter to provide an overall name for the test run. This can be useful if you are sending to a file, for example, and need to distinguish different test runs - either from different Catch executables or runs of the same executable with different options. If not supplied the name is defaulted to the name of the executable. + +
+## Eliding assertions expected to throw +
-e, --nothrow
+ +Skips all assertions that test that an exception is thrown, e.g. ```REQUIRE_THROWS```. + +These can be a nuisance in certain debugging environments that may break when exceptions are thrown (while this is usually optional for handled exceptions, it can be useful to have enabled if you are trying to track down something unexpected). + +When running with this option any throw checking assertions are skipped so as not to contribute additional noise. Be careful if this affects the behaviour of subsequent tests. + +
+## Usage +
-h, -?, --help
+ +Prints the command line arguments to stdout + +--- + +[Home](../README.md) \ No newline at end of file diff --git a/docs/logging.md b/docs/logging.md new file mode 100644 index 00000000..1c0111a5 --- /dev/null +++ b/docs/logging.md @@ -0,0 +1,50 @@ +# Logging macros + +Additional messages can be logged during a test case. + +## Streaming macros + +All these macros allow heterogenous sequences of values to be streaming using the insertion operator (```<<```) in the same way that std::ostream, std::cout, etc support it. + +E.g.: +```c++ +INFO( "The number is " << i ); +``` + +(Note that there is no initial ```<<``` - instead the insertion sequence is placed in parentheses.) +These macros come in three forms: + +**INFO(** _message expression_ **)** + +The message is logged to a buffer, but only reported if a subsequent failure occurs within the same test case. This allows you to log contextual information in case of failures which is not shown during a successful test run. The buffer is cleared on the next successful assertion. + +**SCOPED_INFO(** _message expression_ **)** + +As INFO, but is only in effect during the current scope. If a failure occurs beyond the end of the scope the message is not logged. In a looped block the message is reset on each iteration. + +**WARN(** _message expression_ **)** + +The message is always reported. + +**FAIL(** _message expression_ **)** + +The message is reported and the test case fails. + +## Quickly capture a variable value + +**CAPTURE(** _expression_ **)** + +Sometimes you just want to log the name and value of a variable. While you can easily do this with the INFO macro, above, as a convenience the CAPTURE macro handles the stringising of the variable name for you (actually it works with any expression, not just variables). + +E.g. +```c++ +CAPTURE( theAnswer ); +``` + +This would log something like: + +
"theAnswer := 42"
+ +--- + +[Home](../README.md) \ No newline at end of file diff --git a/docs/own-main.md b/docs/own-main.md new file mode 100644 index 00000000..08692940 --- /dev/null +++ b/docs/own-main.md @@ -0,0 +1,66 @@ +# Supplying main() yourself + +The easiest way to use Catch is to let it supply ```main()``` for you and handle configuring itself from the command line. + +This is achieved by writing ```#define CATCH_CONFIG_MAIN``` before the ```#include "catch.hpp"``` in *exactly one* source file. + +Sometimes, though, you need to write your own version of main(). You can do this by writing ```#define CATCH_CONFIG_RUNNER``` instead. Now you are free to write ```main()``` as normal and call into Catch yourself manually. + +You now have a lot of flexibility - but here are three recipes to get your started: + +## Let Catch take full control of args and config + +If you just need to have code that executes before and/ or after Catch this is the simplest option. + +```c++ +#define CATCH_CONFIG_RUNNER +#include "catch.hpp" + +int main( int argc, char* const argv[] ) +{ + // global setup... + + int result = Catch::Session().run( argc, argv ); + + // global clean-up... + + return result; +} +``` + +## Amending the config + +If you still want Catch to process the command line, but you want to programatically tweak the config, you can do so in one of two ways: + +```c++ +#define CATCH_CONFIG_RUNNER +#include "catch.hpp" + +int main( int argc, char* const argv[] ) +{ + Catch::Session session; // There must be exactly once instance + + // writing to session.configData() here sets defaults + + int returnCode = session.applyCommandLine( argc, argv ); + if( returnCode != 0 ) // Indicates a command line error + return returnCode; + + // writing to session.configData() or session.Config() here + // overrides command line args + + return session.run(); +} +``` + +Take a look at the definitions of Config and ConfigData to see what you can do with them. + +To take full control of the config simply omit the call to ```applyCommandLine()```. + +## Adding your own command line options + +Catch embeds a powerful command line parser which you can also use to parse your own options out. This capability is still in active development but will be documented here when it is ready. + +--- + +[Home](../README.md) \ No newline at end of file diff --git a/docs/reference-index.md b/docs/reference-index.md new file mode 100644 index 00000000..1b85cfb4 --- /dev/null +++ b/docs/reference-index.md @@ -0,0 +1,12 @@ +These are the currently documented areas of the framework. There is more to come. + +Before looking at this material be sure to read the [tutorial](tutorial.md) + +* [Command Line](command-line.md) +* [Assertion Macros](assertions.md) +* [Logging Macros](logging.md) +* [Supplying your own main()](own-main.md) + +--- + +[Home](../README.md) \ No newline at end of file diff --git a/docs/tutorial.md b/docs/tutorial.md new file mode 100644 index 00000000..b418c61b --- /dev/null +++ b/docs/tutorial.md @@ -0,0 +1,100 @@ +# Getting Catch + +The simplest way to get Catch is to download the single header version from [http://builds.catch-lib.net](builds.catch-lib.net). Don't be put off by the word "builds" there. The single header is generated by merging a set of individual headers but it is still just normal source code in a header file. + +The full source for Catch, including test projects, documentation, and other things, is hosted on GitHub. [http://catch-lib.net](catch-lib.net) will redirect you there. + + +## Where to put it? + +Catch is header only. All you need to do is drop the file(s) somewhere reachable from your project - either in some central location you can set your header search path to find, or directly into your project tree itself! This is a particularly good option for other Open-Source projects that want to use Catch for their test suite. See [this blog entry for more on that](http://www.levelofindirection.com/journal/2011/5/27/unit-testing-in-c-and-objective-c-just-got-ridiculously-easi-1.html). + +The rest of this tutorial will assume that the Catch single-include header (or the include folder) is available unqualified - but you may need to prefix it with a folder name if necessary. + +# Writing tests + +Let's start with a really simple example. Say you have written a function to calculate factorials and now you want to test it (let's leave aside TDD for now). + +```c++ +unsigned int Factorial( unsigned int number ) { + return number <= 1 ? number : Factorial(number-1)*number; +} +``` + +To keep things simple we'll put everything in a single file. + +```c++ +#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file +#include "catch.hpp" + +unsigned int Factorial( unsigned int number ) { + return number <= 1 ? number : Factorial(number-1)*number; +} + +TEST_CASE( "Factorials are computed", "[factorial]" ) { + REQUIRE( Factorial(1) == 1 ); + REQUIRE( Factorial(2) == 2 ); + REQUIRE( Factorial(3) == 6 ); + REQUIRE( Factorial(10) == 3628800 ); +} +``` + +This will compile to a complete executable which responds to [command line arguments](command-line.md). If you just run it with no arguments it will execute all test cases (in this case there is just one), report any failures, report a summary of how many tests passed and failed and return the number of failed tests (useful for if you just want a yes/ no answer to: "did it work"). + +If you run this as written it will pass. Everything is good. Right? +Well, there is still a bug here. In fact the first version of this tutorial I posted here genuinely had the bug in! So it's not completely contrived (thanks to Daryle Walker (```@CTMacUser```) for pointing this out). + +What is the bug? Well what is the factorial of zero? +[The factorial of zero is one](http://mathforum.org/library/drmath/view/57128.html) - which is just one of those things you have to know (and remember!). + +Let's add that to the test case: + +```c++ +TEST_CASE( "Factorials are computed", "[factorial]" ) { + REQUIRE( Factorial(0) == 1 ); + REQUIRE( Factorial(1) == 1 ); + REQUIRE( Factorial(2) == 2 ); + REQUIRE( Factorial(3) == 6 ); + REQUIRE( Factorial(10) == 3628800 ); +} +``` + +Now we get a failure - something like: + +``` +Example.cpp:9: FAILED: + REQUIRE( Factorial(0) == 1 ) +with expansion: + 0 == 1 +``` + +Note that we get the actual return value of Factorial(0) printed for us (0) - even though we used a natural expression with the == operator. That let's us immediately see what the problem is. + +Let's change the factorial function to: + +```c++ +unsigned int Factorial( unsigned int number ) { + return number > 1 ? Factorial(number-1)*number : 1; +} +``` + +Now all the tests pass. + +Of course there are still more issues to do deal with. For example we'll hit problems when the return value starts to exceed the range of an unsigned int. With factorials that can happen quite quickly. You might want to add tests for such cases and decide how to handle them. We'll stop short of doing that here. + +## What did we do here? + +Although this was a simple test it's been enough to demonstrate a few things about how Catch is used. Let's take moment to consider those before we move on. + +1. All we did was ```#define``` one identifier and ```#include``` one header and we got everything - even an implementation of ```main()``` that will [respond to command line arguments](command-line.md). You can only use that ```#define``` in one implementation file, for (hopefully) obvious reasons. Once you have more than one file with unit tests in you'll just ```#include "catch.hpp"``` and go. Usually it's a good idea to have a dedicated implementation file that just has ```#define CATCH_CONFIG_MAIN``` and ```#include "catch.hpp"```. You can also provide your own implementation of main and drive Catch yourself (see [Supplying-your-own-main()](own-main.md). +2. We introduce test cases with the TEST_CASE macro. This macro takes two arguments - a hierarchical test name (forward slash separated, by convention) and a free-form description. The test name should be unique - and ideally will logically group related tests together like folders in a file system. You can run sets of tests by specifying a wildcarded test name. +3. The name and description arguments are just strings. We haven't had to declare a function or method - or explicitly register the test case anywhere. Behind the scenes a function with a generated name is defined for you, and automatically registered using static registry classes. By abstracting the function name away we can name our tests without the constraints of identifier names. +4. We write our individual test assertions using the REQUIRE macro. Rather than a separate macro for each type of condition we express the condition naturally using C/C++ syntax. Behind the scenes a simple set of expression templates captures the left-hand-side and right-hand-side of the expression so we can display the values in our test report. As we'll see later there _are_ other assertion macros - but because of this technique the number of them is drastically reduced. + +## Next steps +For more specific information see the [Reference pages](reference-index.md) + + +--- + +[Home](../README.md) \ No newline at end of file diff --git a/docs/whats-changed.md b/docs/whats-changed.md new file mode 100644 index 00000000..e3187c56 --- /dev/null +++ b/docs/whats-changed.md @@ -0,0 +1,22 @@ +## What's new in Catch + +This page has been added following quite a large (hopefully the last such) merge from the integration branch. Please read this summary through so you know what to expect (and whether any changes - breaking in some cases - will affect you). + +* Calling Catch from your own ```main()``` has changed - please review [the updated docs](own-main.md) +* The command line has changed. The biggest change is that test case names and tags should now only be supplied as primary arguments - in fact the ```-t``` option has been repurposed to mean "list tags". There are [updated docs for this too](command-line.md) +* There is a new reporter interface. If you have written a custom reporter you can use the ```LegacyReporterAdapter``` to minimise any differences. Ideally you should update to the new interface - especially as it has been designed to be more robust in the face of future changes (which should be minimal). +* The docs have moved from the wiki to the repository itself. They consist of a set of markdown files in the docs folder and are referenced directly from the README in the root. You can still read them online from GitHub. +* Lots of new goodness - more documentation for which is coming. The existing docs have been updated to account for some of the changes already (e.g. variadic macros). A quick rundown: + * Variadic macros are used, where possible, so that, e.g. you can write a ```TEST_CASE``` with just a name - or even no name at all (making it an anonymous test case). + * The hierarchical naming convention is deprecated in favour of using tags (see next) + * ```TEST_CASE```s (but not ```SECTION```s) can now be tagged by placing keywords in square brackets in the second argument - e.g.: ```TEST_CASE( "A nice name", "[tag1][tag2]")```. The old style is still supported but please consider using this new style. + * Tests can still be "hidden" using the ```./``` prefix as before, but the preferred way now is to give it the ```[hide]``` tag (hidden tests are skipped if you run the test process without specifying any test specs). + * As well as ```TEST_CASE```s and ```SECTION```s you can now also use BDD-style ```SCENARIO``` (in place of ```TEST_CASE```) and ```GIVEN```, ```WHEN``` and ```THEN``` macros (in place of ```SECTION```s). + * New command line parser. Under the hood it is a complete rewrite - now powered by a command line library that will soon be spun out as a separate project: Clara. The options themselves are largely the same but there are some notable differences (as already discussed). + * Completely overhauled output from the textual reporter (now the Console reporter). This now features a much clearer, cleaner format, including good use of indentation. + +If you find any issues please raise issue tickets on the [issue tracker on GitHub](https://github.com/philsquared/Catch/issues) as before. For general questions, comments and suggestions, though, please use the [new forums on Google Groups](https://groups.google.com/forum/?fromgroups#!forum/catch-forum). + +--- + +[Home](../README.md) \ No newline at end of file diff --git a/docs/why-catch.md b/docs/why-catch.md new file mode 100644 index 00000000..2b08edc5 --- /dev/null +++ b/docs/why-catch.md @@ -0,0 +1,42 @@ +# Why do we need yet another C++ test framework? + +Good question. For C++ there are quite a number of established frameworks, including (but not limited to), [CppUnit](http://sourceforge.net/apps/mediawiki/cppunit/index.php?title=Main_Page), [Google Test](http://code.google.com/p/googletest/), [Boost.Test](http://www.boost.org/doc/libs/1_49_0/libs/test/doc/html/index.html), [Aeryn](https://launchpad.net/aeryn), [Cute](http://r2.ifs.hsr.ch/cute), [Fructose](http://fructose.sourceforge.net/) and [many, many more](http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#C.2B.2B). Even for Objective-C there are a few, including OCUnit - which now comes bundled with XCode. + +So what does Catch bring to the party that differentiates it from these? Apart from a Catchy name, of course. + +## Key Features + +* Really easy to get started. Just download catch.hpp, #include it and you're away. +* No external dependencies. As long as you can compile C++98 and have a C++ standard library available. +* Write test cases as, self-registering, functions or methods. +* Divide test cases into sections, each of which is run in isolation (eliminates the need for fixtures!) +* Use BDD-style GIVEN-WHEN-THEN in place of test cases and sections. +* Only one core assertion macro for comparisons. Standard C/C++ operators are used for the comparison - yet the full expression is decomposed and lhs and rhs values are logged. + +## Other core features + +* Tests are named using free-form strings - no more couching names in legal identifiers. +* Tests can be tagged for easily running ad-hoc groups of tests. +* Failures can (optionally) break into the debugger on Windows and Mac. +* Output is through modular reporter objects. Basic textual and XML reporters are included. Custom reporters can easily be added. +* JUnit xml output is supported for integration with third-party tools, such as CI servers. +* A default main() function is provided (in a header), but you can supply your own for complete control (e.g. integration into your own test runner GUI). +* A command line parser is provided and can still be used if you choose to provided your own main() function. +* Catch can test itself. +* Alternative assertion macro(s) report failures but don't abort the test case +* Floating point tolerance comparisons are built in using an expressive Approx() syntax. +* Internal and friendly macros are isolated so name clashes can be managed +* Support for Matchers (early stages) + +## Objective-C-specific features + +* Automatically detects if you are using it from an Objective-C project +* Works with and without ARC with no additional configuration +* Implement test fixtures using Obj-C classes too (like OCUnit) +* Additional built in matchers that work with Obj-C types (e.g. string matchers) + +See the [tutorial](tutorial.md) to get more of a taste of using CATCH in practice + +--- + +[Home](../README.md) \ No newline at end of file diff --git a/glueHeaders.py b/glueHeaders.py deleted file mode 100644 index 11c1ab75..00000000 --- a/glueHeaders.py +++ /dev/null @@ -1,54 +0,0 @@ -import os -import sys -import re -import datetime - -includesParser = re.compile( r'\s*#include\s*"(.*)"' ) -guardParser = re.compile( r'\s*#.*_INCLUDED') -defineParser = re.compile( r'\s*#define') -commentParser1 = re.compile( r'^\s*/\*') -commentParser2 = re.compile( r'^\s*\*') -blankParser = re.compile( r'^\s*$') -seenHeaders = set([]) -rootPath = os.path.join( os.path.realpath(os.path.dirname(sys.argv[0])), 'include/' ) - -def parseFile( path, filename ): - f = open( path + filename, 'r' ) - blanks = 0 - for line in f: - m = includesParser.match( line ) - if m: - header = m.group(1) - headerPath, sep, headerFile = header.rpartition( "/" ) - if not headerFile in seenHeaders: - seenHeaders.add( headerFile ) - print "// #included from: " + header - if( headerPath == "internal" and path.endswith( "internal/" ) ): - headerPath = "" - sep = "" - if os.path.exists( path + headerPath + sep + headerFile ): - parseFile( path + headerPath + sep, headerFile ) - else: - parseFile( rootPath + headerPath + sep, headerFile ) - elif (not guardParser.match( line ) or defineParser.match( line ) ) and not commentParser1.match( line )and not commentParser2.match( line ): - if blankParser.match( line ): - blanks = blanks + 1 - else: - blanks = 0 - if blanks < 2: - print line.rstrip() - -print "/*" -print " * Generated: " + str( datetime.datetime.now() ) -print " * ----------------------------------------------------------" -print " * This file has been merged from multiple headers. Please don't edit it directly" -print " * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved." -print " *" -print " * Distributed under the Boost Software License, Version 1.0. (See accompanying" -print " * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)" -print " */" -print '#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED' -print '#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED' -parseFile( rootPath, 'catch.hpp' ) -print '#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED' -print \ No newline at end of file diff --git a/include/catch.hpp b/include/catch.hpp index 54acd738..d2ebab64 100644 --- a/include/catch.hpp +++ b/include/catch.hpp @@ -11,6 +11,7 @@ #ifdef __clang__ #pragma clang diagnostic ignored "-Wglobal-constructors" +#pragma clang diagnostic ignored "-Wvariadic-macros" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" @@ -25,6 +26,7 @@ #include "internal/catch_interfaces_exception.h" #include "internal/catch_approx.hpp" #include "internal/catch_matchers.hpp" +#include "internal/catch_compiler_capabilities.h" // These files are included here so the single_include script doesn't put them // in the conditionally compiled sections @@ -68,25 +70,44 @@ #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) -#define CATCH_INFO( msg ) INTERNAL_CATCH_MSG( msg, Catch::ResultWas::Info, Catch::ResultDisposition::ContinueOnFailure, "CATCH_INFO" ) +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( msg, Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN" ) #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( msg, Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL" ) #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( msg, Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED" ) #define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_SCOPED_INFO( msg, "CATCH_SCOPED_INFO" ) -#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_MSG( #msg " := " << msg, Catch::ResultWas::Info, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CAPTURE" ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) #define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_SCOPED_INFO( #msg " := " << msg, "CATCH_SCOPED_CAPTURE" ) -#define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) - -#define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) -#define CATCH_TEST_CASE_NORETURN( name, description ) INTERNAL_CATCH_TESTCASE_NORETURN( name, description ) -#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "Anonymous test case" ) -#define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#else + #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) +#endif +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) #define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) #define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#else +#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) +#endif +#define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" ) + // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else @@ -110,22 +131,29 @@ #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) #define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) -#define INFO( msg ) INTERNAL_CATCH_MSG( msg, Catch::ResultWas::Info, Catch::ResultDisposition::ContinueOnFailure, "INFO" ) +#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) #define WARN( msg ) INTERNAL_CATCH_MSG( msg, Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN" ) #define FAIL( msg ) INTERNAL_CATCH_MSG( msg, Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL" ) #define SUCCEED( msg ) INTERNAL_CATCH_MSG( msg, Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED" ) #define SCOPED_INFO( msg ) INTERNAL_CATCH_SCOPED_INFO( msg, "SCOPED_INFO" ) -#define CAPTURE( msg ) INTERNAL_CATCH_MSG( #msg " := " << msg, Catch::ResultWas::Info, Catch::ResultDisposition::ContinueOnFailure, "CAPTURE" ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) #define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_SCOPED_INFO( #msg " := " << msg, "SCOPED_CAPTURE" ) -#define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) - -#define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) -#define TEST_CASE_NORETURN( name, description ) INTERNAL_CATCH_TESTCASE_NORETURN( name, description ) -#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "Anonymous test case" ) -#define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#else + #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) +#endif +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) #define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) #define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) @@ -133,6 +161,18 @@ #define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#else +#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) +#endif +#define GIVEN( desc ) SECTION( " Given: " desc, "" ) +#define WHEN( desc ) SECTION( " When: " desc, "" ) +#define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) +#define THEN( desc ) SECTION( " Then: " desc, "" ) +#define AND_THEN( desc ) SECTION( " And: " desc, "" ) + using Catch::Detail::Approx; #ifdef __clang__ diff --git a/include/catch_runner.hpp b/include/catch_runner.hpp index 8f18267a..6127c36f 100644 --- a/include/catch_runner.hpp +++ b/include/catch_runner.hpp @@ -12,6 +12,8 @@ #include "internal/catch_list.hpp" #include "internal/catch_runner_impl.hpp" #include "internal/catch_test_spec.h" +#include "internal/catch_version.h" +#include "internal/catch_text.h" #include #include @@ -19,12 +21,11 @@ namespace Catch { - class Runner2 { // This will become Runner when Runner becomes Context + class Runner { public: - Runner2( Config& configWrapper ) - : m_configWrapper( configWrapper ), - m_config( configWrapper.data() ) + Runner( Ptr const& config ) + : m_config( config ) { openStream(); makeReporter(); @@ -32,31 +33,28 @@ namespace Catch { Totals runTests() { - std::vector filterGroups = m_config.filters; + std::vector filterGroups = m_config->filters(); if( filterGroups.empty() ) { TestCaseFilters filterGroup( "" ); filterGroups.push_back( filterGroup ); } - Runner context( m_configWrapper, m_reporter ); // This Runner will be renamed Context + RunContext context( m_config.get(), m_reporter ); + Totals totals; - std::vector::const_iterator it = filterGroups.begin(); - std::vector::const_iterator itEnd = filterGroups.end(); - for(; it != itEnd && !context.aborting(); ++it ) { - m_reporter->StartGroup( it->getName() ); - totals += runTestsForGroup( context, *it ); - if( context.aborting() ) - m_reporter->Aborted(); - m_reporter->EndGroup( it->getName(), totals ); + for( std::size_t i=0; i < filterGroups.size() && !context.aborting(); ++i ) { + context.testGroupStarting( filterGroups[i].getName(), i, filterGroups.size() ); + totals += runTestsForGroup( context, filterGroups[i] ); + context.testGroupEnded( filterGroups[i].getName(), totals, i, filterGroups.size() ); } return totals; } - Totals runTestsForGroup( Runner& context, const TestCaseFilters& filterGroup ) { + Totals runTestsForGroup( RunContext& context, const TestCaseFilters& filterGroup ) { Totals totals; - std::vector::const_iterator it = getRegistryHub().getTestCaseRegistry().getAllTests().begin(); - std::vector::const_iterator itEnd = getRegistryHub().getTestCaseRegistry().getAllTests().end(); + std::vector::const_iterator it = getRegistryHub().getTestCaseRegistry().getAllTests().begin(); + std::vector::const_iterator itEnd = getRegistryHub().getTestCaseRegistry().getAllTests().end(); int testsRunForGroup = 0; for(; it != itEnd; ++it ) { if( filterGroup.shouldInclude( *it ) ) { @@ -71,36 +69,31 @@ namespace Catch { } } } - if( testsRunForGroup == 0 ) - std::cerr << "\n[No test cases matched with: " << filterGroup.getName() << "]" << std::endl; + if( testsRunForGroup == 0 && !filterGroup.getName().empty() ) + m_reporter->noMatchingTestCases( filterGroup.getName() ); return totals; } private: void openStream() { - if( !m_config.stream.empty() ) - m_configWrapper.useStream( m_config.stream ); - // Open output file, if specified - if( !m_config.outputFilename.empty() ) { - m_ofs.open( m_config.outputFilename.c_str() ); + if( !m_config->getFilename().empty() ) { + m_ofs.open( m_config->getFilename().c_str() ); if( m_ofs.fail() ) { std::ostringstream oss; - oss << "Unable to open file: '" << m_config.outputFilename << "'"; + oss << "Unable to open file: '" << m_config->getFilename() << "'"; throw std::domain_error( oss.str() ); } - m_configWrapper.setStreamBuf( m_ofs.rdbuf() ); + m_config->setStreamBuf( m_ofs.rdbuf() ); } } void makeReporter() { - std::string reporterName = m_config.reporter.empty() - ? "basic" - : m_config.reporter; + std::string reporterName = m_config->getReporterName().empty() + ? "console" + : m_config->getReporterName(); - ReporterConfig reporterConfig( m_config.name, m_configWrapper.stream(), m_config.includeWhichResults == Include::SuccessfulResults, m_config ); - - m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, reporterConfig ); + m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() ); if( !m_reporter ) { std::ostringstream oss; oss << "No reporter registered with name: '" << reporterName << "'"; @@ -109,156 +102,134 @@ namespace Catch { } private: - Config& m_configWrapper; - const ConfigData& m_config; + Ptr m_config; std::ofstream m_ofs; - Ptr m_reporter; - std::set m_testsAlreadyRun; + Ptr m_reporter; + std::set m_testsAlreadyRun; }; - inline int Main( Config& configWrapper ) { - int result = 0; - try - { - Runner2 runner( configWrapper ); - - const ConfigData& config = configWrapper.data(); - - // Handle list request - if( config.listSpec != List::None ) { - List( config ); - Catch::cleanUp(); - return 0; - } - - result = static_cast( runner.runTests().assertions.failed ); - - } - catch( std::exception& ex ) { - std::cerr << ex.what() << std::endl; - result = (std::numeric_limits::max)(); - } - - Catch::cleanUp(); - return result; - } - - inline void showUsage( std::ostream& os ) { - AllOptions options; - - for( AllOptions::const_iterator it = options.begin(); it != options.end(); ++it ) { - OptionParser& opt = **it; - os << " " << opt.optionNames() << " " << opt.argsSynopsis() << "\n"; - } - os << "\nFor more detail usage please see: https://github.com/philsquared/Catch/wiki/Command-line\n" << std::endl; - } - - inline void addIndent( std::ostream& os, std::size_t indent ) { - while( indent-- > 0 ) - os << ' '; - } - - inline void recursivelyWrapLine( std::ostream& os, std::string paragraph, std::size_t columns, std::size_t indent ) { - std::size_t width = columns-indent; - std::size_t tab = 0; - std::size_t wrapPoint = width; - for( std::size_t pos = 0; pos < paragraph.size(); ++pos ) { - if( pos == width ) { - addIndent( os, indent ); - os << paragraph.substr( 0, wrapPoint ) << "\n"; - return recursivelyWrapLine( os, paragraph.substr( wrapPoint+1 ), columns, indent+tab ); - } - if( paragraph[pos] == '\t' ) { - tab = pos; - paragraph = paragraph.substr( 0, tab ) + paragraph.substr( tab+1 ); - pos--; - } - else if( paragraph[pos] == ' ' ) { - wrapPoint = pos; - } - } - addIndent( os, indent ); - os << paragraph << "\n"; - } - - inline std::string addLineBreaks( const std::string& str, std::size_t columns, std::size_t indent = 0 ) { - std::ostringstream oss; - std::string::size_type pos = 0; - std::string::size_type newline = str.find_first_of( '\n' ); - while( newline != std::string::npos ) { - std::string paragraph = str.substr( pos, newline-pos ); - recursivelyWrapLine( oss, paragraph, columns, indent ); - pos = newline+1; - newline = str.find_first_of( '\n', pos ); - } - if( pos != str.size() ) - recursivelyWrapLine( oss, str.substr( pos, str.size()-pos ), columns, indent ); - - return oss.str(); - } - - inline void showHelp( const CommandParser& parser ) { - std::string exeName = parser.exeName(); - std::string::size_type pos = exeName.find_last_of( "/\\" ); - if( pos != std::string::npos ) { - exeName = exeName.substr( pos+1 ); - } - - AllOptions options; - Options::HelpOptionParser helpOpt; - bool displayedSpecificOption = false; - for( AllOptions::const_iterator it = options.begin(); it != options.end(); ++it ) { - OptionParser& opt = **it; - if( opt.find( parser ) && opt.optionNames() != helpOpt.optionNames() ) { - displayedSpecificOption = true; - std::cout << "\n" << opt.optionNames() << " " << opt.argsSynopsis() << "\n\n" - << opt.optionSummary() << "\n\n" - - << addLineBreaks( opt.optionDescription(), 80, 2 ) << "\n" << std::endl; - } - } - - if( !displayedSpecificOption ) { - std::cout << exeName << " is a CATCH host application. Options are as follows:\n\n"; - showUsage( std::cout ); - } - } + class Session { + static bool alreadyInstantiated; + + public: - inline int Main( int argc, char* const argv[], Config& config ) { + struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; - try { - CommandParser parser( argc, argv ); - - if( Command cmd = Options::HelpOptionParser().find( parser ) ) { - if( cmd.argsCount() != 0 ) - cmd.raiseError( "Does not accept arguments" ); - - showHelp( parser ); - Catch::cleanUp(); - return 0; + Session() + : m_cli( makeCommandLineParser() ) { + if( alreadyInstantiated ) { + std::string msg = "Only one instance of Catch::Session can ever be used"; + std::cerr << msg << std::endl; + throw std::logic_error( msg ); } - - AllOptions options; - - options.parseIntoConfig( parser, config.data() ); + alreadyInstantiated = true; } - catch( std::exception& ex ) { - std::cerr << ex.what() << "\n\nUsage: ...\n\n"; - showUsage( std::cerr ); + ~Session() { Catch::cleanUp(); - return (std::numeric_limits::max)(); } - - return Main( config ); - } - - inline int Main( int argc, char* const argv[] ) { - Config config; -// !TBD: This doesn't always work, for some reason -// if( isDebuggerActive() ) -// config.useStream( "debug" ); - return Main( argc, argv, config ); - } + + void showHelp( std::string const& processName ) { + std::cout << "\nCatch v" << libraryVersion.majorVersion << "." + << libraryVersion.minorVersion << " build " + << libraryVersion.buildNumber; + if( libraryVersion.branchName != "master" ) + std::cout << " (" << libraryVersion.branchName << " branch)"; + std::cout << "\n"; + + m_cli.usage( std::cout, processName ); + std::cout << "For more detail usage please see the project docs\n" << std::endl; + } + + int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + try { + m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); + if( unusedOptionBehaviour == OnUnusedOptions::Fail ) + enforceNoUsedTokens(); + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + m_config.reset(); + } + catch( std::exception& ex ) { + std::cerr << "\nError in input:\n" + << Text( ex.what(), TextAttributes() + .setInitialIndent(2) + .setIndent(4) ) + << "\n\n"; + m_cli.usage( std::cout, m_configData.processName ); + return (std::numeric_limits::max)(); + } + return 0; + } + + void useConfigData( ConfigData const& _configData ) { + m_configData = _configData; + m_config.reset(); + } + + void enforceNoUsedTokens() const { + if( !m_unusedTokens.empty() ) { + std::vector::const_iterator + it = m_unusedTokens.begin(), + itEnd = m_unusedTokens.end(); + std::string msg; + for(; it != itEnd; ++it ) + msg += " unrecognised option: " + it->data + "\n"; + throw std::runtime_error( msg.substr( 0, msg.size()-1 ) ); + } + } + + int run( int argc, char* const argv[] ) { + + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + + int run() { + if( m_configData.showHelp ) + return 0; + + try + { + config(); // Force config to be constructed + Runner runner( m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + return static_cast( runner.runTests().assertions.failed ); + } + catch( std::exception& ex ) { + std::cerr << ex.what() << std::endl; + return (std::numeric_limits::max)(); + } + } + + Clara::CommandLine const& cli() const { + return m_cli; + } + std::vector const& unusedTokens() const { + return m_unusedTokens; + } + ConfigData& configData() { + return m_configData; + } + Config& config() { + if( !m_config ) + m_config = new Config( m_configData ); + return *m_config; + } + + private: + Clara::CommandLine m_cli; + std::vector m_unusedTokens; + ConfigData m_configData; + Ptr m_config; + }; + + bool Session::alreadyInstantiated = false; } // end namespace Catch diff --git a/include/internal/catch_approx.hpp b/include/internal/catch_approx.hpp index 128ef050..2f467a36 100644 --- a/include/internal/catch_approx.hpp +++ b/include/internal/catch_approx.hpp @@ -24,7 +24,7 @@ namespace Detail { m_value( value ) {} - Approx( const Approx& other ) + Approx( Approx const& other ) : m_epsilon( other.m_epsilon ), m_scale( other.m_scale ), m_value( other.m_value ) @@ -41,20 +41,20 @@ namespace Detail { return approx; } - friend bool operator == ( double lhs, const Approx& rhs ) { + friend bool operator == ( double lhs, Approx const& rhs ) { // Thanks to Richard Harris for his help refining this formula return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); } - friend bool operator == ( const Approx& lhs, double rhs ) { + friend bool operator == ( Approx const& lhs, double rhs ) { return operator==( rhs, lhs ); } - friend bool operator != ( double lhs, const Approx& rhs ) { + friend bool operator != ( double lhs, Approx const& rhs ) { return !operator==( lhs, rhs ); } - friend bool operator != ( const Approx& lhs, double rhs ) { + friend bool operator != ( Approx const& lhs, double rhs ) { return !operator==( rhs, lhs ); } @@ -82,7 +82,7 @@ namespace Detail { } template<> -inline std::string toString( const Detail::Approx& value ) { +inline std::string toString( Detail::Approx const& value ) { return value.toString(); } diff --git a/include/internal/catch_assertionresult.h b/include/internal/catch_assertionresult.h index 37372b67..fb230adc 100644 --- a/include/internal/catch_assertionresult.h +++ b/include/internal/catch_assertionresult.h @@ -16,9 +16,9 @@ namespace Catch { struct AssertionInfo { AssertionInfo() {} - AssertionInfo( const std::string& _macroName, - const SourceLineInfo& _lineInfo, - const std::string& _capturedExpression, + AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, ResultDisposition::Flags _resultDisposition ); std::string macroName; @@ -39,7 +39,7 @@ namespace Catch { class AssertionResult { public: AssertionResult(); - AssertionResult( const AssertionInfo& info, const AssertionResultData& data ); + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); ~AssertionResult(); bool isOk() const; @@ -48,6 +48,7 @@ namespace Catch { bool hasExpression() const; bool hasMessage() const; std::string getExpression() const; + std::string getExpressionInMacro() const; bool hasExpandedExpression() const; std::string getExpandedExpression() const; std::string getMessage() const; diff --git a/include/internal/catch_assertionresult.hpp b/include/internal/catch_assertionresult.hpp index 91853307..392270d8 100644 --- a/include/internal/catch_assertionresult.hpp +++ b/include/internal/catch_assertionresult.hpp @@ -13,22 +13,19 @@ namespace Catch { - AssertionInfo::AssertionInfo( const std::string& _macroName, - const SourceLineInfo& _lineInfo, - const std::string& _capturedExpression, + AssertionInfo::AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, ResultDisposition::Flags _resultDisposition ) : macroName( _macroName ), lineInfo( _lineInfo ), capturedExpression( _capturedExpression ), resultDisposition( _resultDisposition ) - { - if( shouldNegate( resultDisposition ) ) - capturedExpression = "!" + _capturedExpression; - } + {} AssertionResult::AssertionResult() {} - AssertionResult::AssertionResult( const AssertionInfo& info, const AssertionResultData& data ) + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) : m_info( info ), m_resultData( data ) {} @@ -58,7 +55,16 @@ namespace Catch { } std::string AssertionResult::getExpression() const { - return m_info.capturedExpression; + if( shouldNegate( m_info.resultDisposition ) ) + return "!" + m_info.capturedExpression; + else + return m_info.capturedExpression; + } + std::string AssertionResult::getExpressionInMacro() const { + if( m_info.macroName.empty() ) + return m_info.capturedExpression; + else + return m_info.macroName + "( " + m_info.capturedExpression + " )"; } bool AssertionResult::hasExpandedExpression() const { diff --git a/include/internal/catch_capture.hpp b/include/internal/catch_capture.hpp index e2919848..cf143244 100644 --- a/include/internal/catch_capture.hpp +++ b/include/internal/catch_capture.hpp @@ -10,11 +10,14 @@ #include "catch_expression_decomposer.hpp" #include "catch_expressionresult_builder.h" +#include "catch_message.h" #include "catch_interfaces_capture.h" #include "catch_debugger.hpp" #include "catch_context.h" #include "catch_common.h" #include "catch_interfaces_registry_hub.h" +#include "internal/catch_compiler_capabilities.h" + #include namespace Catch { @@ -24,8 +27,8 @@ namespace Catch { } template - ExpressionResultBuilder expressionResultBuilderFromMatcher( const MatcherT& matcher, - const std::string& matcherCallAsString ) { + ExpressionResultBuilder expressionResultBuilderFromMatcher( MatcherT const& matcher, + std::string const& matcherCallAsString ) { std::string matcherAsString = matcher.toString(); if( matcherAsString == "{?}" ) matcherAsString = matcherCallAsString; @@ -35,18 +38,18 @@ namespace Catch { } template - ExpressionResultBuilder expressionResultBuilderFromMatcher( const MatcherT& matcher, - const ArgT& arg, - const std::string& matcherCallAsString ) { + ExpressionResultBuilder expressionResultBuilderFromMatcher( MatcherT const& matcher, + ArgT const& arg, + std::string const& matcherCallAsString ) { return expressionResultBuilderFromMatcher( matcher, matcherCallAsString ) .setLhs( Catch::toString( arg ) ) .setResultType( matcher.match( arg ) ); } template - ExpressionResultBuilder expressionResultBuilderFromMatcher( const MatcherT& matcher, + ExpressionResultBuilder expressionResultBuilderFromMatcher( MatcherT const& matcher, ArgT* arg, - const std::string& matcherCallAsString ) { + std::string const& matcherCallAsString ) { return expressionResultBuilderFromMatcher( matcher, matcherCallAsString ) .setLhs( Catch::toString( arg ) ) .setResultType( matcher.match( arg ) ); @@ -54,30 +57,6 @@ namespace Catch { struct TestFailureException{}; -class ScopedInfo { -public: - ScopedInfo() : m_resultBuilder( ResultWas::Info ) { - getResultCapture().pushScopedInfo( this ); - } - ~ScopedInfo() { - getResultCapture().popScopedInfo( this ); - } - template - ScopedInfo& operator << ( const T& value ) { - m_resultBuilder << value; - return *this; - } - AssertionResult buildResult( const AssertionInfo& assertionInfo ) const { - return m_resultBuilder.buildResult( assertionInfo ); - } - -private: - ExpressionResultBuilder m_resultBuilder; -}; - -// This is just here to avoid compiler warnings with macro constants and boolean literals -inline bool isTrue( bool value ){ return value; } - } // end namespace Catch /////////////////////////////////////////////////////////////////////////////// @@ -89,7 +68,7 @@ inline bool isTrue( bool value ){ return value; } if( internal_catch_action & Catch::ResultAction::Debug ) BreakIntoDebugger(); \ if( internal_catch_action & Catch::ResultAction::Abort ) throw Catch::TestFailureException(); \ if( !Catch::shouldContinueOnFailure( resultDisposition ) ) throw Catch::TestFailureException(); \ - if( Catch::isTrue( false ) ){ bool this_is_here_to_invoke_warnings = ( originalExpr ); Catch::isTrue( this_is_here_to_invoke_warnings ); } \ + Catch::isTrue( false && originalExpr ); \ } /////////////////////////////////////////////////////////////////////////////// @@ -107,7 +86,6 @@ inline bool isTrue( bool value ){ return value; } } catch( ... ) { \ INTERNAL_CATCH_ACCEPT_EXPR( Catch::ExpressionResultBuilder( Catch::ResultWas::ThrewException ) << Catch::translateActiveException(), \ resultDisposition | Catch::ResultDisposition::ContinueOnFailure, expr ); \ - throw; \ } \ } while( Catch::isTrue( false ) ) @@ -168,17 +146,24 @@ inline bool isTrue( bool value ){ return value; } } while( Catch::isTrue( false ) ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_MSG( reason, resultType, resultDisposition, macroName ) \ +#define INTERNAL_CATCH_INFO( log, macroName ) \ + do { \ + Catch::getResultCapture().acceptMessage( Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); \ + } while( Catch::isTrue( false ) ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG( log, messageType, resultDisposition, macroName ) \ do { \ INTERNAL_CATCH_ACCEPT_INFO( "", macroName, resultDisposition ); \ - INTERNAL_CATCH_ACCEPT_EXPR( Catch::ExpressionResultBuilder( resultType ) << reason, resultDisposition, true ) \ + INTERNAL_CATCH_ACCEPT_EXPR( Catch::ExpressionResultBuilder( messageType ) << log, resultDisposition, true ) \ } while( Catch::isTrue( false ) ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_SCOPED_INFO( log, macroName ) \ - INTERNAL_CATCH_ACCEPT_INFO( "", macroName, Catch::ResultDisposition::Normal ); \ - Catch::ScopedInfo INTERNAL_CATCH_UNIQUE_NAME( info ); \ - INTERNAL_CATCH_UNIQUE_NAME( info ) << log + Catch::ScopedMessageBuilder INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ); \ + INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) << log; \ + Catch::getResultCapture().pushScopedMessage( INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) ) + /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ @@ -191,7 +176,6 @@ inline bool isTrue( bool value ){ return value; } } catch( ... ) { \ INTERNAL_CATCH_ACCEPT_EXPR( ( Catch::ExpressionResultBuilder( Catch::ResultWas::ThrewException ) << Catch::translateActiveException() ), \ resultDisposition | Catch::ResultDisposition::ContinueOnFailure, false ); \ - throw; \ } \ } while( Catch::isTrue( false ) ) diff --git a/include/internal/catch_commandline.hpp b/include/internal/catch_commandline.hpp index e4f92081..871db331 100644 --- a/include/internal/catch_commandline.hpp +++ b/include/internal/catch_commandline.hpp @@ -10,659 +10,119 @@ #include "catch_config.hpp" #include "catch_common.h" +#include "clara.h" namespace Catch { - - class Command { - public: - Command(){} - - explicit Command( const std::string& name ) : m_name( name ) { - } - - Command& operator += ( const std::string& arg ) { - m_args.push_back( arg ); - return *this; - } - Command& operator += ( const Command& other ) { - std::copy( other.m_args.begin(), other.m_args.end(), std::back_inserter( m_args ) ); - if( m_name.empty() ) - m_name = other.m_name; - return *this; - } - Command operator + ( const Command& other ) { - Command newCommand( *this ); - newCommand += other; - return newCommand; - } - - operator SafeBool::type() const { - return SafeBool::makeSafe( !m_name.empty() || !m_args.empty() ); - } - - std::string name() const { return m_name; } - std::string operator[]( std::size_t i ) const { return m_args[i]; } - std::size_t argsCount() const { return m_args.size(); } - - CATCH_ATTRIBUTE_NORETURN - void raiseError( const std::string& message ) const { - std::ostringstream oss; - if( m_name.empty() ) - oss << "Error while parsing " << m_name << ". " << message << "."; - else - oss << "Error while parsing arguments. " << message << "."; - - if( m_args.size() > 0 ) - oss << " Arguments were:"; - for( std::size_t i = 0; i < m_args.size(); ++i ) - oss << " " << m_args[i]; - throw std::domain_error( oss.str() ); - } - - private: - - std::string m_name; - std::vector m_args; - }; - class CommandParser { - public: - CommandParser( int argc, char const * const * argv ) : m_argc( static_cast( argc ) ), m_argv( argv ) {} + inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } + inline void abortAfterX( ConfigData& config, int x ) { + if( x < 1 ) + throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); + config.abortAfter = x; + } + inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } - std::string exeName() const { - return m_argv[0]; - } - Command find( const std::string& arg1, const std::string& arg2, const std::string& arg3 ) const { - return find( arg1 ) + find( arg2 ) + find( arg3 ); - } + inline void addWarning( ConfigData& config, std::string const& _warning ) { + if( _warning == "NoAssertions" ) + config.warnings = (ConfigData::WarnAbout::What)( config.warnings | ConfigData::WarnAbout::NoAssertions ); + else + throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); - Command find( const std::string& shortArg, const std::string& longArg ) const { - return find( shortArg ) + find( longArg ); - } - Command find( const std::string& arg ) const { - if( arg.empty() ) - return getArgs( "", 1 ); - else - for( std::size_t i = 1; i < m_argc; ++i ) - if( m_argv[i] == arg ) - return getArgs( m_argv[i], i+1 ); - return Command(); - } - Command getDefaultArgs() const { - return getArgs( "", 1 ); - } - - private: - Command getArgs( const std::string& cmdName, std::size_t from ) const { - Command command( cmdName ); - for( std::size_t i = from; i < m_argc && m_argv[i][0] != '-'; ++i ) - command += m_argv[i]; - return command; - } - - std::size_t m_argc; - char const * const * m_argv; - }; - - class OptionParser : public SharedImpl { - public: - OptionParser( int minArgs = 0, int maxArgs = 0 ) - : m_minArgs( minArgs ), m_maxArgs( maxArgs ) - {} - - virtual ~OptionParser() {} - - Command find( const CommandParser& parser ) const { - Command cmd; - for( std::vector::const_iterator it = m_optionNames.begin(); - it != m_optionNames.end(); - ++it ) - cmd += parser.find( *it ); - return cmd; - } - - void validateArgs( const Command& args ) const { - if( tooFewArgs( args ) || tooManyArgs( args ) ) { - std::ostringstream oss; - if( m_maxArgs == -1 ) - oss <<"Expected at least " << pluralise( static_cast( m_minArgs ), "argument" ); - else if( m_minArgs == m_maxArgs ) - oss <<"Expected " << pluralise( static_cast( m_minArgs ), "argument" ); - else - oss <<"Expected between " << m_minArgs << " and " << m_maxArgs << " argument"; - args.raiseError( oss.str() ); - } - } - - void parseIntoConfig( const CommandParser& parser, ConfigData& config ) { - if( Command cmd = find( parser ) ) { - validateArgs( cmd ); - parseIntoConfig( cmd, config ); - } - } - - virtual void parseIntoConfig( const Command& cmd, ConfigData& config ) = 0; - virtual std::string argsSynopsis() const = 0; - virtual std::string optionSummary() const = 0; - virtual std::string optionDescription() const { return ""; } - - std::string optionNames() const { - std::string names; - for( std::vector::const_iterator it = m_optionNames.begin(); - it != m_optionNames.end(); - ++it ) { - if( !it->empty() ) { - if( !names.empty() ) - names += ", "; - names += *it; - } - else { - names = "[" + names; - } - } - if( names[0] == '[' ) - names += "]"; - return names; - } - - protected: - - bool tooFewArgs( const Command& args ) const { - return args.argsCount() < static_cast( m_minArgs ); - } - bool tooManyArgs( const Command& args ) const { - return m_maxArgs >= 0 && args.argsCount() > static_cast( m_maxArgs ); - } - std::vector m_optionNames; - int m_minArgs; - int m_maxArgs; - }; - - namespace Options { - - class HelpOptionParser : public OptionParser { - public: - HelpOptionParser() { - m_optionNames.push_back( "-?" ); - m_optionNames.push_back( "-h" ); - m_optionNames.push_back( "--help" ); - } - virtual std::string argsSynopsis() const { - return "[