martinmoene / lest

A modern, C++11-native, single-file header-only, tiny framework for unit-tests, TDD and BDD (includes C++98 variant)
Boost Software License 1.0
390 stars 45 forks source link

User-defined functions based on lest macros #23

Closed MariusDe closed 8 years ago

MariusDe commented 8 years ago

Hi, again a basic question about a simple use-case: what options are available for users which need stand-alone functions which use lest related macros (like EXPECT(), etc.)? My use-case would be a (reusable) function which needs to be called repeatedly from one or more CASE()s.

I tried the classic approaches: stand-alone function defined outside of CASE() as well as lambda function - defined inside the CASE(), which contains EXPECT() calls- but I did not managed to have working code (gcc 4.8.1). Here is the error when calling the lambda function containing the EXPECT() calls:

.../lest/src/lest.hpp:136:23: error: '$' is not captured
             else if ( $.pass ) \
                       ^
.../lest/src/lest.hpp:81:28: note: in expansion of macro 'lest_EXPECT'
 # define EXPECT            lest_EXPECT
                            ^

Cheers, Marius

martinmoene commented 8 years ago

You need to provide the test environment, type lest::env, variable $.

For example:

// using EXPECT from a function called from a CASE, single-file test program

#include "lest.hpp"

#define CASE( name ) lest_CASE( specification, name )

static lest::tests specification; 

CASE( "Call a lambda that uses EXPECT" )
{
    auto foo = [ &$ ]( int a, int b ){ EXPECT( a == b ); };

    foo( 1, 1 );
    foo( 1, 2 );
}

void bar( lest::env & $, int a, int b )
{
    EXPECT( a == b );
}

CASE( "Call a function that uses EXPECT" )
{
    bar( $, 1, 1 );
    bar( $, 1, 2 );
}

// or, via a macro if you like:

#define mbar( a, b ) \
    bar( $, a, b )

CASE( "Call a macro that calls a function that uses EXPECT" )
{
    mbar( 1, 1 );
    mbar( 1, 2 );
}

int main( int argc, char * argv[] )
{
    return lest::run( specification, argc, argv, std::cout );
}

Compile & run:

prompt>g++ -Wall -Wextra -std=c++11 -Dlest_FEATURE_AUTO_REGISTER -I. -o main.exe main.cpp && main --pass
main.cpp:11: passed: Call a lambda that uses EXPECT: a == b for 1 == 1
main.cpp:11: failed: Call a lambda that uses EXPECT: a == b for 1 == 2
main.cpp:19: passed: Call a function that uses EXPECT: a == b for 1 == 1
main.cpp:19: failed: Call a function that uses EXPECT: a == b for 1 == 2
main.cpp:19: passed: Call a macro that calls a function that uses EXPECT: a == b for 1 == 1
main.cpp:19: failed: Call a macro that calls a function that uses EXPECT: a == b for 1 == 2
3 out of 3 selected tests failed.```
MariusDe commented 8 years ago

Nice! Many thanks, Martin!

martinmoene commented 8 years ago

Good questions, you're welcome;)

MariusDe commented 8 years ago

Martin, I just managed a few minutes ago to type some code. I might be doing something wrong, because I cannot compile the example above. Here is the output of my compiler for the above code (seems like lest_CASE( specification, proposition ) is not expanded correctly in my case - gcc 4.8.1; and don't have an older gcc to try it out). Thank you!

> g++ -Wall -Wextra -std=c++11 -Dlest_FEATURE_AUTO_REGISTER -Ic:\\Users\\Marius\\workspace\\3rdParty\\lest\\src\\ -o main.exe StandaloneFunction.cpp
In file included from StandaloneFunction.cpp:4:0:
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src\\lest.hpp:111:5: error: expected primary-expression before 'void'
     void lest_FUNCTION( lest::env & ); \
     ^
StandaloneFunction.cpp:6:22: note: in expansion of macro 'lest_CASE'
 #define CASE( name ) lest_CASE( specification, name );
                      ^
StandaloneFunction.cpp:19:1: note: in expansion of macro 'CASE'
 CASE( "Call a lambda that uses EXPECT" )
 ^
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src\\lest.hpp:111:5: error: expected '}' before 'void'
     void lest_FUNCTION( lest::env & ); \
     ^
StandaloneFunction.cpp:6:22: note: in expansion of macro 'lest_CASE'
 #define CASE( name ) lest_CASE( specification, name );
                      ^
StandaloneFunction.cpp:19:1: note: in expansion of macro 'CASE'
 CASE( "Call a lambda that uses EXPECT" )
 ^
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src\\lest.hpp:111:5: error: expected ',' or ';' before 'void'
     void lest_FUNCTION( lest::env & ); \
     ^
StandaloneFunction.cpp:6:22: note: in expansion of macro 'lest_CASE'
 #define CASE( name ) lest_CASE( specification, name );
                      ^
StandaloneFunction.cpp:19:1: note: in expansion of macro 'CASE'
 CASE( "Call a lambda that uses EXPECT" )
 ^
In file included from StandaloneFunction.cpp:4:0:
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src\\lest.hpp:219:36: error: '__lest_function__19' was not declared in this scope
 #define lest_FUNCTION  lest_UNIQUE(__lest_function__  )
                                    ^
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src\\lest.hpp:215:36: note: in definition of macro 'lest_UNIQUE3'
 #define lest_UNIQUE3( name, line ) name ## line
                                    ^
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src\\lest.hpp:213:36: note: in expansion of macro 'lest_UNIQUE2'
 #define lest_UNIQUE(  name       ) lest_UNIQUE2( name, __LINE__ )
                                    ^
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src\\lest.hpp:219:24: note: in expansion of macro 'lest_UNIQUE'
 #define lest_FUNCTION  lest_UNIQUE(__lest_function__  )
                        ^
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src\\lest.hpp:112:88: note: in expansion of macro 'lest_FUNCTION'
     namespace { lest::add_test lest_REGISTRAR( specification, lest::test( proposition, lest_FUNCTION ) ); } \
                                                                                        ^
StandaloneFunction.cpp:6:22: note: in expansion of macro 'lest_CASE'
 #define CASE( name ) lest_CASE( specification, name );
                      ^
StandaloneFunction.cpp:19:1: note: in expansion of macro 'CASE'
 CASE( "Call a lambda that uses EXPECT" )
 ^
StandaloneFunction.cpp:20:1: error: expected unqualified-id before '{' token
 {
 ^
martinmoene commented 8 years ago

.. it would be handy if you show the source code for what you're trying to accomplish...

MariusDe commented 8 years ago

Martin, I was wrong! Your code runs fine! I compiled the wrong file.. 8-) But the error above is from the below code (which uses CASE() in a "module"):

// g++ -Wall -Wextra -std=c++11 -Dlest_FEATURE_AUTO_REGISTER -I. -o main.exe StandaloneFunction.cpp && main

#include "lest.hpp"

#define CASE( name ) lest_CASE( specification, name );

static lest::tests specification; 

void bar( lest::env & $, int a, int b )
{
    EXPECT( a == b );
}

const lest::test Module2[] =
{

CASE( "Call a lambda that uses EXPECT" )
{
    auto foo = [ &$ ]( int a, int b ){ EXPECT( a == b ); };

    foo( 1, 1 );
    foo( 1, 2 );
},

CASE( "Call a function that uses EXPECT" )
{
    bar( $, 1, 1 );
    bar( $, 1, 2 );
},
/* 
// or, via a macro if you like:

#define mbar( a, b ) \
    bar( $, a, b )

CASE( "Call a macro that calls a function that uses EXPECT" )
{
    mbar( 1, 1 );
    mbar( 1, 2 );
}, */

}; // module

lest_MODULE( specification, Module2 );

int main( int argc, char * argv[] )
{
    return lest::run( specification, argc, argv, std::cout );
}

g++ -Wall -Wextra -std=c++11 -I. -o 

The above code is compiled and produces the below output:

g++ -Wall -Wextra -std=c++11 -Dlest_FEATURE_AUTO_REGISTER -Ic:\\Users\\Marius\\workspace\\3rdParty\\lest\\src\\ -o main.exe StandaloneFunction.cpp
In file included from StandaloneFunction.cpp:3:0:
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src/lest.hpp:111:5: error: expected primary-expression before 'void'
     void lest_FUNCTION( lest::env & ); \
     ^
StandaloneFunction.cpp:5:22: note: in expansion of macro 'lest_CASE'
 #define CASE( name ) lest_CASE( specification, name );
                      ^
StandaloneFunction.cpp:18:1: note: in expansion of macro 'CASE'
 CASE( "Call a lambda that uses EXPECT" )
 ^
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src/lest.hpp:111:5: error: expected '}' before 'void'
     void lest_FUNCTION( lest::env & ); \
     ^
StandaloneFunction.cpp:5:22: note: in expansion of macro 'lest_CASE'
 #define CASE( name ) lest_CASE( specification, name );
                      ^
StandaloneFunction.cpp:18:1: note: in expansion of macro 'CASE'
 CASE( "Call a lambda that uses EXPECT" )
 ^
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src/lest.hpp:111:5: error: expected ',' or ';' before 'void'
     void lest_FUNCTION( lest::env & ); \
     ^
StandaloneFunction.cpp:5:22: note: in expansion of macro 'lest_CASE'
 #define CASE( name ) lest_CASE( specification, name );
                      ^
StandaloneFunction.cpp:18:1: note: in expansion of macro 'CASE'
 CASE( "Call a lambda that uses EXPECT" )
 ^
In file included from StandaloneFunction.cpp:3:0:
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src/lest.hpp:219:36: error: '__lest_function__18' was not declared in this scope
 #define lest_FUNCTION  lest_UNIQUE(__lest_function__  )
                                    ^
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src/lest.hpp:215:36: note: in definition of macro 'lest_UNIQUE3'
 #define lest_UNIQUE3( name, line ) name ## line
                                    ^
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src/lest.hpp:213:36: note: in expansion of macro 'lest_UNIQUE2'
 #define lest_UNIQUE(  name       ) lest_UNIQUE2( name, __LINE__ )
                                    ^
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src/lest.hpp:219:24: note: in expansion of macro 'lest_UNIQUE'
 #define lest_FUNCTION  lest_UNIQUE(__lest_function__  )
                        ^
c:\\Users\\Marius\\workspace\\3rdParty\\lest\\src/lest.hpp:112:88: note: in expansion of macro 'lest_FUNCTION'
     namespace { lest::add_test lest_REGISTRAR( specification, lest::test( proposition, lest_FUNCTION ) ); } \
                                                                                        ^
StandaloneFunction.cpp:5:22: note: in expansion of macro 'lest_CASE'
 #define CASE( name ) lest_CASE( specification, name );
                      ^
StandaloneFunction.cpp:18:1: note: in expansion of macro 'CASE'
 CASE( "Call a lambda that uses EXPECT" )
 ^
StandaloneFunction.cpp:19:1: error: expected unqualified-id before '{' token
 {
 ^
martinmoene commented 8 years ago

mm missed the " for the above code" ...

martinmoene commented 8 years ago

... The real content of StandaloneFunction.cpp please...

MariusDe commented 8 years ago

Please see my previous post, which was updated with code + output of StandaloneFunction.cpp. Thank you!

martinmoene commented 8 years ago

Works for me with gcc 4.8.1.

MariusDe commented 8 years ago

Thanks, then it might be due to the environment (gcc 4.8.1 runs under MinGW).. I have a Linux virtual machine, on which I will install gcc soon and give it another try on native environment. Be right back..

martinmoene commented 8 years ago

For above test I used MinGW gcc 4.8.1 (not via msys) under Windows 8.1. I've no idea(s) yet what might go wrong.

MariusDe commented 8 years ago

Done! So .. on Fedora 22 (gcc 5.1.1), the below code:

#include "lest.hpp"

#define CASE( name ) lest_CASE( specification, name );

static lest::tests specification; 

void bar( lest::env & $, int a, int b )
{
    EXPECT( a == b );
}

const lest::test Module2[] =
{

CASE( "Call a lambda that uses EXPECT" )
{
    auto foo = [ &$ ]( int a, int b ){ EXPECT( a == b ); };

    foo( 1, 1 );
    foo( 1, 2 );
},

CASE( "Call a function that uses EXPECT" )
{
    bar( $, 1, 1 );
    bar( $, 1, 2 );
},
/* 
// or, via a macro if you like:

#define mbar( a, b ) \
    bar( $, a, b )

CASE( "Call a macro that calls a function that uses EXPECT" )
{
    mbar( 1, 1 );
    mbar( 1, 2 );
}, */

}; // module

lest_MODULE( specification, Module2 );

int main( int argc, char * argv[] )
{
    return lest::run( specification, argc, argv, std::cout );
}

.. produces the following output (at least first error lines below are same as the ones I had with gcc 4.8.1 above):

In file included from StandaloneFunction.cpp:3:0:
lest.hpp:111:5: error: expected primary-expression before ‘void’
     void lest_FUNCTION( lest::env & ); \
     ^
StandaloneFunction.cpp:5:22: note: in expansion of macro ‘lest_CASE’
 #define CASE( name ) lest_CASE( specification, name );
                      ^
StandaloneFunction.cpp:18:1: note: in expansion of macro ‘CASE’
 CASE( "Call a lambda that uses EXPECT" )
 ^
lest.hpp:111:5: error: expected ‘}’ before ‘void’
     void lest_FUNCTION( lest::env & ); \
     ^
StandaloneFunction.cpp:5:22: note: in expansion of macro ‘lest_CASE’
 #define CASE( name ) lest_CASE( specification, name );
                      ^
StandaloneFunction.cpp:18:1: note: in expansion of macro ‘CASE’
 CASE( "Call a lambda that uses EXPECT" )
 ^
lest.hpp:111:5: error: expected ‘,’ or ‘;’ before ‘void’
     void lest_FUNCTION( lest::env & ); \
     ^
StandaloneFunction.cpp:5:22: note: in expansion of macro ‘lest_CASE’
 #define CASE( name ) lest_CASE( specification, name );
                      ^
StandaloneFunction.cpp:18:1: note: in expansion of macro ‘CASE’
 CASE( "Call a lambda that uses EXPECT" )
 ^
In file included from StandaloneFunction.cpp:3:0:
lest.hpp:219:36: error: ‘__lest_function__18’ was not declared in this scope
 #define lest_FUNCTION  lest_UNIQUE(__lest_function__  )
                                    ^
lest.hpp:215:36: note: in definition of macro ‘lest_UNIQUE3’
 #define lest_UNIQUE3( name, line ) name ## line
                                    ^
lest.hpp:213:36: note: in expansion of macro ‘lest_UNIQUE2’
 #define lest_UNIQUE(  name       ) lest_UNIQUE2( name, __LINE__ )
                                    ^
lest.hpp:219:24: note: in expansion of macro ‘lest_UNIQUE’
 #define lest_FUNCTION  lest_UNIQUE(__lest_function__  )
                        ^
lest.hpp:112:88: note: in expansion of macro ‘lest_FUNCTION’
     namespace { lest::add_test lest_REGISTRAR( specification, lest::test( proposition, lest_FUNCTION ) ); } \
                                                                                        ^
StandaloneFunction.cpp:5:22: note: in expansion of macro ‘lest_CASE’
 #define CASE( name ) lest_CASE( specification, name );
                      ^
StandaloneFunction.cpp:18:1: note: in expansion of macro ‘CASE’
 CASE( "Call a lambda that uses EXPECT" )
 ^
StandaloneFunction.cpp:19:1: error: expected unqualified-id before ‘{’ token
 {
 ^

So given I reproduced it on a fresh installation as well (so in my case Win 7 + Fedora 22), I would like to know why this is not the case on your machine. This might be..: a) a user / compilation error (I don't think this is the issue, but it happened to me a few minutes before as well). Can you please check you compiled the code in this post using the command line above the error? b) are there any environment variables or compiler defines that lest needs (on Fedora I just unzipped lest and included the header, nothing else - how nice! :) )?

Thanks!

MariusDe commented 8 years ago

Ouch.. dos2unix lest.hpp did the trick on Fedora (and I would expect this to be the case on Windows as well)! By the way, in Fedora I downloaded the tar.gz and unpacked it with midnight commander's UI (which probably does not perform any EOL conversion). But I would assume that by using the right command line the Unix EOL is chosen. Will try that..

martinmoene commented 8 years ago

Great! Line ending did cross my mind... Might be worth mentioning somewhere. Thanks for noting& solving !


Was writing:

a) this is what I had already done, and just did again: it compiles/runs fine, Next I downloaded v1.24.3.zip, unzipped it and specified its include folder: still compiles/runs fine. b) not that I'm aware of (yet;)

Just to be sure: which lest code did you download?

martinmoene commented 8 years ago

Just using LF under Windows (as it appears after unzip with 7-zip) does not lead to erroneous compilation under Windows 8.1/g++ 4.8.1.

MariusDe commented 8 years ago

Untar using:

tar -xvzf lest-1.24.3.tar.gz

.. on Fedora still leads to the compilation error above.

Sorry, in my previous post I did a mistake: dos2unix does not fixed this on Fedora (I double checked again by running it on all files from lest framework). I will resume the work in another day, I am a bit tired and make silly mistakes..

My next step will be to unzip on Windows 7 with 7-zip and see if that makes a difference.

martinmoene commented 8 years ago

Isn't the source of the problem in the source file StandaloneFunction.cpp ?

MariusDe commented 8 years ago

It can be anywhere, so also in StandaloneFunction.cpp. Here is the version of the above code (from the post where I've reproduced the issue on Fedora) after the preprocessor expanded the macros/etc (using gcc 4.8.1 on Win 8 this time). Can you please do the same with the code and do a diff between your version and mine?

 g++ -Wall -Wextra -std=c++11 -Dlest_FEATURE_AUTO_REGISTER -Ic:\\Users\\Marius\\workspace\\3rdParty\\lest\\src\\ -E StandaloneFunction.cpp > ExpandedStandaloneFunction.cpp

(of course first 54k lines skipped)


static lest::tests specification;

void bar( lest::env & $, int a, int b )
{
    do { try { if ( lest::result score = ( lest::expression_decomposer() << a == b ) ) throw lest::failure{ lest::location{"StandaloneFunction.cpp", 12}, "a == b", score.decomposition }; else if ( $.pass ) lest::report( $.os, lest::passing{ lest::location{"StandaloneFunction.cpp", 12}, "a == b", score.decomposition }, $.testing ); } catch(...) { lest::inform( lest::location{"StandaloneFunction.cpp", 12}, "a == b" ); } } while ( lest::is_false() );
}

const lest::test Module2[] =
{

void __lest_function__18( lest::env & ); namespace { lest::add_test __lest_registrar__18( specification, lest::test( "Call a lambda that uses EXPECT", __lest_function__18 ) ); } void __lest_function__18( lest::env & $ )
{
    auto foo = [ &$ ]( int a, int b ){ do { try { if ( lest::result score = ( lest::expression_decomposer() << a == b ) ) throw lest::failure{ lest::location{"StandaloneFunction.cpp", 20}, "a == b", score.decomposition }; else if ( $.pass ) lest::report( $.os, lest::passing{ lest::location{"StandaloneFunction.cpp", 20}, "a == b", score.decomposition }, $.testing ); } catch(...) { lest::inform( lest::location{"StandaloneFunction.cpp", 20}, "a == b" ); } } while ( lest::is_false() ); };

    foo( 1, 1 );
    foo( 1, 2 );
},

void __lest_function__26( lest::env & ); namespace { lest::add_test __lest_registrar__26( specification, lest::test( "Call a function that uses EXPECT", __lest_function__26 ) ); } void __lest_function__26( lest::env & $ )
{
    bar( $, 1, 1 );
    bar( $, 1, 2 );
},
# 43 "StandaloneFunction.cpp"
};

namespace { lest::add_module _( specification, Module2 ); };

int main( int argc, char * argv[] )
{
    return lest::run( specification, argc, argv, std::cout );
}

Thanks!

MariusDe commented 8 years ago

Compiling the preprocessed source-code will give the following error

> g++ -Wall -Wextra -std=c++11 -Dlest_FEATURE_AUTO_REGISTER -Ic:\\Users\\Marius\\workspace\\3rdParty\\lest\\src\\ -o main.exe ExpandedStandaloneFunction.cpp
StandaloneFunction.cpp:18:1: error: expected primary-expression before 'void'
 CASE( "Call a lambda that uses EXPECT" )
 ^
StandaloneFunction.cpp:18:1: error: expected '}' before 'void'
StandaloneFunction.cpp:18:1: error: expected ',' or ';' before 'void'
StandaloneFunction.cpp:18:152: error: '__lest_function__18' was not declared in this scope
 CASE( "Call a lambda that uses EXPECT" )
                                                                                                                                                        ^
StandaloneFunction.cpp:24:2: error: mixing declarations and function-definitions is forbidden
 },
  ^
StandaloneFunction.cpp:26:1: error: expected unqualified-id before 'void'
 CASE( "Call a function that uses EXPECT" )
 ^
StandaloneFunction.cpp:26:154: error: '__lest_function__26' was not declared in this scope
 CASE( "Call a function that uses EXPECT" )
                                                                                                                                                          ^
StandaloneFunction.cpp:30:2: error: mixing declarations and function-definitions is forbidden
 },
  ^
StandaloneFunction.cpp:43:1: error: expected unqualified-id before '}' token
 }; // module
 ^
StandaloneFunction.cpp:43:1: error: expected declaration before '}' toke

I think it expects that the below will first be closed using '}' .. but why does it work in your case then?

const lest::test Module2[] =
{
martinmoene commented 8 years ago

Ok, I was right to ask for the real code in StandaloneFunction.cpp, but having to go up in the timeline to go forward in time has proved too difficult for me.

Take note of your line(44) lest_MODULE( specification, Module2 );.

Then please read Module registration macro: ... Note that with lest using auto test case registration there's no need for macro MODULE(), see the auto-registration example part 1, 2, 3. The same holds for _lestcpp03, see cpp03 example part 1, 2, 3.

No need, meaning it's an error to do so.

Cheers, Martin

martinmoene commented 8 years ago

... and look at lines 3, 13,14, 41 and 44.

Either:

Use modules:

// OMIT: #define CASE( name ) lest_CASE( specification, name );
...
const lest::test Module2[] =
{
...
}
...
lest_MODULE( specification, Module2 );

and omit -Dlest_FEATURE_AUTO_REGISTER.

or

Use auto test registration:

#define CASE( name ) lest_CASE( specification, name );
...
namespace 
{
...
}
//OMIT: lest_MODULE( specification, Module2 );

and specify -Dlest_FEATURE_AUTO_REGISTER.

Please have a look/start from the relevant examples.

MariusDe commented 8 years ago

Martin, I referenced something which was already posted, to reduce the length of the post. Next time I will copy the code again, no problem.

Thank you a lot for the examples and documentation reference!! So manual and automatic registration functionality cannot be mixed. Makes perfectly sense! Your example containing reusable functions was using on automatic registration. My last example of reusable functions was using manual registration code using the lest_FEATURE_AUTO_REGISTER macro, which lead to the above errors.

Maybe for the future a preprocessing error (which would politely invite the ignorant user to "Please read the ... manual") could help in getting faster to the problem when manual registration macros are used ; )

OK, I will next try to combine manual registration & reusable functions, as this is what my test currently uses.

Cheers, Marius

martinmoene commented 8 years ago

Indeed makes sense to try to warn against unintended or unsupported use.

cheers, Martin

MariusDe commented 8 years ago

I think a preprocessor check in lest_MODULE() would suffice.

So manual registration & reusable functions in a sample program work amazing. I have no further questions about this issue.

Cheers, Marius