-
-
Notifications
You must be signed in to change notification settings - Fork 201
C UNIT TESTING!!! (this time on the right repo) #3477
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
base: main
Are you sure you want to change the base?
Changes from 21 commits
7f1684f
3bc9ee3
578478b
fac3c87
4d81bd2
943eafe
e0b8ed7
a991b4a
96e35c0
7922bcf
99ad425
d383916
e51672d
50f3474
9724c02
4d058a1
4172f57
f8447fe
f0d6031
f1b8f43
917b360
ad295f2
5ddff7a
ac84921
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
#include <Python.h> | ||
|
||
#include "base.h" | ||
#include "test_common.h" | ||
|
||
static PyObject *base_module; | ||
|
||
/* setUp and tearDown must be nonstatic void(void) */ | ||
void setUp(void) {} | ||
|
||
void tearDown(void) {} | ||
|
||
/** | ||
* @brief Tests _pg_is_int_tuple when passed a tuple of ints | ||
*/ | ||
PG_CTEST(test__pg_is_int_tuple_nominal)(PyObject *self, PyObject *_null) { | ||
PyObject *arg1 = Py_BuildValue("(iii)", 1, 2, 3); | ||
PyObject *arg2 = Py_BuildValue("(iii)", -1, -2, -3); | ||
PyObject *arg3 = Py_BuildValue("(iii)", 1, -2, -3); | ||
|
||
TEST_ASSERT_EQUAL(1, _pg_is_int_tuple(arg1)); | ||
TEST_ASSERT_EQUAL(1, _pg_is_int_tuple(arg2)); | ||
TEST_ASSERT_EQUAL(1, _pg_is_int_tuple(arg3)); | ||
|
||
Py_RETURN_NONE; | ||
} | ||
|
||
/** | ||
* @brief Tests _pg_is_int_tuple when passed a tuple of non-numeric values | ||
*/ | ||
PG_CTEST(test__pg_is_int_tuple_failureModes)(PyObject *self, PyObject *_null) { | ||
PyObject *arg1 = | ||
Py_BuildValue("(sss)", (char *)"Larry", (char *)"Moe", (char *)"Curly"); | ||
PyObject *arg2 = Py_BuildValue("(sss)", (char *)NULL, (char *)NULL, | ||
(char *)NULL); // tuple of None's | ||
PyObject *arg3 = Py_BuildValue("(OOO)", arg1, arg2, arg1); | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
TEST_ASSERT_EQUAL(0, _pg_is_int_tuple(arg1)); | ||
TEST_ASSERT_EQUAL(0, _pg_is_int_tuple(arg2)); | ||
TEST_ASSERT_EQUAL(0, _pg_is_int_tuple(arg3)); | ||
|
||
Py_RETURN_NONE; | ||
} | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* @brief Tests _pg_is_int_tuple when passed a tuple of floats | ||
*/ | ||
PG_CTEST(test__pg_is_int_tuple_floats)(PyObject *self, PyObject *_null) { | ||
PyObject *arg1 = Py_BuildValue("(ddd)", 1.0, 2.0, 3.0); | ||
PyObject *arg2 = Py_BuildValue("(ddd)", -1.1, -2.2, -3.3); | ||
PyObject *arg3 = Py_BuildValue("(ddd)", 1.0, -2.0, -3.1); | ||
|
||
TEST_ASSERT_EQUAL(0, _pg_is_int_tuple(arg1)); | ||
TEST_ASSERT_EQUAL(0, _pg_is_int_tuple(arg2)); | ||
TEST_ASSERT_EQUAL(0, _pg_is_int_tuple(arg3)); | ||
|
||
Py_RETURN_NONE; | ||
} | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/*=======Test Reset Option=====*/ | ||
/* This must be void(void) */ | ||
void resetTest(void) { | ||
tearDown(); | ||
setUp(); | ||
} | ||
|
||
/*=======Exposed Test Reset Option=====*/ | ||
static PyObject *reset_test(PyObject *self, PyObject *_null) { | ||
resetTest(); | ||
|
||
Py_RETURN_NONE; | ||
} | ||
|
||
/*=======Run The Tests=======*/ | ||
static PyObject *run_tests(PyObject *self, PyObject *_null) { | ||
UnityBegin("base_ctest.c"); | ||
RUN_TEST_PG_INTERNAL(test__pg_is_int_tuple_nominal); | ||
RUN_TEST_PG_INTERNAL(test__pg_is_int_tuple_failureModes); | ||
RUN_TEST_PG_INTERNAL(test__pg_is_int_tuple_floats); | ||
|
||
return PyLong_FromLong(UnityEnd()); | ||
} | ||
|
||
static PyMethodDef base_test_methods[] = { | ||
{"test__pg_is_int_tuple_nominal", | ||
(PyCFunction)test__pg_is_int_tuple_nominal, METH_NOARGS, | ||
"Tests _pg_is_int_tuple when passed a tuple of ints"}, | ||
{"test__pg_is_int_tuple_failureModes", | ||
(PyCFunction)test__pg_is_int_tuple_failureModes, METH_NOARGS, | ||
"Tests _pg_is_int_tuple when passed a tuple of non-numeric values"}, | ||
{"test__pg_is_int_tuple_floats", (PyCFunction)test__pg_is_int_tuple_floats, | ||
METH_NOARGS, "Tests _pg_is_int_tuple when passed a tuple of floats"}, | ||
{"reset_test", (PyCFunction)reset_test, METH_NOARGS, | ||
"Resets the test suite between tests, run_tests automatically calls this " | ||
"after each test case it calls"}, | ||
{"run_tests", (PyCFunction)run_tests, METH_NOARGS, | ||
"Runs all the tests in this test wuite"}, | ||
{NULL, NULL, 0, NULL}}; | ||
|
||
MODINIT_DEFINE(base_ctest) { | ||
PyObject *module; | ||
|
||
static struct PyModuleDef _module = { | ||
PyModuleDef_HEAD_INIT, | ||
"base_ctest", | ||
"C unit tests for the pygame.base internal implementation", | ||
-1, | ||
base_test_methods, | ||
NULL, | ||
NULL, | ||
NULL, | ||
NULL}; | ||
|
||
/* create the module */ | ||
module = PyModule_Create(&_module); | ||
if (!module) { | ||
return NULL; | ||
} | ||
|
||
return module; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
unity_subproject = subproject('unity') | ||
unity_dependency = unity_subproject.get_variable('unity_dep') | ||
|
||
base_ctest = py.extension_module( | ||
'base_ctest', | ||
'base_ctest.c', | ||
c_args: warnings_error, | ||
dependencies: [pg_base_deps, unity_dependency], | ||
sources: ['../src_c/base.c'], | ||
install: true, | ||
subdir: pg, | ||
include_directories: ['../src_c'] | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
#include <Python.h> | ||
|
||
#include "unity.h" | ||
|
||
#ifndef TEST_COMMON_H | ||
#define TEST_COMMON_H | ||
|
||
struct TestCase { | ||
char *test_name; | ||
int line_num; | ||
}; | ||
|
||
/* | ||
This will take some explanation... the PG_CTEST macro defines two things | ||
for an individual test case. The test case itself, and a struct instance | ||
called meta_TEST_CASE_NAME. The struct has two pieces of important | ||
information that unity needs: the name in string format and the line | ||
number of the test. This would be an absolute nighmare to maintain by | ||
hand, so I defined a macro to do it automagically for us. | ||
|
||
The RUN_TEST_PG_INTERNAL macro then references that struct for each test | ||
case that we tell it about and automatically populates the unity fields | ||
with the requisite data. | ||
|
||
Note that the arguments to the test function must be *exactly* | ||
(PyObject * self, PyObject * _null), but due to gcc throwing a fit, I | ||
cannot just use token pasting to have the macro generate that part for me | ||
*/ | ||
#define PG_CTEST(TestFunc) \ | ||
static struct TestCase meta_##TestFunc = {#TestFunc, __LINE__}; \ | ||
static PyObject *TestFunc | ||
|
||
#define RUN_TEST_PG_INTERNAL(TestFunc) \ | ||
{ \ | ||
Unity.CurrentTestName = meta_##TestFunc.test_name; \ | ||
Unity.CurrentTestLineNumber = meta_##TestFunc.line_num; \ | ||
Unity.NumberOfTests++; \ | ||
if (TEST_PROTECT()) { \ | ||
setUp(); \ | ||
TestFunc(self, _null); \ | ||
} \ | ||
if (TEST_PROTECT()) { \ | ||
tearDown(); \ | ||
} \ | ||
UnityConcludeTest(); \ | ||
} | ||
|
||
#endif // #ifndef TEST_COMMON_H |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,8 @@ | |
] | ||
COVERAGE_ARGS = ["-Csetup-args=-Dcoverage=true"] | ||
|
||
CTEST_ARGS = ["-Csetup-args=-Dctest=true"] | ||
|
||
# We assume this script works with any pip version above this. | ||
PIP_MIN_VERSION = "23.1" | ||
|
||
|
@@ -213,6 +215,7 @@ def cmd_build(self): | |
stripped = self.args.get("stripped", False) | ||
sanitize = self.args.get("sanitize") | ||
coverage = self.args.get("coverage", False) | ||
ctest = self.args.get("ctest", False) | ||
if wheel_dir and coverage: | ||
pprint("Cannot pass --wheel and --coverage together", Colors.RED) | ||
sys.exit(1) | ||
Comment on lines
+218
to
221
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid shipping C-test artifacts in wheels by guarding Right now Proposed guard near the existing coverage check: if wheel_dir and coverage:
pprint("Cannot pass --wheel and --coverage together", Colors.RED)
sys.exit(1)
+
+ if wheel_dir and ctest:
+ pprint("Cannot pass --wheel and --ctest together (test-only artifacts)", Colors.RED)
+ sys.exit(1) Also applies to: 263-265 🤖 Prompt for AI Agents
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless @ankith26 overrides me, I want to leave this as-is. It's only included in one CI run, which we don't publish wheels from anyway. However, we might want to occasionally share wheels as developers with the ctest suite enabled There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
@@ -226,6 +229,8 @@ def cmd_build(self): | |
build_suffix += "-sdl3" | ||
if coverage: | ||
build_suffix += "-cov" | ||
if ctest: | ||
build_suffix += "-ctest" | ||
install_args = [ | ||
"--no-build-isolation", | ||
f"-Cbuild-dir=.mesonpy-build{build_suffix}", | ||
|
@@ -255,12 +260,14 @@ def cmd_build(self): | |
if coverage: | ||
install_args.extend(COVERAGE_ARGS) | ||
|
||
if ctest: | ||
install_args.extend(CTEST_ARGS) | ||
|
||
if sanitize: | ||
install_args.append(f"-Csetup-args=-Db_sanitize={sanitize}") | ||
|
||
info_str = ( | ||
f"with {debug=}, {lax=}, {sdl3=}, {stripped=}, {coverage=} and {sanitize=}" | ||
) | ||
info_str = f"with {debug=}, {lax=}, {sdl3=}, {stripped=}, {coverage=}, {ctest=}, and {sanitize=}" | ||
|
||
if wheel_dir: | ||
pprint(f"Building wheel at '{wheel_dir}' ({info_str})") | ||
cmd_run( | ||
|
@@ -416,6 +423,9 @@ def parse_args(self): | |
"supported if the underlying compiler supports the --coverage argument" | ||
), | ||
) | ||
build_parser.add_argument( | ||
"--ctest", action="store_true", help="Build the C-direct unit tests" | ||
) | ||
|
||
# Docs command | ||
docs_parser = subparsers.add_parser("docs", help="Generate docs") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[wrap-git] | ||
url = https://github.com/ThrowTheSwitch/Unity.git | ||
revision = v2.6.1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import unittest | ||
|
||
base_ctest = None | ||
try: | ||
import pygame.base_ctest as base_ctest | ||
except ModuleNotFoundError: | ||
pass | ||
|
||
|
||
class Ctest(unittest.TestCase): | ||
@unittest.skipIf(base_ctest is None, "base_ctest not built") | ||
def test_run_base_ctests(self): | ||
self.assertEqual(base_ctest.run_tests(), 0) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
Uh oh!
There was an error while loading. Please reload this page.