Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ cmake --build build -j
cmake --install build
```

Models are **not** built by default — pass `-DROSS_BUILD_MODELS=ON` to include `phold` and any symlinked-in models. `BUILD_TESTING=ON` enables the ctest suite.
Models are **not** built by default — pass `-DROSS_BUILD_MODELS=ON` to include `phold` and any symlinked-in models. Test discovery is gated by `ROSS_BUILD_TESTING` (defaults ON at top level, inherits from `BUILD_TESTING` if the parent set it; defaults OFF under `add_subdirectory()`/`FetchContent` — parent must opt in).

MPI is required and auto-discovered via `find_package(MPI)` — do not set `CC=mpicc` or `-DCMAKE_C_COMPILER=mpicc`. For non-standard installs, hint with `-DMPI_HOME=...` or `module load <mpi>` before configuring. The top-level `CMakeLists.txt` does per-arch detection via `CMAKE_SYSTEM_PROCESSOR` and falls back to a `gtod` clock if unrecognized. Set `-DROSS_CLOCK_OVERRIDE=YES` to force the gtod clock.

Expand Down Expand Up @@ -42,6 +42,8 @@ ctest --test-dir build -V -R <name> # with output

Tests live in `models/phold/CMakeLists.txt` and are generated by the `ROSS_TEST_SCHEDULERS` and `ROSS_TEST_INSTRUMENTATION` functions in `models/CMakeLists.txt`. Each new model variant (e.g. `phold_comm_test`, `phold_gvt_hook_test`) gets the full scheduler matrix applied via these functions. When adding a model or test variant, call these helpers rather than hand-writing `ADD_TEST`.

`ROSS_BUILD_TESTING` is the gate for ctest discovery: it controls whether `include(CTest)` runs at all, and the helpers above early-return when it's OFF so models build without registering their tests. Phold binaries still build under `-DROSS_BUILD_TESTING=OFF -DROSS_BUILD_MODELS=ON`; only test registration is suppressed.

## Architecture

### Simulation hierarchy (top to bottom)
Expand Down
27 changes: 24 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ include(GNUInstallDirs)

# ROSS Configuration Options

ENABLE_TESTING()
INCLUDE(CTest)

# RPATH policy is set only when ROSS is the top-level project. When ROSS is
# consumed via add_subdirectory()/FetchContent, the parent project owns
# install-RPATH and the global CMAKE_INSTALL_RPATH; mutating those from a
Expand Down Expand Up @@ -221,6 +218,30 @@ endif()
# ROSS Core code
ADD_SUBDIRECTORY(core)

# Test suite gating. Project-scoped knob so:
# - top-level builds preserve today's UX (default ON; `-DBUILD_TESTING=OFF`
# still suppresses tests by feeding the default)
# - subdirectory consumption defaults OFF (parent must opt in explicitly)
# Once set, ROSS_BUILD_TESTING is the sole gate on include(CTest) and on
# the add_test() registrations inside ROSS_TEST_SCHEDULERS /
# ROSS_TEST_INSTRUMENTATION (see models/CMakeLists.txt).
if(PROJECT_IS_TOP_LEVEL)
if(DEFINED BUILD_TESTING)
set(_ross_build_testing_default ${BUILD_TESTING})
else()
set(_ross_build_testing_default ON)
endif()
option(ROSS_BUILD_TESTING "Build ROSS test suite" ${_ross_build_testing_default})
unset(_ross_build_testing_default)
else()
option(ROSS_BUILD_TESTING "Build ROSS test suite" OFF)
endif()

if(ROSS_BUILD_TESTING)
include(CTest)
add_subdirectory(tests)
endif()

# ROSS Models directory
OPTION(ROSS_BUILD_MODELS "Build ROSS models?" OFF)
IF(ROSS_BUILD_MODELS)
Expand Down
10 changes: 10 additions & 0 deletions Documentation/dev/ross-build-testing-knob.build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Added a project-scoped `ROSS_BUILD_TESTING` option that gates
`include(CTest)` and the test-registration helpers. Defaults to
`BUILD_TESTING`'s value when ROSS is the top-level project, so
`cmake -S . -B build` still runs tests and `-DBUILD_TESTING=OFF` still
suppresses them. Defaults `OFF` when ROSS is consumed via
`add_subdirectory()` or `FetchContent`, so a parent project's test
suite is no longer mixed with ROSS's by default — parents that want
ROSS's tests opt in with `-DROSS_BUILD_TESTING=ON`. Phold binaries
still build under `-DROSS_BUILD_TESTING=OFF -DROSS_BUILD_MODELS=ON`;
only test registration is suppressed.
10 changes: 10 additions & 0 deletions models/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## BASIC SCHEDULERS
FUNCTION(ROSS_TEST_SCHEDULERS target_name)
# if a parent superbuild has called
# enable_testing() at its own scope, add_test() here would otherwise
# register against that scope even with ROSS_BUILD_TESTING=OFF.
if(NOT ROSS_BUILD_TESTING)
return()
endif()
ADD_TEST(${target_name}_SCHED_Sequential ${target_name} --synch=1)
ADD_TEST(${target_name}_SCHED_Conservative mpirun -np 2 ./${target_name} --synch=2)
ADD_TEST(${target_name}_SCHED_Optimistic mpirun -np 2 ./${target_name} --synch=3 --extramem=100000)
Expand All @@ -18,6 +24,10 @@ ENDFUNCTION(ROSS_TEST_SCHEDULERS)

## INSTRUMENTATION TESTS
FUNCTION(ROSS_TEST_INSTRUMENTATION target_name)
# See ROSS_TEST_SCHEDULERS above for why this guard exists.
if(NOT ROSS_BUILD_TESTING)
return()
endif()
ADD_TEST(${target_name}_INST_OptPE mpirun -np 2 ./${target_name} --synch=3 --engine-stats=4 --event-trace=2 --extramem=100000 --vt-interval=10000 --vt-samp-end=100000)
ADD_TEST(${target_name}_INST_OptLP mpirun -np 2 ./${target_name} --synch=3 --engine-stats=4 --event-trace=2 --kp-data=1 --lp-data=1 --extramem=100000 --vt-interval=10000 --vt-samp-end=100000)

Expand Down
55 changes: 55 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Subsystem driver tests. Each driver is a complete ROSS program (calls
# tw_init / tw_run / tw_end itself) that exercises one piece of the
# engine in isolation. See improve-testing.md for design rationale.
#
# This file is reached via add_subdirectory(tests) at top-level, itself
# gated on ROSS_BUILD_TESTING -- so by the time we get here, the gate
# is on. Same pattern as ROSS_TEST_SCHEDULERS.

# ROSS_DRIVER_TEST(<name> [SCHEDULERS <Sequential|OptDebug|RollbackCheck>...])
#
# Default scheduler set is all three single-rank schedulers. Override
# with SCHEDULERS when a driver's event-handler side effects don't
# survive replay under --synch=4/6 (e.g. drivers with assertions in
# the handler whose state they mutate).
function(ROSS_DRIVER_TEST name)
if(NOT ROSS_BUILD_TESTING)
return()
endif()

cmake_parse_arguments(ARG "" "" "SCHEDULERS" ${ARGN})
if(NOT ARG_SCHEDULERS)
set(ARG_SCHEDULERS Sequential OptDebug RollbackCheck)
endif()

add_executable(${name}
drivers/${name}.c
helpers/test_lp_minimal.c)
target_include_directories(${name} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/helpers)
target_link_libraries(${name} PRIVATE ROSS::ROSS)

foreach(sched IN LISTS ARG_SCHEDULERS)
if(sched STREQUAL "Sequential")
add_test(NAME ${name}_SCHED_Sequential
COMMAND $<TARGET_FILE:${name}> --synch=1)
elseif(sched STREQUAL "OptDebug")
add_test(NAME ${name}_SCHED_OptDebug
COMMAND $<TARGET_FILE:${name}> --synch=4 --nkp=1)
elseif(sched STREQUAL "RollbackCheck")
add_test(NAME ${name}_SCHED_RollbackCheck
COMMAND $<TARGET_FILE:${name}> --synch=6 --nkp=1)
else()
message(FATAL_ERROR
"ROSS_DRIVER_TEST(${name}): unknown scheduler '${sched}'")
endif()
set_tests_properties(${name}_SCHED_${sched} PROPERTIES TIMEOUT 30)
endforeach()
endfunction()

ROSS_DRIVER_TEST(lifecycle_test)
ROSS_DRIVER_TEST(rand_reverse_test)

# Splay's event handler asserts and mutates LP state (last_ts,
# delivery_count) without a paired reverse handler. Sequential only.
ROSS_DRIVER_TEST(splay_order_test SCHEDULERS Sequential)
23 changes: 23 additions & 0 deletions tests/drivers/lifecycle_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Lifecycle smoke driver: tw_init -> register one minimal LP -> tw_run
* with zero scheduled events -> tw_end. The only assertion is the exit
* code -- if any of init / run / end aborts, ctest fails. Caught here
* means caught the moment it appears, not when phold's teardown
* happens to expose it.
*/

#include "test_lp_minimal.h"

int
main(int argc, char **argv)
{
tw_init(&argc, &argv);

tw_define_lps(1, sizeof(test_lp_minimal_message));
tw_lp_settype(0, &test_lp_minimal_type[0]);

tw_run();
tw_end();

return 0;
}
92 changes: 92 additions & 0 deletions tests/drivers/rand_reverse_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* rand-clcg4 reverse-symmetry driver.
*
* Forward draws on a tw_rng_stream are reversed via tw_rand_reverse_unif
* (the only public reverse; tw_rand_integer/exponential/ulong each cost
* one unif draw, so they reverse with one reverse_unif). After N forward
* + N reverse calls the stream's current seed (Cg) and draw counter must
* be byte-identical to the pre-draw snapshot. Asserting this directly
* catches rand-clcg4 arithmetic regressions that phold's specific draw
* pattern might not happen to tickle.
*
* Run before tw_run() so the engine's own RNG use doesn't enter the
* picture -- this isolates the test to rand-clcg4 itself.
*/

#include <string.h>

#include "test_assert.h"
#include "test_lp_minimal.h"

typedef struct rng_snapshot {
int32_t Cg[4];
unsigned long count;
} rng_snapshot;

static void
snapshot_rng(rng_snapshot *out, const tw_rng_stream *g)
{
memcpy(out->Cg, g->Cg, sizeof(out->Cg));
out->count = g->count;
}

static int
rng_matches(const rng_snapshot *before, const tw_rng_stream *now)
{
return memcmp(before->Cg, now->Cg, sizeof(before->Cg)) == 0
&& before->count == now->count;
}

int
main(int argc, char **argv)
{
tw_init(&argc, &argv);

tw_define_lps(1, sizeof(test_lp_minimal_message));
tw_lp_settype(0, &test_lp_minimal_type[0]);

tw_rng_stream *rng = &g_tw_lp[0]->rng[0];
rng_snapshot before;

/* Bulk unif <-> reverse_unif symmetry. The bedrock invariant. */
snapshot_rng(&before, rng);
for (int i = 0; i < 1000; i++)
tw_rand_unif(rng);
for (int i = 0; i < 1000; i++)
tw_rand_reverse_unif(rng);
ROSS_TEST_ASSERT(rng_matches(&before, rng),
"unif/reverse_unif asymmetry after 1000 round-trips: "
"Cg before=[%d,%d,%d,%d] after=[%d,%d,%d,%d] count_delta=%ld",
before.Cg[0], before.Cg[1], before.Cg[2], before.Cg[3],
rng->Cg[0], rng->Cg[1], rng->Cg[2], rng->Cg[3],
(long)(rng->count - before.count));

/* Each of these is documented to cost exactly one rng_gen_val call;
* the assertion catches a regression where one of them silently
* starts drawing more (or fewer) than once. */
snapshot_rng(&before, rng);
(void) tw_rand_integer(rng, 0, 1000);
tw_rand_reverse_unif(rng);
ROSS_TEST_ASSERT(rng_matches(&before, rng),
"tw_rand_integer not one unif draw: count_delta=%ld",
(long)(rng->count - before.count));

snapshot_rng(&before, rng);
(void) tw_rand_exponential(rng, 1.0);
tw_rand_reverse_unif(rng);
ROSS_TEST_ASSERT(rng_matches(&before, rng),
"tw_rand_exponential not one unif draw: count_delta=%ld",
(long)(rng->count - before.count));

snapshot_rng(&before, rng);
(void) tw_rand_ulong(rng, 0, 1000);
tw_rand_reverse_unif(rng);
ROSS_TEST_ASSERT(rng_matches(&before, rng),
"tw_rand_ulong not one unif draw: count_delta=%ld",
(long)(rng->count - before.count));

tw_run();
tw_end();

return 0;
}
106 changes: 106 additions & 0 deletions tests/drivers/splay_order_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Splay queue ordering driver.
*
* Schedules a known set of self-events on a single LP with a mix of
* distinct and tied timestamps in non-sorted enqueue order, then
* verifies the splay queue delivers them with monotonically
* non-decreasing recv_ts. The exact order of tied events depends on
* USE_RAND_TIEBREAKER and the LP's RNG state, so we don't pin that --
* only the bedrock priority-queue invariant (no smaller-ts event
* delivered after a larger-ts one) plus exact delivery count.
*
* Sequential only (--synch=1). The event handler mutates LP state
* without a reverse handler, so replay under --synch=4/6 would
* scramble the test's own bookkeeping.
*/

#include <ross.h>

#include "test_assert.h"

typedef struct splay_state {
tw_stime last_ts;
int delivery_count;
} splay_state;

typedef struct splay_message {
int sched_index;
} splay_message;

/* 20 events: mix of distinct timestamps (so ordering is checkable)
* and ties (so the comparator's tie path is exercised), enqueued in
* deliberately non-sorted order to force the splay tree to balance
* across both branches on each insert. */
static const double schedule[] = {
5.0, 1.0, 9.0, 3.0, 7.0, 2.0, 8.0, 4.0, 6.0, 10.0,
5.0, 1.0, 3.0, 7.0, 1.0, 5.0, 9.0, 3.0, 1.0, 5.0
};
#define N_EVENTS (sizeof(schedule) / sizeof(schedule[0]))

static void
splay_init(splay_state *s, tw_lp *lp)
{
s->last_ts = 0.0;
s->delivery_count = 0;

for (unsigned int i = 0; i < N_EVENTS; i++) {
tw_event *e = tw_event_new(lp->gid, schedule[i], lp);
splay_message *m = (splay_message *) tw_event_data(e);
m->sched_index = (int) i;
tw_event_send(e);
}
}

static void
splay_event(splay_state *s, tw_bf *bf, splay_message *m, tw_lp *lp)
{
(void) bf;

tw_stime now = tw_now(lp);
ROSS_TEST_ASSERT(now >= s->last_ts,
"delivery #%d out of order: now=%g last_ts=%g sched_index=%d",
s->delivery_count, now, s->last_ts, m->sched_index);
s->last_ts = now;
s->delivery_count++;
}

static void
splay_final(splay_state *s, tw_lp *lp)
{
(void) lp;
ROSS_TEST_ASSERT(s->delivery_count == (int) N_EVENTS,
"delivered %d events, expected %d",
s->delivery_count, (int) N_EVENTS);
}

static tw_peid
splay_map(tw_lpid gid)
{
return (tw_peid) gid / g_tw_nlp;
}

static tw_lptype splay_lp_type[] = {
{(init_f) splay_init,
(pre_run_f) NULL,
(event_f) splay_event,
(revent_f) NULL,
(commit_f) NULL,
(final_f) splay_final,
(map_f) splay_map,
sizeof(splay_state)},
{0},
};

int
main(int argc, char **argv)
{
tw_init(&argc, &argv);

tw_define_lps(1, sizeof(splay_message));
tw_lp_settype(0, &splay_lp_type[0]);

tw_run();
tw_end();

return 0;
}
Loading
Loading