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

Q: constructor/destructor functions? #46

Closed
flatcap opened this issue Apr 22, 2020 · 4 comments · Fixed by #48
Closed

Q: constructor/destructor functions? #46

flatcap opened this issue Apr 22, 2020 · 4 comments · Fixed by #48

Comments

@flatcap
Copy link
Contributor

flatcap commented Apr 22, 2020

Would you consider adding user-defined functions that'd be called at the beginning/end of main()
Several of our tests need to have setlocale() called beforehand, so it'd be nice to centralise that.

Something simple like:

#define ACUTEST_CTOR my_constructor

main()
{
#ifdef ACUTEST_CTOR
  ACUTEST_CTOR();
#endif
}

If yes, I'll create a PR

@mity
Copy link
Owner

mity commented Apr 22, 2020

I'm fine with the proposal in principle.

But I would prefer it is named differently, e.g. TEST_INIT or TEST_GLOBAL_INIT (and *_FINI respectivelly):

  1. All our public interface identifiers begin with TEST_.
  2. _CTOR/_DTOR sounds too C++-like to me and I'm afraid people might expect it gets called before/after main() (e.g. even before their own global constructors).

Secondly, if you want e.g. to just call setlocale(), isn't expecting a function name as the macro expansion too uncomfortable? You'd have often to implement a dummy wrapper function. Shouldn't we rather allow any C block?

Then you could just to do e.g. something like this:

#define TEST_INIT            { setlocale(... any args ...); }

instead of the much more verbose

void
my_init(void)
{
     setlocale(... any args ...);
}

#define TEST_INIT            my_init

Finally, I am wondering whether the start/end of main() is the best possible call site for it. It would mean that it gets called exactly once on Unix or whenever with --no-exec, but more then once on Windows (which calls CreateProcess() for the child processes per every test, unless --no-exec is used). And it would get called even when e.g. --help is used, which may be a bad idea if the function connects to a remote server or to a database for example.

Unless you plan something time-expensive there, wouldn't it be better to call it from beginning/end of test_do_run_()? That way, it would have the exactly same semantics no mater what: It would always be called exactly once per every executed test.

Or, perhaps, we could offer both? TEST_INIT per every test and TEST_GLOBAL_INIT from main() (but rather after the command line is parsed)? Just thinking aloud here.

@mity
Copy link
Owner

mity commented Apr 22, 2020

(Or maybe TEST_PROCESS_INIT instead of TEST_GLOBAL_INIT. That's even more descriptive.)

@mity
Copy link
Owner

mity commented May 3, 2020

As using preprocessor is often tricky, especially when one wants to have it reasonably flexible and foolproof, I've played a little bit with it to see how the execute-the-macro pattern could look like. I have ended with this:

#include <stdio.h>

// Test suite side:
// ================

//#define X                                          /* empty definition */
//#define X   printf("foo\n")                        /* single call; no semicolon */
//#define X   printf("foo\n");                       /* single call; with semicolon */
//#define X   const char* str = "foo\n"; printf(str) /* using a var. declaration */
//#define X   printf("foo"); printf("\n");           /* multiple calls */
//#define X   { printf("foo"); printf("\n"); }       /* a whole code block */


// Acutest side:
// =============

static void
call_X(void)
{
#ifdef X
    X
#endif
    ;
}

int
main(int argc, char** argv)
{
    call_X();

    return 0;
}

Notes:

  • It works with all the example macro definitions listed (as well as when X is not defined, of course).
  • However it does not work if the user specifies just a function name as in the original post. Must be #define X my_constructor() and not #define X my_constructor, i.e. the parenthesis are required.
  • It uses a helper function which serves two purposes:
    1. It allows X to declare helper variables (even when the user does not enclose it in a code block).
    2. It makes sure the X expansion cannot reach our local variables in our internal function where we want to execute the initialization.

Is it foolproof and flexible for practical use? Or any better idea?

@flatcap
Copy link
Contributor Author

flatcap commented May 4, 2020

Sorry for disappearing. A stressful release led me to take a week's "holiday".
That looks very nice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants