Skip to content

Conversation

@gocarlos
Copy link

this simple change makes using this header with cpp code work out of the box

@aceckel
Copy link
Contributor

aceckel commented Apr 28, 2020

I think a more changes would be required to support C++ compilation. I am aware of the following C-only features being used that would need to be changed (either by switching to a C++-compatible approach or by using conditional compilation):

  1. Non-strict prototype for the run function. We make use of the non-strictness here to conditionally pass a data parameter. C++ doesn't support non-strict prototypes, so this would fail to compile.
  2. C99 designated initializers for the struct ctest instantiation. C++ didn't add designated initializers until C++20, though some compilers feature an extension for "trivial" designated initializers, in which all initializers are present and in declaration order.
  3. Tentative definitions of the setup/teardown test fixture functions. They are tentatively defined when defining the test fixture data structure and are overridden if the user provides a setup function or teardown function. This is how we implement optional setup and teardown functions in C. C++ doesn't support tentative definitions, so this approach would violate the One Definition Rule, resulting in failure to link.

I'm not sure if @bvdberg would want to see the library modified to accommodate C++. However, here are some ways that we could do it:

For 1), we could change the non-strict prototype to a union of strict prototypes, and remove the explicit suppression of -Wstrict-prototypes.

For 2), we could simply remove the designated initializers from the braced initialization.

However, 3) would most likely require conditional compilation. Here is one possible implementation using templates specialization to perform the desired override behavior:

diff --git a/ctest.h b/ctest.h
index f52236b..ad7094e 100644
--- a/ctest.h
+++ b/ctest.h
@@ -16,6 +16,10 @@
 #ifndef CTEST_H
 #define CTEST_H
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #ifdef __GNUC__
 #define CTEST_IMPL_FORMAT_PRINTF(a, b) __attribute__ ((format(printf, a, b)))
 #else
@@ -75,8 +79,12 @@ CTEST_IMPL_DIAG_POP()
 #define CTEST_IMPL_DATA_TNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_data)
 #define CTEST_IMPL_SETUP_FNAME(sname) CTEST_IMPL_NAME(sname##_setup)
 #define CTEST_IMPL_SETUP_FPNAME(sname) CTEST_IMPL_NAME(sname##_setup_ptr)
+#define CTEST_IMPL_SETUP_TNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_setup)
+#define CTEST_IMPL_SETUP_TPNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_setup_ptr)
 #define CTEST_IMPL_TEARDOWN_FNAME(sname) CTEST_IMPL_NAME(sname##_teardown)
 #define CTEST_IMPL_TEARDOWN_FPNAME(sname) CTEST_IMPL_NAME(sname##_teardown_ptr)
+#define CTEST_IMPL_TEARDOWN_TNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_teardown)
+#define CTEST_IMPL_TEARDOWN_TPNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_teardown_ptr)
 
 #define CTEST_IMPL_MAGIC (0xdeadbeef)
 #ifdef __APPLE__
@@ -96,6 +104,34 @@ CTEST_IMPL_DIAG_POP()
         .skip = tskip, \
         .magic = CTEST_IMPL_MAGIC }
 
+#ifdef __cplusplus
+
+#define CTEST_SETUP(sname) \
+    template <> void CTEST_IMPL_SETUP_FNAME(sname)(struct CTEST_IMPL_DATA_SNAME(sname)* data)
+
+#define CTEST_TEARDOWN(sname) \
+    template <> void CTEST_IMPL_TEARDOWN_FNAME(sname)(struct CTEST_IMPL_DATA_SNAME(sname)* data)
+
+#define CTEST_DATA(sname) \
+    template <typename T> void CTEST_IMPL_SETUP_FNAME(sname)(T* data) { } \
+    template <typename T> void CTEST_IMPL_TEARDOWN_FNAME(sname)(T* data) { } \
+    struct CTEST_IMPL_DATA_SNAME(sname)
+
+#define CTEST_IMPL_CTEST(sname, tname, tskip) \
+    static void CTEST_IMPL_FNAME(sname, tname)(void); \
+    CTEST_IMPL_STRUCT(sname, tname, tskip, NULL, NULL, NULL); \
+    static void CTEST_IMPL_FNAME(sname, tname)(void)
+
+#define CTEST_IMPL_CTEST2(sname, tname, tskip) \
+    static struct CTEST_IMPL_DATA_SNAME(sname) CTEST_IMPL_DATA_TNAME(sname, tname); \
+    static void CTEST_IMPL_FNAME(sname, tname)(struct CTEST_IMPL_DATA_SNAME(sname)* data); \
+    static void (*CTEST_IMPL_SETUP_TPNAME(sname, tname))(struct CTEST_IMPL_DATA_SNAME(sname)*) = &CTEST_IMPL_SETUP_FNAME(sname)<struct CTEST_IMPL_DATA_SNAME(sname)>; \
+    static void (*CTEST_IMPL_TEARDOWN_TPNAME(sname, tname))(struct CTEST_IMPL_DATA_SNAME(sname)*) = &CTEST_IMPL_TEARDOWN_FNAME(sname)<struct CTEST_IMPL_DATA_SNAME(sname)>; \
+    CTEST_IMPL_STRUCT(sname, tname, tskip, &CTEST_IMPL_DATA_TNAME(sname, tname), &CTEST_IMPL_SETUP_TPNAME(sname, tname), &CTEST_IMPL_TEARDOWN_TPNAME(sname, tname)); \
+    static void CTEST_IMPL_FNAME(sname, tname)(struct CTEST_IMPL_DATA_SNAME(sname)* data)
+
+#else
+
 #define CTEST_SETUP(sname) \
     static void CTEST_IMPL_SETUP_FNAME(sname)(struct CTEST_IMPL_DATA_SNAME(sname)* data); \
     static void (*CTEST_IMPL_SETUP_FPNAME(sname))(struct CTEST_IMPL_DATA_SNAME(sname)*) = &CTEST_IMPL_SETUP_FNAME(sname); \
@@ -123,6 +159,7 @@ CTEST_IMPL_DIAG_POP()
     CTEST_IMPL_STRUCT(sname, tname, tskip, &CTEST_IMPL_DATA_TNAME(sname, tname), &CTEST_IMPL_SETUP_FPNAME(sname), &CTEST_IMPL_TEARDOWN_FPNAME(sname)); \
     static void CTEST_IMPL_FNAME(sname, tname)(struct CTEST_IMPL_DATA_SNAME(sname)* data)
 
+#endif
 
 void CTEST_LOG(const char* fmt, ...) CTEST_IMPL_FORMAT_PRINTF(1, 2);
 void CTEST_ERR(const char* fmt, ...) CTEST_IMPL_FORMAT_PRINTF(1, 2);  // doesn't return
@@ -543,5 +580,9 @@ __attribute__((no_sanitize_address)) int ctest_main(int argc, const char *argv[]
 
 #endif
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif
 

@gocarlos
Copy link
Author

you are right, didn't test this enough with cpp...

@bvdberg
Copy link
Owner

bvdberg commented Apr 29, 2020

This framework is called ctest...and it's meant for unit testing in C. For C++ there are frameworks that are better suited for C++, like yaffut. These use C++ classes as the Fixture and tests become members of the fixture class. That works for C++, but not for C. So I'm not very interested in enabling C++ in ctest, since it just doesn't make sense.

@gocarlos
Copy link
Author

This framework is called ctest...and it's meant for unit testing in C. For C++ there are frameworks that are better suited for C++, like yaffut. These use C++ classes as the Fixture and tests become members of the fixture class. That works for C++, but not for C. So I'm not very interested in enabling C++ in ctest, since it just doesn't make sense.

there are some projects that are going to transition from C to C++ in the future. It would be nice if those projects could use the existing testing framework (ctest) to show that the transition is working...

@aceckel
Copy link
Contributor

aceckel commented Apr 30, 2020

I agree with @gocarlos that there is real value in being able to reuse existing unit tests while migrating a project from C to C++. Another reason to consider adding C++ support is to enable projects using a combination of C and C++ to use a single unit testing framework. Currently, if those projects want to us a single framework, they have to make a choice between a C framework other than ctest (which most likely requires more boilerplate than ctest such as manual test registration) or a C++ framework (which prohibits testing of C code that makes use of C features not available in C++). IMO ctest is in a good position to provide the best of both worlds, filling this niche nicely. We only need the changes mentioned above to make it happen, so I think it is worth considering.

@bvdberg
Copy link
Owner

bvdberg commented Apr 30, 2020

I agree that for a certain scenario it could be useful. I do want to make sure a combined c/c++ testrunner does work. So certain cpp files would get a different 'view' of the ctest.h by your proposal.
Just make a proposal and i'll look at it.

@aceckel
Copy link
Contributor

aceckel commented Aug 20, 2020

I have created this PR with the changes discussed above.

@bvdberg
Copy link
Owner

bvdberg commented Sep 20, 2020

Obsolete this pull #45

@bvdberg bvdberg closed this Sep 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants