{"id":41306786,"url":"https://github.com/oliverlee/skytest","last_synced_at":"2026-01-23T05:05:00.598Z","repository":{"id":212617976,"uuid":"730975401","full_name":"oliverlee/skytest","owner":"oliverlee","description":"a lightweight C++ unit test framework","archived":false,"fork":false,"pushed_at":"2025-06-11T05:08:57.000Z","size":120,"stargazers_count":2,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-11T06:24:57.954Z","etag":null,"topics":["compile-time","cpp","embedded","unit-test"],"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/oliverlee.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-12-13T04:51:34.000Z","updated_at":"2025-06-11T05:09:00.000Z","dependencies_parsed_at":"2024-01-05T01:47:43.208Z","dependency_job_id":"ff07e69a-2fff-4452-bbd5-3bcbfaba10b1","html_url":"https://github.com/oliverlee/skytest","commit_stats":null,"previous_names":["oliverlee/skytest"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/oliverlee/skytest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverlee%2Fskytest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverlee%2Fskytest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverlee%2Fskytest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverlee%2Fskytest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oliverlee","download_url":"https://codeload.github.com/oliverlee/skytest/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oliverlee%2Fskytest/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28680623,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-23T04:33:33.518Z","status":"ssl_error","status_checked_at":"2026-01-23T04:33:30.433Z","response_time":59,"last_error":"SSL_read: 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":["compile-time","cpp","embedded","unit-test"],"created_at":"2026-01-23T05:04:58.819Z","updated_at":"2026-01-23T05:05:00.593Z","avatar_url":"https://github.com/oliverlee.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# skytest\n\nA non-allocating[^1] C++17+ unit test framework.\n\n## usage\n\n```starlark\n# .bazelrc\ncommon --registry=https://raw.githubusercontent.com/digiboys/bazel-registry/main\ncommon --registry=https://bcr.bazel.build\n```\n\n```starlark\n# MODULE.bazel\nbazel_dep(\n    name = \"skytest\",\n    version = \"0.0.0\",\n    dev_dependency = True,\n)\n```\n\nThen create a `cc_test` that depends on `@skytest`\n\n## overview\n\nA minimal unit test example\n\n```cpp:example/minimal_pass.cpp\n#include \"skytest/skytest.hpp\"\n\nauto main() -\u003e int\n{\n  using namespace skytest::literals;\n  using ::skytest::expect;\n\n  \"truthy\"_test = [] { return expect(true); };\n}\n\n```\n\nwhen run, will print\n\n```console:example/minimal_pass.log\ntest `truthy`...[CONSTEXPR PASS]\nall tests passed (1 test)\n\n```\n\nA test that fails\n\n```cpp:example/minimal_fail.cpp\n#include \"skytest/skytest.hpp\"\n\nauto main(int argc, char*[]) -\u003e int\n{\n  using namespace skytest::literals;\n  using ::skytest::eq;\n  using ::skytest::expect;\n\n  \"falsy\"_test = [\u0026] { return expect(eq(0, argc)); };\n}\n\n```\n\nwill print\n\n```console:example/minimal_fail.log\ntest `falsy`...[FAIL] example/minimal_fail.cpp:9\n(0 == 1)\n\n0 tests passed | 1 test failed\n\n```\n\nWhen running tests, `skytest` will attempt to invoke test closures at\ncompile-time. If able to, results will be classified `CONSTEXPR PASS` or\n`CONSTEXPR FAIL` instead of `PASS` or `FAIL`.\n\n\u003cdetails\u003e\u003csummary\u003erequiring compile-time evaluation of tests\u003c/summary\u003e\n\n\nThe `ctest` literal can be used to require closures to be tested at\ncompile-time. In order to be usable with `ctest`, test closures must be empty\nand non-constexpr functions must not be invoked.\n\n```cpp:example/ctest_fail.cpp\n#include \"skytest/skytest.hpp\"\n\nauto main() -\u003e int\n{\n  using namespace skytest::literals;\n  using ::skytest::expect;\n  using ::skytest::lt;\n\n  static auto n = 0;\n  \"read non-const\"_ctest = [] { return expect(lt(0, n)); };\n}\n\n```\n\nresults in the follow build error (snippet):\n\n```console:example/ctest_fail.log\n./src/detail/test_style.hpp:43:27: error: constexpr variable 'value\u003cskytest::detail::static_closure\u003cf\u003e\u003e' must be initialized by a constant expression\n   43 |     static constexpr auto value = std::optional\u003cbool\u003e{bool{F{}()}};\n      |                           ^       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n./src/test.hpp:46:39: note: in instantiation of static data member 'skytest::detail::test_style::compile_time::value' requested here\n   46 |     result.compile_time = S::template value\u003cF\u003e;\n      |                                       ^\n./src/test.hpp:71:5: note: (skipping 2 contexts in backtrace; use -ftemplate-backtrace-limit=0 to see all)\n   71 |     assign_impl(static_closure\u003cf\u003e{});\n      |     ^\nexternal/llvm_toolchain_llvm/bin/../include/c++/v1/__functional/operations.h:374:37: note: read of non-const variable 'n' is not allowed in a constant expression\n  374 |     return std::forward\u003c_T1\u003e(__t) \u003c std::forward\u003c_T2\u003e(__u);\n      |                                     ^\n./src/detail/predicate.hpp:35:18: note: in call to 'static_cast\u003cconst std::less\u003cvoid\u003e \u0026\u003e(*this).operator()\u003cconst int \u0026, const int \u0026\u003e(0, n)'\n   35 |     auto value = static_cast\u003cconst F\u0026\u003e(*this)(std::as_const(args)...);\n      |                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nexample/ctest_fail.cpp:10:47: note: (skipping 2 calls in backtrace; use -fconstexpr-backtrace-limit=0 to see all)\n   10 |   \"read non-const\"_ctest = [] { return expect(lt(0, n)); };\n      |                                               ^\nexample/ctest_fail.cpp:9:15: note: declared here\n    9 |   static auto n = 0;\n      |               ^\n1 error generated.\n\n```\n\n\u003c/details\u003e\n\n\n## examples\n\n#### binary comparisons\n\u003cdetails\u003e\u003csummary\u003e\u003c/summary\u003e\n\n\nBinary comparison predicates display arguments on failure.\n\n```cpp:example/binary_comparisons.cpp\n#include \"skytest/skytest.hpp\"\n\nauto main() -\u003e int\n{\n  using namespace skytest::literals;\n  using ::skytest::eq;\n  using ::skytest::expect;\n  using ::skytest::ge;\n  using ::skytest::gt;\n  using ::skytest::le;\n  using ::skytest::lt;\n  using ::skytest::ne;\n\n  \"eq\"_test = [] { return expect(eq(1, 1)); };\n  \"ne\"_test = [] { return expect(ne(1, 0)); };\n  \"lt\"_test = [] { return expect(lt(0, 1)); };\n  \"gt\"_test = [] { return expect(gt(1, 0)); };\n  \"le\"_test = [] { return expect(le(1, 1)); };\n  \"ge\"_test = [] { return expect(ge(1, 1)); };\n}\n\n```\n\n\u003c/details\u003e\n\n\n#### logical operators\n\u003cdetails\u003e\u003csummary\u003e\u003c/summary\u003e\n\n\nLogical operators can be used to compose predicates - including user defined predicates.\n\n```cpp:example/logical_operations.cpp\n#include \"skytest/skytest.hpp\"\n\nauto main() -\u003e int\n{\n  using namespace skytest::literals;\n  using ::skytest::expect;\n  using ::skytest::lt;\n\n  \"and\"_test = [] { return expect(lt(1, 2) and lt(2, 3)); };\n  \"or\"_test = [] { return expect(lt(3, 2) or lt(2, 3)); };\n  \"not\"_test = [] { return expect(not lt(1, 0)); };\n}\n\n```\n\n\u003c/details\u003e\n\n\n#### additional output on failure\n\u003cdetails\u003e\u003csummary\u003e\u003c/summary\u003e\n\n\nAdditional output can be displayed on test failure.\n\n```cpp:example/additional_output.cpp\n#include \"skytest/skytest.hpp\"\n\n#include \u003cutility\u003e\n\nauto main() -\u003e int\n{\n  using namespace skytest::literals;\n  using ::skytest::eq;\n  using ::skytest::expect;\n\n  \"string-view-ish\"_test = [] { return expect(eq(1, 2), \"a message\"); };\n\n  \"ostream invocable closure\"_test = [] {\n    const auto x = std::pair{1, 2};\n    const auto y = std::pair{2, 1};\n    return expect(eq(x, y), [=](auto\u0026 os) {\n      os \u003c\u003c x.first \u003c\u003c \", \" \u003c\u003c y.first \u003c\u003c \"\\n\";\n    });\n  };\n}\n\n```\n```console:example/additional_output.log\ntest `string-view-ish`...[CONSTEXPR FAIL] example/additional_output.cpp:11\n(1 == 2)\na message\ntest `ostream invocable closure`...[CONSTEXPR FAIL] example/additional_output.cpp:16\n(std::pair\u003cint, int\u003e{...} == std::pair\u003cint, int\u003e{...})\n1, 2\n\n0 tests passed | 2 tests failed\n\n```\n\nNOTE: The message closure is not invoked within the test closure. Capturing\n`x` and `y` by reference will result in dangling.\n\u003c/details\u003e\n\n\n#### user defined predicates\n\u003cdetails\u003e\u003csummary\u003e\u003c/summary\u003e\n\n\nDefining a predicate with `pred` captures and displays arguments on failure.\n\n```cpp:example/user_defined_predicates.cpp\n#include \"skytest/skytest.hpp\"\n\n#include \u003carray\u003e\n#include \u003citerator\u003e\n\nauto main() -\u003e int\n{\n  using namespace skytest::literals;\n  using ::skytest::expect;\n  using ::skytest::pred;\n\n  static constexpr auto empty = pred([](const auto\u0026 rng) {\n    return std::empty(rng);\n  });\n\n  \"empty array\"_test = [] { return expect(empty(std::array{1, 2, 3})); };\n}\n\n```\n```console:example/user_defined_predicates.log\ntest `empty array`...[CONSTEXPR FAIL] example/user_defined_predicates.cpp:16\n(lambda at example/user_defined_predicates.cpp:12:38){}([1, 2, 3])\n\n0 tests passed | 1 test failed\n\n```\n\nThe description of user defined predicates can be customized\n\n```cpp:example/described_predicates.cpp\n\n#include \"skytest/skytest.hpp\"\n\n#include \u003carray\u003e\n#include \u003cstring_view\u003e\n\nusing namespace skytest::literals;\nusing ::skytest::expect;\nusing ::skytest::pred;\n\nstruct empty_desc\n{\n  using notation_type = skytest::notation::function;\n  static constexpr auto symbol = std::string_view{\"empty\"};\n};\n\nstatic constexpr auto empty = pred(empty_desc{}, [](const auto\u0026 rng) {\n  return std::empty(rng);\n});\n\nauto main() -\u003e int\n{\n\n  \"empty array\"_test = [] { return expect(empty(std::array{1, 2, 3})); };\n}\n\n```\n```console:example/described_predicates.log\ntest `empty array`...[CONSTEXPR FAIL] example/described_predicates.cpp:24\nempty([1, 2, 3])\n\n0 tests passed | 1 test failed\n\n```\n\nC++20 enables a terser syntax.\n\n```cpp:example/described_predicates_20.cpp\n#include \"skytest/skytest.hpp\"\n\n#include \u003carray\u003e\n\nauto main() -\u003e int\n{\n  using namespace skytest::literals;\n  using ::skytest::expect;\n  using ::skytest::function;\n  using ::skytest::pred;\n\n  static constexpr auto empty = pred(function\u003c\"∅\"\u003e, std::ranges::empty);\n\n  \"empty array\"_test = [] { return expect(empty(std::array{1, 2, 3})); };\n}\n\n```\n\n\u003c/details\u003e\n\n\n#### parameterized tests\n\u003cdetails\u003e\u003csummary\u003e\u003c/summary\u003e\n\n\nTests can be parameterized with a tuple-like\n\n```cpp:example/type_parameterized.cpp\n#include \"skytest/skytest.hpp\"\n\n#include \u003ccmath\u003e\n#include \u003ccomplex\u003e\n#include \u003ctuple\u003e\n\nauto main() -\u003e int\n{\n  using namespace ::skytest::literals;\n  using ::skytest::eq;\n  using ::skytest::expect;\n  using ::skytest::types;\n\n  \"typed\"_test * std::tuple\u003cint, float, std::complex\u003cdouble\u003e\u003e{} =  //\n      [](auto param) { return expect(eq(0, std::abs(param))); };\n\n  \"with types param\"_test * types\u003cint, float\u003e =  //\n      [](auto type_id) {\n        using T = typename decltype(type_id)::type;\n        return expect(eq(T{}, T{} + T{}));\n      };\n}\n\n```\n\nor with a range\n\n```cpp:example/value_parameterized.cpp\n#include \"skytest/skytest.hpp\"\n\n#include \u003cvector\u003e\n\nauto main() -\u003e int\n{\n  using namespace ::skytest::literals;\n  using ::skytest::expect;\n  using ::skytest::lt;\n\n  \"values\"_test * std::vector{1, 2, 3} =  //\n      [](auto param) { return expect(lt(0, param)); };\n}\n\n```\n\nIf parameters are defined as a static constant, `constexr_params` and\n`param_ref` can be used to define compile-time tests.\n\n```cpp:example/constexpr_params_parameterized.cpp\n#include \"skytest/skytest.hpp\"\n\nauto main() -\u003e int\n{\n  using namespace ::skytest::literals;\n  using ::skytest::constexpr_params;\n  using ::skytest::eq;\n  using ::skytest::expect;\n\n  \"types with param_ref\"_ctest * constexpr_params\u003c1, 2U\u003e =  //\n      [](auto param) {\n        return expect(eq(std::is_same_v\u003cint, decltype(param)\u003e ? 1 : 2, param));\n      };\n}\n\n```\n```cpp:example/param_ref_parameterized.cpp\n#include \"skytest/skytest.hpp\"\n\n#include \u003carray\u003e\n#include \u003ctuple\u003e\n\nauto main() -\u003e int\n{\n  using namespace ::skytest::literals;\n  using ::skytest::eq;\n  using ::skytest::expect;\n  using ::skytest::lt;\n  using ::skytest::param_ref;\n\n  static constexpr auto type_params = std::tuple{1.0F, 1.0};\n\n  \"types with param_ref\"_test * param_ref\u003ctype_params\u003e =  //\n      [](auto param) { return expect(eq(1, param)); };\n\n  static constexpr auto value_params = std::array{1, 2, 3};\n\n  \"values with param_ref\"_test * param_ref\u003cvalue_params\u003e =  //\n      [](auto param) { return expect(lt(0, param)); };\n}\n\n```\n\nIf parameters are defined as a literal-type[^2], C++20 allows use of `param`:\n\n```cpp:example/param_parameterized.cpp\n#include \"skytest/skytest.hpp\"\n\n#include \u003carray\u003e\n\nauto main() -\u003e int\n{\n  using namespace ::skytest::literals;\n  using ::skytest::expect;\n  using ::skytest::lt;\n  using ::skytest::param;\n\n  \"with paramf\"_test * param\u003cstd::array{1, 2, 3}\u003e =  //\n      [](auto param) { return expect(lt(0, param)); };\n}\n\n```\n\n\u003c/details\u003e\n\n\n#### aborts\n\u003cdetails\u003e\u003csummary\u003e\u003c/summary\u003e\n\n\nCheck that abort is called (e.g. in a function precondition).\n\n```cpp:example/aborts.cpp\n#include \"skytest/skytest.hpp\"\n\n#include \u003ccassert\u003e\n\nauto main() -\u003e int\n{\n  using namespace ::skytest::literals;\n  using ::skytest::aborts;\n  using ::skytest::expect;\n\n  static const auto not_zero = [](int value) { assert(value != 0); };\n\n  \"aborts\"_test = [] { return expect(aborts([] { not_zero(0); })); };\n}\n\n```\n\n\u003c/details\u003e\n\n\n[^1]: The default printer uses `std::cout` and `skytest::aborts` calls `fork`.\n[^2]: https://en.cppreference.com/w/cpp/named_req/LiteralType\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foliverlee%2Fskytest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foliverlee%2Fskytest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foliverlee%2Fskytest/lists"}