From d3a1b908fd3b0c47da2c33c16d18ebeb00da3b97 Mon Sep 17 00:00:00 2001 From: hachem Date: Fri, 4 Sep 2026 05:46:36 +0200 Subject: refactor: reorganize tests --- test/.gitignore | 7 +++++ test/Makefile | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++ test/hello.cpp | 16 +++++++++++ test/instr_demo.c | 51 ++++++++++++++++++++++++++++++++++ test/macro_demo.c | 47 ++++++++++++++++++++++++++++++++ test/memory_demo.cpp | 31 +++++++++++++++++++++ test/sample_demo.cpp | 47 ++++++++++++++++++++++++++++++++ test/trace_demo.cpp | 49 +++++++++++++++++++++++++++++++++ 8 files changed, 325 insertions(+) create mode 100644 test/.gitignore create mode 100644 test/Makefile create mode 100644 test/hello.cpp create mode 100644 test/instr_demo.c create mode 100644 test/macro_demo.c create mode 100644 test/memory_demo.cpp create mode 100644 test/sample_demo.cpp create mode 100644 test/trace_demo.cpp (limited to 'test') diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..d15c2a1 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,7 @@ +* +!.gitignore +!Makefile +!*.c +!*.cpp +!*.h +!*.hpp diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..d8401b5 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,77 @@ +# hsdbg test workspace +# +# Targets: +# hello a trivial program, for a first launch and single-stepping +# macro_demo layered #define macros, for the macro unwrapper +# trace_demo nested calls, for the flame-chart timeline +# sample_demo plain busywork, for the sampling profiler +# memory_demo allocation waves, for the resident-memory graph +# instr_demo -finstrument-functions + the trace runtime, auto-traced + +UNAME := $(shell uname) + +CC ?= clang +CXX ?= clang++ + +CFLAGS := -g -O0 -Wall -Wextra +CXXFLAGS := -g -O0 -Wall -Wextra -std=c++23 + +OBJ := .obj +TRACE_RUNTIME := ../ext/hsdbg_trace/hsdbg_trace.c + +TARGETS := hello macro_demo trace_demo sample_demo memory_demo instr_demo + +ifeq ($(UNAME),Darwin) + DSYMUTIL := dsymutil +else + DSYMUTIL := true +endif + +all: $(TARGETS) + +$(OBJ): + mkdir -p $(OBJ) + +$(OBJ)/%.o: %.cpp | $(OBJ) + $(CXX) $(CXXFLAGS) -c $< -o $@ + +$(OBJ)/%.o: %.c | $(OBJ) + $(CC) $(CFLAGS) -c $< -o $@ + +hello: $(OBJ)/hello.o + $(CXX) $(CXXFLAGS) $^ -o $@ + $(DSYMUTIL) $@ + +macro_demo: $(OBJ)/macro_demo.o + $(CC) $(CFLAGS) $^ -o $@ + $(DSYMUTIL) $@ + +trace_demo: $(OBJ)/trace_demo.o + $(CXX) $(CXXFLAGS) $^ -o $@ + $(DSYMUTIL) $@ + +sample_demo: $(OBJ)/sample_demo.o + $(CXX) $(CXXFLAGS) $^ -o $@ + $(DSYMUTIL) $@ + +memory_demo: $(OBJ)/memory_demo.o + $(CXX) $(CXXFLAGS) $^ -o $@ + $(DSYMUTIL) $@ + +# instr_demo.c is compiled with -finstrument-functions so every function calls +# the enter/exit hooks; the trace runtime that records them is compiled WITHOUT +# the flag (instrumenting the hooks themselves would recurse forever) +$(OBJ)/instr_demo.o: instr_demo.c | $(OBJ) + $(CC) $(CFLAGS) -finstrument-functions -c $< -o $@ + +$(OBJ)/hsdbg_trace.o: $(TRACE_RUNTIME) | $(OBJ) + $(CC) $(CFLAGS) -c $< -o $@ + +instr_demo: $(OBJ)/instr_demo.o $(OBJ)/hsdbg_trace.o + $(CC) $(CFLAGS) $^ -o $@ + $(DSYMUTIL) $@ + +clean: + rm -rf $(TARGETS) *.dSYM $(OBJ) + +.PHONY: all clean diff --git a/test/hello.cpp b/test/hello.cpp new file mode 100644 index 0000000..9337000 --- /dev/null +++ b/test/hello.cpp @@ -0,0 +1,16 @@ +#include + +auto add(int a, int b) -> int +{ + int result = a+b; + return result; +} + +auto main() -> int +{ + int a = 50; + int b = 67; + int c = add(a, b); + + std::println("{} + {} = {}", a, b, c); +} diff --git a/test/instr_demo.c b/test/instr_demo.c new file mode 100644 index 0000000..7983b82 --- /dev/null +++ b/test/instr_demo.c @@ -0,0 +1,51 @@ +// a fully instrumented demo: built with -finstrument-functions so EVERY function +// here is traced automatically, no hand-picking. kept in plain C with only +// printf and nanosleep so the trace is exactly these functions +#include +#include + +static void nap(long ms) +{ + struct timespec ts = { ms / 1000, (ms % 1000) * 1000000L }; + nanosleep(&ts, NULL); +} + +static long leaf(int n) +{ + long total = 0; + for (int i = 0; i < n * 40000; ++i) + total += i % 7; + + return total; +} + +static long inner(int round) +{ + long total = 0; + for (int i = 1; i <= 3; ++i) + total += leaf(round % 4 + i); + + return total; +} + +static long outer(int round) +{ + const long a = inner(round); + nap(20); + const long b = inner(round + 1); + return a + b; +} + +int main(void) +{ + for (int round = 0; round < 15; ++round) + { + const long result = outer(round); + printf("round %d -> %ld\n", round, result); + fflush(stdout); + nap(120); + } + + puts("done"); + return 0; +} diff --git a/test/macro_demo.c b/test/macro_demo.c new file mode 100644 index 0000000..072ec02 --- /dev/null +++ b/test/macro_demo.c @@ -0,0 +1,47 @@ +// a playground for the macro unwrapper. open this file in hsdbg's source view, +// then hover any highlighted macro for a preview and click it to step through +// the expansion one layer at a time in the macros panel. + +#include + +// object-like macros that lean on each other, so unrolling takes real layers: +// KIB -> (1 << 10), MIB -> (KIB * KIB) -> ((1 << 10) * (1 << 10)), and so on +#define KIB (1 << 10) +#define MIB (KIB * KIB) +#define GIB (MIB * KIB) + +// function-like macros, including one built out of another +#define SQUARE(x) ((x) * (x)) +#define CUBE(x) (SQUARE(x) * (x)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define CLAMP(v, lo, hi) MAX(lo, MIN(v, hi)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +// the classic stringize-through-indirection: STR sees its argument raw, XSTR +// expands it first. hover both to see "VERSION" vs "3" +#define VERSION 3 +#define STR(x) #x +#define XSTR(x) STR(x) + +// token pasting, direct and through a level of indirection +#define CAT(a, b) a ## b +#define XCAT(a, b) CAT(a, b) + +// a variadic logging macro +#define LOG(level, fmt, ...) fprintf(stderr, "[" level "] " fmt "\n", __VA_ARGS__) + +int main(void) +{ + int width = 5; + int height = 9; + + printf("a mebibyte is %d bytes\n", MIB); + printf("square %d, cube %d\n", SQUARE(width), CUBE(width)); + printf("clamped %d\n", CLAMP(height, 0, 8)); + printf("building version %s\n", XSTR(VERSION)); + + int XCAT(sum_, 1) = width + height; + LOG("info", "sum is %d", XCAT(sum_, 1)); + + return 0; +} diff --git a/test/memory_demo.cpp b/test/memory_demo.cpp new file mode 100644 index 0000000..22c6b44 --- /dev/null +++ b/test/memory_demo.cpp @@ -0,0 +1,31 @@ +// a small demo target for the profiler: allocates and frees memory in slow +// waves so the resident-memory graph has something to show +#include +#include +#include +#include + +auto main() -> int +{ + std::vector> blocks; + + for (int cycle = 0; cycle < 6; ++cycle) + { + // ramp up: grab ~40 MB over two seconds + for (int i = 0; i < 40; ++i) + { + blocks.emplace_back(1024 * 1024, static_cast(i)); + std::printf("cycle %d: allocated %zu MB\n", cycle, blocks.size()); + std::fflush(stdout); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + // hold for a moment, then release everything and start over + std::this_thread::sleep_for(std::chrono::seconds(1)); + blocks.clear(); + blocks.shrink_to_fit(); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + std::puts("done"); +} diff --git a/test/sample_demo.cpp b/test/sample_demo.cpp new file mode 100644 index 0000000..a3b1fa8 --- /dev/null +++ b/test/sample_demo.cpp @@ -0,0 +1,47 @@ +// a plain, un-instrumented target for the sampling profiler: NOTHING added to the +// build, no flags, no linked runtime. functions do enough work (tens of ms) that +// the sampler catches them. compile normally: clang++ -g sample_demo.cpp -o sample_demo +#include + +// a spin of roughly the requested milliseconds of busywork +static volatile long sink = 0; +static void burn(int ms) +{ + // ~2 million iterations per ms is a rough guess; exact timing does not matter + for (long i = 0; i < ms * 800000L; ++i) + sink += i % 7; +} + +static void deep(int round) +{ + burn(25 + round % 3 * 15); +} + +static void phase_b(int round) +{ + burn(20); + deep(round); +} + +static void phase_a(int round) +{ + burn(40); +} + +int main() +{ + for (int round = 0; round < 400; ++round) + { + phase_a(round); + phase_b(round); + + if (round % 10 == 0) + { + printf("round %d\n", round); + fflush(stdout); + } + } + + puts("done"); + return 0; +} diff --git a/test/trace_demo.cpp b/test/trace_demo.cpp new file mode 100644 index 0000000..d5dbce0 --- /dev/null +++ b/test/trace_demo.cpp @@ -0,0 +1,49 @@ +// a demo target for the flame-chart timeline: a nest of functions that call one +// another so the timeline has real depth. main is the outermost (bottom row), +// each callee stacks a row higher +#include +#include +#include + +// innermost: a chunk of busywork so the leaf calls have visible width +auto leaf(int n) -> long +{ + long total = 0; + for (int i = 0; i < n * 60000; ++i) + total += i % 7; + + return total; +} + +// calls leaf a few times +auto inner(int round) -> long +{ + long total = 0; + for (int i = 1; i <= 3; ++i) + total += leaf(round % 4 + i); + + return total; +} + +// calls inner, one level below main +auto outer(int round) -> long +{ + const long a = inner(round); + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + const long b = inner(round + 1); + return a + b; +} + +auto main() -> int +{ + for (int round = 0; round < 20; ++round) + { + const long result = outer(round); + std::printf("round %d -> %ld\n", round, result); + std::fflush(stdout); + + std::this_thread::sleep_for(std::chrono::milliseconds(150)); + } + + std::puts("done"); +} -- cgit v1.3