diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..78d457d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,39 @@ +name: Unit Tests + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +jobs: + ci: + name: Run Unit Tests + runs-on: ubuntu-latest + container: + image: barichello/godot-ci:4.2.1 + + steps: + - name: Check out Git repository + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Set up Python (for SCons) + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install scons + run: | + python -m pip install scons==4.0.0 + + - name: Build module + run: | + cd godot-cpp + scons platform=linux target=template_debug + cd .. + scons platform=linux target=template_debug disable_exceptions=no + + - name: Run tests + run: | + set -e + godot --headless -T \ No newline at end of file diff --git a/.gitignore b/.gitignore index a0a4862..43e9524 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,6 @@ bin/*.exp bin/*.lib .sconsign.dblite src/*.obj +tests/*.obj .vscode/ compile_commands.json diff --git a/.gitmodules b/.gitmodules index 8b954ad..829f7cb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = godot-cpp url = https://github.com/godotengine/godot-cpp branch = 4.1 +[submodule "doctest"] + path = doctest + url = https://github.com/doctest/doctest.git diff --git a/SConstruct b/SConstruct index 1db916d..9763f56 100644 --- a/SConstruct +++ b/SConstruct @@ -13,9 +13,13 @@ env = SConscript("godot-cpp/SConstruct") # - LINKFLAGS are for linking flags # tweak this if you want to use different folders, or more folders, to store your source code in. -env.Append(CPPPATH=["src/"]) +env.Append(CPPPATH=["src/","tests/","doctest/doctest/"]) sources = Glob("src/*.cpp") +# If non-release build, compile the test files +if env.debug_features: + sources.extend(Glob("tests/*.cpp")) + if env["platform"] == "macos": library = env.SharedLibrary( "bin/libalife.{}.{}.framework/libalife.{}.{}".format( diff --git a/src/cell.cpp b/src/cell.cpp index d86ae17..c023ad8 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -4,6 +4,8 @@ #include #include +#include "helpers.hpp" + using namespace godot; void Cell::_bind_methods() { @@ -48,9 +50,7 @@ void Cell::_ready() { } void Cell::_process(double delta) { - // Don't run if in editor - if (Engine::get_singleton()->is_editor_hint()) - return; + DONT_RUN_IN_EDITOR; if (_cellState->getAlive()) { // Living Cell behavior diff --git a/src/cell_spawner.cpp b/src/cell_spawner.cpp index 56ecdae..c0c28ce 100644 --- a/src/cell_spawner.cpp +++ b/src/cell_spawner.cpp @@ -3,6 +3,19 @@ #include +#include "helpers.hpp" + +// These includes are for starting testing and should stay with the class we're using as the project entry point +#ifdef DEBUG_ENABLED +#include +#include + +#include +#include + +#include "TestHeader.hpp" +#endif + using namespace godot; void CellSpawner::_bind_methods() { @@ -33,7 +46,10 @@ void CellSpawner::_bind_methods() { } CellSpawner::CellSpawner() { rand.instantiate(); } -CellSpawner::~CellSpawner() {} +CellSpawner::~CellSpawner() { + // Clean up spawned child nodes + queue_free(); +} void CellSpawner::setCellScene(const Ref cellScene) { _cellScene = cellScene; @@ -88,7 +104,24 @@ void CellSpawner::spawnCell() { } void CellSpawner::_ready() { - // Don't run if in editor - if (Engine::get_singleton()->is_editor_hint()) - return; + DONT_RUN_IN_EDITOR; + + // If not in a release build, check for custom cmdline arg to run tests, + // forwarding additional user args into doctest as its args +#ifdef DEBUG_ENABLED + for (auto arg : OS::get_singleton()->get_cmdline_args()) { + if (arg == "--runTests" || arg == "-T") { + // Collect user command line args to pass into doctest + std::vector userArgs; + for (godot::String arg : OS::get_singleton()->get_cmdline_user_args()) { + const char *charArg = arg.ascii().get_data(); + userArgs.push_back(charArg); + } + + // Run the tests, exit, and return the value doctest returned + int retVal = doctest_run(userArgs.size(), userArgs.data()); + get_tree()->quit(retVal); + } + } +#endif } \ No newline at end of file diff --git a/src/cell_spawner.hpp b/src/cell_spawner.hpp index bd3affa..6897c38 100644 --- a/src/cell_spawner.hpp +++ b/src/cell_spawner.hpp @@ -1,12 +1,12 @@ #pragma once #include -#include #include #include #include #include #include +#include namespace godot { diff --git a/src/fps_counter.cpp b/src/fps_counter.cpp index 55d0be4..890ac1f 100644 --- a/src/fps_counter.cpp +++ b/src/fps_counter.cpp @@ -2,6 +2,8 @@ #include +#include "helpers.hpp" + using namespace godot; void FpsCounter::_bind_methods() {} @@ -10,9 +12,7 @@ FpsCounter::FpsCounter() {} FpsCounter::~FpsCounter() {} void FpsCounter::_process(double delta) { - // Don't run if in editor - if (Engine::get_singleton()->is_editor_hint()) - return; + DONT_RUN_IN_EDITOR; const String fps = "FPS " + String::num(Engine::get_singleton()->get_frames_per_second()); diff --git a/src/helpers.hpp b/src/helpers.hpp new file mode 100644 index 0000000..07170f6 --- /dev/null +++ b/src/helpers.hpp @@ -0,0 +1,10 @@ +#pragma once + +#ifdef DEBUG_ENABLED +#define DONT_RUN_IN_EDITOR \ + if (Engine::get_singleton()->is_editor_hint()) { \ + return; \ + } +#else +#define DONT_RUN_IN_EDITOR +#endif \ No newline at end of file diff --git a/src/start_button.cpp b/src/start_button.cpp index 4fa80b3..9dbe8da 100644 --- a/src/start_button.cpp +++ b/src/start_button.cpp @@ -2,6 +2,8 @@ #include +#include "helpers.hpp" + using namespace godot; void StartButton::_bind_methods() { @@ -16,7 +18,7 @@ StartButton::~StartButton() { // _pressed is used as the function for when the button is pressed // This button is also switching the button off once it gets pressed once. void StartButton::_pressed() { - CellSpawner *parent = (CellSpawner *)this->get_parent(); + CellSpawner *parent = static_cast(this->get_parent()); for (int i = 0; i < parent->getNumCells(); i++) { parent->spawnCell(); } @@ -26,9 +28,7 @@ void StartButton::_pressed() { // Mainly used for setting diffrent values for the button but these could be done in the godot client instead // void StartButton::_ready() { - // Don't run if in editor - if (Engine::get_singleton()->is_editor_hint()) - return; + DONT_RUN_IN_EDITOR; this->set_text("Start"); } \ No newline at end of file diff --git a/src/start_button.hpp b/src/start_button.hpp index 85f0b61..31baa7e 100644 --- a/src/start_button.hpp +++ b/src/start_button.hpp @@ -1,8 +1,8 @@ #pragma once #include "cell_spawner.hpp" -#include #include +#include namespace godot { diff --git a/src/stats_counter.cpp b/src/stats_counter.cpp index 4d96b70..691677d 100644 --- a/src/stats_counter.cpp +++ b/src/stats_counter.cpp @@ -3,6 +3,8 @@ #include +#include "helpers.hpp" + using namespace godot; void StatsCounter::_bind_methods() {} @@ -11,9 +13,7 @@ StatsCounter::StatsCounter() {} StatsCounter::~StatsCounter() {} void StatsCounter::_process(double delta) { - // Don't run if in editor - if (Engine::get_singleton()->is_editor_hint()) - return; + DONT_RUN_IN_EDITOR; const String stats = "COLLISIONS " + String::num(Cell::CollisionCount); set_text(stats); diff --git a/tests/TestCellSpawner.cpp b/tests/TestCellSpawner.cpp new file mode 100644 index 0000000..5ab643b --- /dev/null +++ b/tests/TestCellSpawner.cpp @@ -0,0 +1,36 @@ +#include "TestHeader.hpp" + +#include "cell_spawner.hpp" + +#include + +namespace godot { + +TEST_CASE("Dummy") { + CHECK(1 == 1); +} + +TEST_CASE("Test CellSpawner") { + CellSpawner CellSpawner{}; + + REQUIRE(CellSpawner.getMinForce() == 50.0); + REQUIRE(CellSpawner.getMaxForce() == 150.0); + + SUBCASE("Test setting min force") { + CellSpawner.setMinForce(40.0); + CHECK(CellSpawner.getMinForce() == 40.0); + + CellSpawner.setMinForce(CellSpawner.getMaxForce() + 1); + CHECK(CellSpawner.getMinForce() == CellSpawner.getMaxForce()); + } + + SUBCASE("Test setting max force") { + CellSpawner.setMaxForce(160.0); + CHECK(CellSpawner.getMaxForce() == 160.0); + + CellSpawner.setMaxForce(CellSpawner.getMinForce() - 1); + CHECK(CellSpawner.getMaxForce() == CellSpawner.getMinForce()); + } +} + +} //namespace godot \ No newline at end of file diff --git a/tests/TestHeader.hpp b/tests/TestHeader.hpp new file mode 100644 index 0000000..e466fcf --- /dev/null +++ b/tests/TestHeader.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include "doctest.h" + +int doctest_run(const int, const char *const *); \ No newline at end of file diff --git a/tests/TestMain.cpp b/tests/TestMain.cpp new file mode 100644 index 0000000..78ad31e --- /dev/null +++ b/tests/TestMain.cpp @@ -0,0 +1,15 @@ +// Can only be used in one test file. Do not copy!! +#define DOCTEST_CONFIG_IMPLEMENT + +#include "TestHeader.hpp" + +#include +#include + +int doctest_run(const int argc, const char *const *argv) { + doctest::Context context; + context.applyCommandLine(argc, argv); + int res = context.run(); + + return res; +} \ No newline at end of file