Add the foundational infrastructure for SCHED_DEADLINE scheduler tests
following the pattern established by sched_ext selftests. This provides
a clean, extensible framework for testing SCHED_DEADLINE functionality.

The framework uses ELF constructors for automatic test registration,
allowing new tests to be added simply by defining a struct dl_test and
calling REGISTER_DL_TEST(). The runner discovers all registered tests
at runtime and executes them serially, providing a simple and consistent
way to expand test coverage without modifying the test runner itself.

The framework provides a complete test lifecycle with setup, run, and
cleanup phases for each test. Tests can be filtered by name, listed for
inspection, or run in quiet mode for automated testing environments. The
framework includes assertion macros such as DL_EQ and DL_FAIL_IF to
simplify test authoring and provide consistent error reporting. Signal
handling ensures graceful interruption and cleanup when tests are
terminated early.

This commit establishes the framework without any actual tests. Tests
will be added in subsequent patches. The framework design is inspired by
tools/testing/selftests/sched_ext/ but tailored for SCHED_DEADLINE
testing needs.

Assisted-by: Claude Code: claude-sonnet-4-5@20250929
Signed-off-by: Juri Lelli <[email protected]>
---
 tools/testing/selftests/sched/deadline/.gitignore |   3 +
 tools/testing/selftests/sched/deadline/Makefile   |  34 ++++
 tools/testing/selftests/sched/deadline/dl_test.h  | 238 ++++++++++++++++++++++
 tools/testing/selftests/sched/deadline/runner.c   | 219 ++++++++++++++++++++
 4 files changed, 494 insertions(+)

diff --git a/tools/testing/selftests/sched/deadline/.gitignore 
b/tools/testing/selftests/sched/deadline/.gitignore
new file mode 100644
index 0000000000000..503a1a968f952
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/.gitignore
@@ -0,0 +1,3 @@
+runner
+cpuhog
+*.o
diff --git a/tools/testing/selftests/sched/deadline/Makefile 
b/tools/testing/selftests/sched/deadline/Makefile
new file mode 100644
index 0000000000000..fd57794f1a543
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/Makefile
@@ -0,0 +1,34 @@
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_GEN_PROGS := runner
+
+# override lib.mk's default rules
+OVERRIDE_TARGETS := 1
+include ../../lib.mk
+
+CFLAGS += -Wall -O2 -g -pthread
+
+OUTPUT_DIR := $(OUTPUT)
+
+# Utility object files
+UTIL_OBJS :=
+
+# Test object files (all .c files except runner.c, dl_util.c, cpuhog.c)
+# Will be populated as we add tests
+TEST_OBJS :=
+
+# Runner binary links utility and test objects
+$(OUTPUT)/runner: runner.c $(UTIL_OBJS) $(TEST_OBJS) dl_test.h | $(OUTPUT_DIR)
+       $(CC) $(CFLAGS) -o $@ runner.c $(UTIL_OBJS) $(TEST_OBJS) $(LDFLAGS)
+
+$(OUTPUT_DIR):
+       mkdir -p $@
+
+.PHONY: all clean
+
+all: $(TEST_GEN_PROGS)
+
+clean:
+       rm -f $(OUTPUT)/runner
+       rm -f $(OUTPUT)/*.o
+       rm -f *.o
diff --git a/tools/testing/selftests/sched/deadline/dl_test.h 
b/tools/testing/selftests/sched/deadline/dl_test.h
new file mode 100644
index 0000000000000..545fae1af6631
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/dl_test.h
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SCHED_DEADLINE Test Framework
+ *
+ * Provides infrastructure for testing SCHED_DEADLINE scheduler functionality.
+ * Tests register themselves using REGISTER_DL_TEST() macro and are
+ * automatically discovered by the runner at runtime.
+ */
+
+#ifndef __DL_TEST_H__
+#define __DL_TEST_H__
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Test status return codes */
+enum dl_test_status {
+       DL_TEST_PASS = 0,       /* Test passed successfully */
+       DL_TEST_SKIP,           /* Test skipped (feature unavailable, etc.) */
+       DL_TEST_FAIL,           /* Test failed */
+};
+
+/**
+ * struct dl_test - Deadline scheduler test definition
+ * @name: Short test identifier (e.g., "basic_scheduling")
+ * @description: Human-readable description of what the test validates
+ * @setup: Optional callback to prepare test environment
+ * @run: Required callback to execute test logic
+ * @cleanup: Optional callback to cleanup test resources
+ *
+ * Tests are defined by filling in this structure and registering with
+ * REGISTER_DL_TEST(). The runner will discover and execute all registered
+ * tests automatically.
+ */
+struct dl_test {
+       /**
+        * name - The name of the test
+        *
+        * Short identifier used for filtering and reporting. Should be
+        * lowercase with underscores (e.g., "bandwidth_admission").
+        */
+       const char *name;
+
+       /**
+        * description - Human-readable test description
+        *
+        * Explains what the test validates and why it's important.
+        * Displayed when test runs unless quiet mode is enabled.
+        */
+       const char *description;
+
+       /**
+        * setup - Optional setup callback
+        * @ctx: Pointer to context pointer, can be set to pass data to run()
+        *
+        * Called before run() to prepare test environment. Can allocate
+        * resources, check prerequisites, etc.
+        *
+        * Return:
+        * - DL_TEST_PASS: Continue to run()
+        * - DL_TEST_SKIP: Skip this test (feature not available, etc.)
+        * - DL_TEST_FAIL: Abort test (setup failed)
+        *
+        * If setup() returns SKIP or FAIL, run() and cleanup() are not called.
+        */
+       enum dl_test_status (*setup)(void **ctx);
+
+       /**
+        * run - Required test execution callback
+        * @ctx: Context pointer set by setup(), or NULL if no setup
+        *
+        * Executes the actual test logic. This is the main test function.
+        *
+        * Return:
+        * - DL_TEST_PASS: Test passed
+        * - DL_TEST_SKIP: Test skipped (unlikely here, prefer setup())
+        * - DL_TEST_FAIL: Test failed
+        */
+       enum dl_test_status (*run)(void *ctx);
+
+       /**
+        * cleanup - Optional cleanup callback
+        * @ctx: Context pointer set by setup(), or NULL if no setup
+        *
+        * Called after run() to cleanup resources. Always runs if setup()
+        * succeeded, regardless of run() result. Cannot fail the test.
+        */
+       void (*cleanup)(void *ctx);
+};
+
+/**
+ * dl_test_register() - Register a test with the framework
+ * @test: Pointer to test structure
+ *
+ * Called by REGISTER_DL_TEST() macro. Don't call directly.
+ */
+void dl_test_register(struct dl_test *test);
+
+/**
+ * REGISTER_DL_TEST() - Register a test for auto-discovery
+ * @__test: Pointer to struct dl_test
+ *
+ * Uses ELF constructor attribute to automatically register the test
+ * when the binary loads. The runner will discover and execute all
+ * registered tests.
+ *
+ * Example:
+ *   static struct dl_test my_test = {
+ *       .name = "my_test",
+ *       .description = "Tests something important",
+ *       .run = my_test_run,
+ *   };
+ *   REGISTER_DL_TEST(&my_test);
+ */
+#define __DL_CONCAT(a, b) a##b
+#define _DL_CONCAT(a, b) __DL_CONCAT(a, b)
+
+#define REGISTER_DL_TEST(__test)                                       \
+       __attribute__((constructor))                                    \
+       static void _DL_CONCAT(___dlregister_, __LINE__)(void)          \
+       {                                                               \
+               dl_test_register(__test);                               \
+       }
+
+/* Error reporting macros */
+
+/**
+ * DL_ERR() - Print error message with file/line info
+ */
+#define DL_ERR(__fmt, ...)                                             \
+       do {                                                            \
+               fprintf(stderr, "ERR: %s:%d\n", __FILE__, __LINE__);    \
+               fprintf(stderr, __fmt"\n", ##__VA_ARGS__);              \
+       } while (0)
+
+/**
+ * DL_FAIL() - Fail the test with a message
+ *
+ * Prints error message and returns DL_TEST_FAIL. Use in test run() or
+ * setup() functions.
+ */
+#define DL_FAIL(__fmt, ...)                                            \
+       do {                                                            \
+               DL_ERR(__fmt, ##__VA_ARGS__);                           \
+               return DL_TEST_FAIL;                                    \
+       } while (0)
+
+/**
+ * DL_FAIL_IF() - Conditionally fail the test
+ * @__cond: Condition to check
+ * @__fmt: printf-style format string
+ *
+ * If condition is true, fail the test with the given message.
+ */
+#define DL_FAIL_IF(__cond, __fmt, ...)                                 \
+       do {                                                            \
+               if (__cond)                                             \
+                       DL_FAIL(__fmt, ##__VA_ARGS__);                  \
+       } while (0)
+
+/* Comparison assertion macros */
+
+/**
+ * DL_EQ() - Assert two values are equal
+ */
+#define DL_EQ(_x, _y) \
+       DL_FAIL_IF((_x) != (_y), \
+                  "Expected %s == %s (%lld == %lld)", \
+                  #_x, #_y, (long long)(_x), (long long)(_y))
+
+/**
+ * DL_NE() - Assert two values are not equal
+ */
+#define DL_NE(_x, _y) \
+       DL_FAIL_IF((_x) == (_y), \
+                  "Expected %s != %s (%lld != %lld)", \
+                  #_x, #_y, (long long)(_x), (long long)(_y))
+
+/**
+ * DL_LT() - Assert x < y
+ */
+#define DL_LT(_x, _y) \
+       DL_FAIL_IF((_x) >= (_y), \
+                  "Expected %s < %s (%lld < %lld)", \
+                  #_x, #_y, (long long)(_x), (long long)(_y))
+
+/**
+ * DL_LE() - Assert x <= y
+ */
+#define DL_LE(_x, _y) \
+       DL_FAIL_IF((_x) > (_y), \
+                  "Expected %s <= %s (%lld <= %lld)", \
+                  #_x, #_y, (long long)(_x), (long long)(_y))
+
+/**
+ * DL_GT() - Assert x > y
+ */
+#define DL_GT(_x, _y) \
+       DL_FAIL_IF((_x) <= (_y), \
+                  "Expected %s > %s (%lld > %lld)", \
+                  #_x, #_y, (long long)(_x), (long long)(_y))
+
+/**
+ * DL_GE() - Assert x >= y
+ */
+#define DL_GE(_x, _y) \
+       DL_FAIL_IF((_x) < (_y), \
+                  "Expected %s >= %s (%lld >= %lld)", \
+                  #_x, #_y, (long long)(_x), (long long)(_y))
+
+/**
+ * DL_ASSERT() - Assert condition is true
+ */
+#define DL_ASSERT(_x) \
+       DL_FAIL_IF(!(_x), "Expected %s to be true", #_x)
+
+/**
+ * DL_BUG_ON() - Fatal assertion (for framework bugs, not test failures)
+ * @__cond: Condition to check
+ * @__fmt: Error message
+ *
+ * For internal framework consistency checks. If condition is true,
+ * prints error and aborts. Use for "should never happen" cases.
+ */
+#define DL_BUG_ON(__cond, __fmt, ...)                                  \
+       do {                                                            \
+               if (__cond) {                                           \
+                       fprintf(stderr, "BUG: %s:%d: " __fmt "\n",      \
+                               __FILE__, __LINE__, ##__VA_ARGS__);     \
+                       abort();                                        \
+               }                                                       \
+       } while (0)
+
+#endif /* __DL_TEST_H__ */
diff --git a/tools/testing/selftests/sched/deadline/runner.c 
b/tools/testing/selftests/sched/deadline/runner.c
new file mode 100644
index 0000000000000..358f695423ef5
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/runner.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCHED_DEADLINE Test Runner
+ *
+ * Discovers and executes all registered SCHED_DEADLINE tests.
+ * Tests are statically linked and register themselves via ELF constructors.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <stdbool.h>
+#include "dl_test.h"
+
+const char help_fmt[] =
+"Runner for SCHED_DEADLINE scheduler tests.\n"
+"\n"
+"All tests are statically linked and run serially. Tests require root\n"
+"privileges to set SCHED_DEADLINE scheduling policy.\n"
+"\n"
+"Usage: %s [-t TEST] [-h]\n"
+"\n"
+"  -t TEST       Only run tests whose name includes this string\n"
+"  -s            Include print output for skipped tests\n"
+"  -l            List all available tests\n"
+"  -q            Don't print the test descriptions during run\n"
+"  -h            Display this help and exit\n";
+
+static volatile int exit_req;
+static bool quiet, print_skipped, list;
+
+#define MAX_DL_TESTS 256
+
+static struct dl_test *__dl_tests[MAX_DL_TESTS];
+static unsigned int __dl_num_tests;
+
+static void sigint_handler(int sig)
+{
+       exit_req = 1;
+}
+
+static void print_test_preamble(const struct dl_test *test, bool quiet)
+{
+       printf("===== START =====\n");
+       printf("TEST: %s\n", test->name);
+       if (!quiet)
+               printf("DESCRIPTION: %s\n", test->description);
+       printf("OUTPUT:\n");
+}
+
+static const char *status_to_result(enum dl_test_status status)
+{
+       switch (status) {
+       case DL_TEST_PASS:
+       case DL_TEST_SKIP:
+               return "ok";
+       case DL_TEST_FAIL:
+               return "not ok";
+       default:
+               return "<UNKNOWN>";
+       }
+}
+
+static void print_test_result(const struct dl_test *test,
+                             enum dl_test_status status,
+                             unsigned int testnum)
+{
+       const char *result = status_to_result(status);
+       const char *directive = status == DL_TEST_SKIP ? "SKIP " : "";
+
+       printf("%s %u %s # %s\n", result, testnum, test->name, directive);
+       printf("=====  END  =====\n");
+}
+
+static bool should_skip_test(const struct dl_test *test, const char *filter)
+{
+       return filter && !strstr(test->name, filter);
+}
+
+static enum dl_test_status run_test(const struct dl_test *test)
+{
+       enum dl_test_status status;
+       void *context = NULL;
+
+       if (test->setup) {
+               status = test->setup(&context);
+               if (status != DL_TEST_PASS)
+                       return status;
+       }
+
+       status = test->run(context);
+
+       if (test->cleanup)
+               test->cleanup(context);
+
+       return status;
+}
+
+static bool test_valid(const struct dl_test *test)
+{
+       if (!test) {
+               fprintf(stderr, "NULL test detected\n");
+               return false;
+       }
+
+       if (!test->name) {
+               fprintf(stderr,
+                       "Test with no name found. Must specify test name.\n");
+               return false;
+       }
+
+       if (!test->description) {
+               fprintf(stderr, "Test %s requires description.\n", test->name);
+               return false;
+       }
+
+       if (!test->run) {
+               fprintf(stderr, "Test %s has no run() callback\n", test->name);
+               return false;
+       }
+
+       return true;
+}
+
+int main(int argc, char **argv)
+{
+       const char *filter = NULL;
+       unsigned int testnum = 0, i;
+       unsigned int passed = 0, skipped = 0, failed = 0;
+       int opt;
+
+       signal(SIGINT, sigint_handler);
+       signal(SIGTERM, sigint_handler);
+
+       while ((opt = getopt(argc, argv, "qslt:h")) != -1) {
+               switch (opt) {
+               case 'q':
+                       quiet = true;
+                       break;
+               case 's':
+                       print_skipped = true;
+                       break;
+               case 'l':
+                       list = true;
+                       break;
+               case 't':
+                       filter = optarg;
+                       break;
+               default:
+                       fprintf(stderr, help_fmt, argv[0]);
+                       return opt != 'h';
+               }
+       }
+
+       for (i = 0; i < __dl_num_tests; i++) {
+               enum dl_test_status status;
+               struct dl_test *test = __dl_tests[i];
+
+               if (list) {
+                       printf("%s\n", test->name);
+                       if (i == (__dl_num_tests - 1))
+                               return 0;
+                       continue;
+               }
+
+               if (should_skip_test(test, filter)) {
+                       /*
+                        * Printing the skipped tests and their preambles can
+                        * add a lot of noise to the runner output. Printing
+                        * this is only really useful for CI, so let's skip it
+                        * by default.
+                        */
+                       if (print_skipped) {
+                               print_test_preamble(test, quiet);
+                               print_test_result(test, DL_TEST_SKIP, 
++testnum);
+                       }
+                       continue;
+               }
+
+               print_test_preamble(test, quiet);
+               status = run_test(test);
+               print_test_result(test, status, ++testnum);
+
+               switch (status) {
+               case DL_TEST_PASS:
+                       passed++;
+                       break;
+               case DL_TEST_SKIP:
+                       skipped++;
+                       break;
+               case DL_TEST_FAIL:
+                       failed++;
+                       break;
+               }
+
+               if (exit_req) {
+                       fprintf(stderr, "\nInterrupted by signal\n");
+                       break;
+               }
+       }
+
+       printf("\n\n=============================\n\n");
+       printf("RESULTS:\n\n");
+       printf("PASSED:  %u\n", passed);
+       printf("SKIPPED: %u\n", skipped);
+       printf("FAILED:  %u\n", failed);
+
+       return failed > 0 ? 1 : 0;
+}
+
+void dl_test_register(struct dl_test *test)
+{
+       DL_BUG_ON(!test_valid(test), "Invalid test found");
+       DL_BUG_ON(__dl_num_tests >= MAX_DL_TESTS, "Maximum tests exceeded");
+
+       __dl_tests[__dl_num_tests++] = test;
+}

-- 
2.53.0


Reply via email to