{"id":39288465,"url":"https://github.com/nickeldan/scrutiny","last_synced_at":"2026-01-18T01:13:52.369Z","repository":{"id":65232866,"uuid":"533017766","full_name":"nickeldan/scrutiny","owner":"nickeldan","description":"C testing framework for POSIX environments","archived":false,"fork":false,"pushed_at":"2024-01-29T04:50:03.000Z","size":532,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-01-29T05:24:16.129Z","etag":null,"topics":["c","posix","testing-framework"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nickeldan.png","metadata":{"files":{"readme":"README.md","changelog":"changelog","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-09-05T18:34:27.000Z","updated_at":"2023-07-17T05:57:11.000Z","dependencies_parsed_at":"2023-02-12T14:30:29.294Z","dependency_job_id":"0b9b61bb-2484-4c5b-899d-7231a2a21ad0","html_url":"https://github.com/nickeldan/scrutiny","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/nickeldan/scrutiny","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nickeldan%2Fscrutiny","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nickeldan%2Fscrutiny/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nickeldan%2Fscrutiny/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nickeldan%2Fscrutiny/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nickeldan","download_url":"https://codeload.github.com/nickeldan/scrutiny/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nickeldan%2Fscrutiny/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28525859,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T00:39:45.795Z","status":"ssl_error","status_checked_at":"2026-01-18T00:39:39.467Z","response_time":85,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","posix","testing-framework"],"created_at":"2026-01-18T01:13:50.508Z","updated_at":"2026-01-18T01:13:52.358Z","avatar_url":"https://github.com/nickeldan.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"Scrutiny\n========\n\nScrutiny is a C testing framework for POSIX environments.\n\nTesting basics\n--------------\n\nAll of the functionality can be accessed by\n\n```c\n#include \u003cscrutiny/scrutiny.h\u003e\n```\n\nEvery test must be part of a group.  You can create a group by\n\n```c\nscrGroup group;\n\ngroup = scrGroupCreate(NULL, NULL);\n```\n\nAfter that, you can define a test function:\n\n```c\nvoid my_test(void) {\n    ...\n}\n```\n\nand add it to a group:\n\n```c\nscrGroupAddTest(group, \"Test name\", my_test, NULL);\n```\n\nOnce you have added all of the tests, you can run them by\n\n```c\nscrRun(NULL, NULL);\n```\n\nThis function returns `0` if all of the tests pass (or are skipped) and `1` otherwise.  The function also summarizes the results in `stdout`.\n\nYou can pass a `scrStats*` to `scrRun`:\n\n```c\nscrStats stats;\n\nscrRun(NULL, \u0026stats);\n```\n\nwhere `scrStats` is defined as\n\n```c\ntypedef struct scrStats {\n    unsigned int num_passed;\n    unsigned int num_skipped;\n    unsigned int num_failed;\n    unsigned int num_errored;\n} scrStats;\n```\n\nWriting tests\n-------------\n\nVarious macros are provided in order to test various conditions.  For example, for integers,\n\n```c\nvoid integer_test(void) {\n    int x = 5, y = 5, z = 6;\n\n    SCR_ASSERT_EQ(x, y); // equal\n    SCR_ASSERT_NEQ(x, z); // not equal\n    SCR_ASSERT_LE(x, y); // less than or equal to\n    SCR_ASSERT_LT(x, z); // less than\n    SCR_ASSERT_GE(z, x); // greater than or equal to\n    SCR_ASSERT_GT(z, x); // greater than\n}\n```\n\nAll integer values are upgraded to `intmax_t`.  If you need to use `uintmax_t`, use the `UNSIGNED` macros like `SCR_ASSERT_UNSIGNED_LT`.  For floating-point values, use macros like `SCR_ASSERT_FLOAT_LT`.\n\nThough `char` variables are also integer variables, you should use the `SCR_ASSERT_CHAR_EQ` and `SCR_ASSERT_CHAR_NEQ` macros to compare them.\n\nYou can compare pointers by\n\n```c\nvoid pointer_test(void) {\n    void *p = NULL;\n    int x;\n\n    SCR_ASSERT_PTR_EQ(p, NULL);\n    SCR_ASSERT_PTR_NEQ(\u0026x, p);\n}\n```\n\nYou can compare strings (i.e., `char` arrays) by\n\n```c\nvoid string_test(void) {\n    size_t idx;\n    const char *word = \"hello\";\n\n    SCR_ASSERT_STR_EQ(word, \"hello\");\n    SCR_ASSERT_STR_NEQ(word, \"goodbye\");\n    SCR_ASSERT_STR_BEGINS_WITH(word, \"hel\");\n    SCR_ASSERT_STR_NBEGINS_WITH(word, \"hellp\");\n\n    idx = SCR_ASSERT_STR_CONTAINS(word, \"ll\");\n    SCR_ASSERT_EQ(idx, 2);\n    SCR_ASSERT_STR_NCONTAINS(word, \"elp\");\n}\n```\n\nAs you can see, `SCR_ASSERT_STR_CONTAINS` is a special macro in that, if it succeeds, it returns the index where the substring starts.\n\nPlease note that you cannot use the string macros with `NULL` pointers.\n\nYou can test that two memory regions are equal (essentially, running `memcmp`) by\n\n```c\nvoid buffers_equal(void) {\n    SCR_ASSERT_MEM_EQ(\"help\", \"hello\", 3);\n}\n```\n\nYou can skip a test by\n\n```c\nvoid skip_me(void) {\n    SCR_TEST_SKIP();\n}\n```\n\nIn addition, you can make general assertions by\n\n```c\nvoid my_test(void) {\n    int x = 5, y = 5;\n\n    SCR_ASSERT(x + y == 10);\n}\n```\n\nYou can fail a test without any assertion by\n\n```c\nvoid gonna_fail(void) {\n    SCR_FAIL(\"Failing this test for reasons\");\n}\n```\n\nYou can emit logging statements by\n\n```c\nvoid my_test(void) {\n    int x = 5;\n\n    SCR_LOG(\"x is %i\", x);\n}\n```\n\nNote that such statements will only be displayed if the test fails.\n\nTest parameters\n---------------\n\nThe signature of `scrGroupAddTest` is\n\n```c\nvoid\nscrGroupAddTest(scrGroup group, const char *name, scrTestFn test_fn, const scrTestOptions *options);\n```\n\nwhere\n\n```c\ntypedef scrTestOptions\n    unsigned int timeout;\n    unsigned flags;\n} scrTestOptions;\n```\n\nIf `options` is `NULL`, then default options will be used (i.e., `0` for both).\n\nIf `timeout` is positive, then the test will fail if not completed within that many seconds.\n\nAt the moment, the only valid value for `flags` other than `0` is `SCR_TF_XFAIL`.  If this value is passed, then success/failure will be inverted.  That is, the test will be expected to fail and a failure will be counted if the test passes.\n\nGlobal/group context\n--------------------\n\nFor each group of tests, there is a group context which is a `void*`.  It is accessible from the tests via\n\n```c\nvoid my_test(void) {\n    void *ctx = scrGroupCtx();\n}\n```\n\nThe signature of `scrRun` is\n\n```c\nint\nscrRun(const scrOptions *options, scrStats *stats);\n```\n\nwhere\n\n```c\ntypedef struct scrOptions {\n    void *global_ctx;\n    unsigned int flags;\n} scrOptions;\n```\n\nIf the `options` argument is `NULL`, then default values will be used (i.e., `NULL` and `0`).\n\nBy default, each group context is equal to the global context.  However, you can pass function pointers to `scrGroupCreate` which can set up and tear down a group context.  The signature of `scrGroupCreate` is\n\n```c\nscrGroup\nscrGroupCreate(scrCtxCreateFn create_fn, scrCtxCleanupFn cleanup_fn);\n```\n\nwhere\n\n```c\ntypedef void *scrCtxCreateFn(void *);\ntypedef void scrCtxCleanupFn(void *);\n```\n\nIf specified, then `create_fn` will be called with the global context as the argument.  The pointer returned will be the group context.\n\nIf specified, then `cleanup_fn` will be called with the group context (or the global context if `create_fn` was unspecified).\n\nYou can use the test macros in `create_fn`.  If any of the assertions fail, then all of the tests in that group will be counted as having failed.  You can also call `SCR_TEST_SKIP()` which will skip all of the group's tests.\n\nRun flags\n---------\n\nThe `flags` field in `scrOptions` is some bitwise-or combination of any or none of the following:\n\n* `SCR_RF_FAIL_FAST`: Stop running tests as soon as any test either fails or encounters an error.\n* `SCR_RF_VERBOSE`: Show logging messages as well as `stdout`/`stderr` even when tests pass or are skipped.\n\nMonkeypatching\n--------------\n\nWhen building for Linux, you can add, at compile time, the ability to monkeypatch functions.  To enable monkeypatching, add `monkeypatch=yes` to your `make` invocation.\n\nSuppose, for example, you wanted `malloc` to always return `NULL` during testing.  You could create the fake function\n\n```c\nvoid *\nfake_malloc(size_t size)\n{\n    (void)size;\n    return NULL;\n}\n```\n\nand then patch `malloc` with\n\n```c\nbool\nscrGroupPatchFunction(scrGroup group, const char *func_name, const char *file_substring, void *new_func);\n```\n\nHere, `new_func` would be a function pointer to `fake_malloc`.  E.g.,\n\n```c\nif ( !scrGroupPatchFunction(group, \"malloc\", NULL, fake_malloc) ) {\n    // handle the error\n}\n```\n\nThis test would then pass:\n\n```c\nvoid\nmalloc_fail(void)\n{\n    SCR_ASSERT_PTR_EQ(malloc(1), NULL);\n}\n```\n\nWhen you attempt to patch a function, Scrutiny will walk the the process' maps file in procfs and identify any ELF files (libscrutiny.so is skipped).  If any of them contain a global offset table (GOT) entry for the specified function, the address of the entry will be recorded.  When a process running one of the tests in the group is started, it will be ptraced and those GOT entries will be altered to point to the interposed function.  If the to-be-patched function is not found in any `.text` section, then `scrGroupPatchFunction` will return `false`.\n\nIf `file_substring` is not `NULL`, then only ELF files whose paths contain the value as a substring will be patched.  That means that the same function can be patched in the same testing group multiple times.  If the same ELF file would be patched multiple times by different calls to `scrGroupPatchFunction`, then the last call would be the one that is ultimately applied.\n\nDuring testing, you may acquire a pointer to the original function (e.g., the true `malloc`) by\n\n```c\nvoid\nsome_test(void)\n{\n    void *(*true_malloc)(size_t);\n\n    true_malloc = scrPatchedFunction(\"malloc\");\n    ...\n}\n```\n\n`scrPatchedFunction` will return `NULL` if a patch for the function was never registered.\n\nThis feature is highly experimental and will probably not work in the presence of certain link-time optimizations.\n\nBuilding Scrutiny\n-----------------\n\nScrutiny has submodules so you'll need to add `--recurse-submodules` to your `git clone` invocation.\n\nYou can build and install Scrutiny by\n\n```sh\nmake install\n```\n\nAfter that, you can link your test program to Scrutiny with `-lscrutiny`.\n\nScrutiny can be uninstalled by\n\n```sh\nmake uninstall\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnickeldan%2Fscrutiny","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnickeldan%2Fscrutiny","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnickeldan%2Fscrutiny/lists"}