From 26016549363cb946b4aa79d86984e06b2026bb88 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 24 Dec 2024 21:17:24 +0100 Subject: [PATCH] Hello libcode-validation --- .editorconfig | 17 +++ .gitattributes | 1 + .gitea/workflows/on-push.yaml | 20 +++ .gitignore | 31 ++++ LICENSE | 31 ++++ README.md | 21 +++ build/.gitignore | 4 + build/bootstrap.build | 7 + build/export.build | 6 + build/root.build | 16 ++ buildfile | 5 + code/validation/.gitignore | 9 ++ code/validation/assert.hxx | 51 +++++++ code/validation/assert.txx | 210 +++++++++++++++++++++++++++ code/validation/buildfile | 62 ++++++++ code/validation/except.hxx | 29 ++++ code/validation/exec.cxx | 118 +++++++++++++++ code/validation/exec.hxx | 16 ++ code/validation/macros.hxx | 54 +++++++ code/validation/main.cxx | 43 ++++++ code/validation/main.hxx | 15 ++ code/validation/setup.cxx | 60 ++++++++ code/validation/setup.hxx | 54 +++++++ code/validation/teardown.cxx | 60 ++++++++ code/validation/teardown.hxx | 54 +++++++ code/validation/test-case.cxx | 73 ++++++++++ code/validation/test-case.hxx | 57 ++++++++ code/validation/test-fail-extras.cxx | 13 ++ code/validation/test-fail-extras.hxx | 58 ++++++++ code/validation/test-result.cxx | 69 +++++++++ code/validation/test-result.hxx | 45 ++++++ code/validation/test-summary.cxx | 48 ++++++ code/validation/test-summary.hxx | 38 +++++ code/validation/traits.hxx | 23 +++ code/validation/utility.hxx | 138 ++++++++++++++++++ code/validation/validate.hxx | 8 + code/validation/version.hxx.in | 37 +++++ manifest | 11 ++ repositories.manifest | 2 + tests/.gitignore | 8 + tests/basics/.gitignore | 1 + tests/basics/basics.cxx | 31 ++++ tests/basics/buildfile | 4 + tests/build/.gitignore | 4 + tests/build/bootstrap.build | 5 + tests/build/root.build | 16 ++ tests/buildfile | 1 + 47 files changed, 1684 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitea/workflows/on-push.yaml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build/.gitignore create mode 100644 build/bootstrap.build create mode 100644 build/export.build create mode 100644 build/root.build create mode 100644 buildfile create mode 100644 code/validation/.gitignore create mode 100644 code/validation/assert.hxx create mode 100644 code/validation/assert.txx create mode 100644 code/validation/buildfile create mode 100644 code/validation/except.hxx create mode 100644 code/validation/exec.cxx create mode 100644 code/validation/exec.hxx create mode 100644 code/validation/macros.hxx create mode 100644 code/validation/main.cxx create mode 100644 code/validation/main.hxx create mode 100644 code/validation/setup.cxx create mode 100644 code/validation/setup.hxx create mode 100644 code/validation/teardown.cxx create mode 100644 code/validation/teardown.hxx create mode 100644 code/validation/test-case.cxx create mode 100644 code/validation/test-case.hxx create mode 100644 code/validation/test-fail-extras.cxx create mode 100644 code/validation/test-fail-extras.hxx create mode 100644 code/validation/test-result.cxx create mode 100644 code/validation/test-result.hxx create mode 100644 code/validation/test-summary.cxx create mode 100644 code/validation/test-summary.hxx create mode 100644 code/validation/traits.hxx create mode 100644 code/validation/utility.hxx create mode 100644 code/validation/validate.hxx create mode 100644 code/validation/version.hxx.in create mode 100644 manifest create mode 100644 repositories.manifest create mode 100644 tests/.gitignore create mode 100644 tests/basics/.gitignore create mode 100644 tests/basics/basics.cxx create mode 100644 tests/basics/buildfile create mode 100644 tests/build/.gitignore create mode 100644 tests/build/bootstrap.build create mode 100644 tests/build/root.build create mode 100644 tests/buildfile diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ec9f3de --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +indent_size = 4 +max_line_length = off +trim_trailing_whitespace = false + +[*.yaml] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitea/workflows/on-push.yaml b/.gitea/workflows/on-push.yaml new file mode 100644 index 0000000..67bb511 --- /dev/null +++ b/.gitea/workflows/on-push.yaml @@ -0,0 +1,20 @@ +name: on-push +on: [push] + +jobs: + build-and-test: + runs-on: linux + container: code.helloryan.se/infra/buildenv/cxx-amd64-fedora-40:latest + volumes: + - /build + steps: + - name: Clone repository + uses: actions/checkout@v3 + - name: Initialize + run: | + bpkg create -d /build cc config.cc.coptions="-Wall -Werror" + bdep init -A /build + - name: Build + run: b + - name: Test + run: b test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c96e1ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +.bdep/ + +# Local default options files. +# +.build2/local/ + +# Compiler/linker output. +# +*.d +*.t +*.i +*.i.* +*.ii +*.ii.* +*.o +*.obj +*.gcm +*.pcm +*.ifc +*.so +*.dylib +*.dll +*.a +*.lib +*.exp +*.pdb +*.ilk +*.exe +*.exe.dlls/ +*.exe.manifest +*.pc diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dfc745b --- /dev/null +++ b/LICENSE @@ -0,0 +1,31 @@ +Copyright © 2024 Ryan. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. All advertising materials mentioning features or use of this software must + display the following acknowledgement: + + This product includes software developed by Ryan, http://helloryan.se/. + +4. Neither the name(s) of the copyright holder(s) nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ea85e5 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# libcode-validation + +![Build status](https://code.helloryan.se/code/libcode-validation/actions/workflows/on-push.yaml/badge.svg) + +## Requirements + +None, other than a modern C++-compiler. + +## Building + +See the wiki, https://code.helloryan.se/code/wiki/wiki/Build-Instructions, for +build instructions. + +## Contact + +Please report bugs and issues by sending an e-mail to: ryan@helloryan.se. + +## Contributing + +Please send an e-mail to ryan@helloryan.se to request an account and +write-access to the libcode-validation repository. diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 0000000..974e01d --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,4 @@ +/config.build +/root/ +/bootstrap/ +build/ diff --git a/build/bootstrap.build b/build/bootstrap.build new file mode 100644 index 0000000..b253927 --- /dev/null +++ b/build/bootstrap.build @@ -0,0 +1,7 @@ +project = libcode-validation + +using version +using config +using test +using install +using dist diff --git a/build/export.build b/build/export.build new file mode 100644 index 0000000..956a8ec --- /dev/null +++ b/build/export.build @@ -0,0 +1,6 @@ +$out_root/ +{ + include code/validation/ +} + +export $out_root/code/validation/$import.target diff --git a/build/root.build b/build/root.build new file mode 100644 index 0000000..21e0a2e --- /dev/null +++ b/build/root.build @@ -0,0 +1,16 @@ +# Uncomment to suppress warnings coming from external libraries. +# +#cxx.internal.scope = current + +cxx.std = latest + +using cxx + +hxx{*}: extension = hxx +ixx{*}: extension = ixx +txx{*}: extension = txx +cxx{*}: extension = cxx + +# The test target for cross-testing (running tests under Wine, etc). +# +test.target = $cxx.target diff --git a/buildfile b/buildfile new file mode 100644 index 0000000..bbe185e --- /dev/null +++ b/buildfile @@ -0,0 +1,5 @@ +./: {code/ tests/} doc{README.md} legal{LICENSE} manifest + +# Don't install tests. +# +tests/: install = false diff --git a/code/validation/.gitignore b/code/validation/.gitignore new file mode 100644 index 0000000..b1ed0e0 --- /dev/null +++ b/code/validation/.gitignore @@ -0,0 +1,9 @@ +# Generated version header. +# +version.hxx + +# Unit test executables and Testscript output directories +# (can be symlinks). +# +*.test +test-*.test diff --git a/code/validation/assert.hxx b/code/validation/assert.hxx new file mode 100644 index 0000000..3739fb3 --- /dev/null +++ b/code/validation/assert.hxx @@ -0,0 +1,51 @@ +#ifndef code__validation__assert_hxx_ +#define code__validation__assert_hxx_ + +#include +#include + +#include +#include +#include +#include + +namespace code::validation::Assert +{ + + template + void + assert_true(T const&, + std::string, + std::source_location const& = std::source_location::current()); + + template + void + assert_false(T const&, + std::string, + std::source_location const& = std::source_location::current()); + + template + void + assert_null(T const&, + std::string, + std::source_location const& = std::source_location::current()); + + template + void + assert_not_null(T const&, + std::string, + std::source_location const& = std::source_location::current()); + + template + void + assert_equal(T1 const&, + T2 const&, + std::string, + std::string, + std::source_location const& = std::source_location::current()); + +} // namespace code::validation::Assert + +#include + +#endif diff --git a/code/validation/assert.txx b/code/validation/assert.txx new file mode 100644 index 0000000..3bef164 --- /dev/null +++ b/code/validation/assert.txx @@ -0,0 +1,210 @@ +namespace code::validation::Assert +{ + + namespace + { + + class Unary_expression_failure + { + public: + Unary_expression_failure(std::string message, + std::string file, + std::uint32_t line, + std::string expr, + std::string val) + : message_{std::move(message)}, + file_{std::move(file)}, + line_{line}, + expr_{std::move(expr)}, + val_{std::move(val)} + {} + + void + print(std::ostream& o) const + { + o << " " << message_ << '\n'; + o << " source: " << file_ << ':' << line_ << ":\n"; + o << " expression: " << expr_ << '\n'; + o << " value : " << val_ << '\n'; + } + + private: + std::string message_; + std::string file_; + std::uint32_t line_; + std::string expr_; + std::string val_; + + }; + + class Binary_expression_failure + { + public: + Binary_expression_failure(std::string message, + std::string file, + std::uint32_t line, + std::string lhs_expr, + std::string lhs_val, + std::string rhs_expr, + std::string rhs_val) + : message_{std::move(message)}, + file_{std::move(file)}, + line_{line}, + lhs_expr_{std::move(lhs_expr)}, + lhs_val_{std::move(lhs_val)}, + rhs_expr_{std::move(rhs_expr)}, + rhs_val_{std::move(rhs_val)} + {} + + void + print(std::ostream& o) const + { + o << " " << message_ << '\n'; + o << " source: " << file_ << ':' << line_ << ":\n"; + o << " left-hand expression : " << lhs_expr_ << '\n'; + o << " left-hand value : " << lhs_val_ << '\n'; + o << " right-hand expression: " << rhs_expr_ << '\n'; + o << " right-hand value : " << rhs_val_ << '\n'; + } + + private: + std::string message_; + std::string file_; + std::uint32_t line_; + std::string lhs_expr_; + std::string lhs_val_; + std::string rhs_expr_; + std::string rhs_val_; + + }; + + } // namespace + + template + void + assert_true(T const& value, + std::string expr, + std::source_location const& source) + { + if (value == true) { + return; + } + + std::string expr_str; + + if constexpr (has_output_operator) { + std::stringstream str; + str << value; + expr_str = str.str(); + } + + throw Fail{ + Unary_expression_failure{ + "expected expression to be true; was false", + source.file_name(), + source.line(), + std::move(expr), + std::move(expr_str) + } + }; + } + + template + void + assert_false(T const& value, + std::string expr, + std::source_location const& source) + { + if (value == false) { + return; + } + + std::string expr_str; + + if constexpr (has_output_operator) { + std::stringstream str; + str << value; + expr_str = str.str(); + } + + throw Fail{ + Unary_expression_failure{ + "expected expression to be false; was true", + source.file_name(), + source.line(), + std::move(expr), + std::move(expr_str) + } + }; + } + + template + void + assert_null(T const& value, + std::string expr, + std::source_location const& source) + { + if (value == nullptr) { + return; + } + + std::string expr_str; + + if constexpr (has_output_operator) { + std::stringstream str; + str << value; + expr_str = str.str(); + } + + throw Fail{ + Unary_expression_failure{ + "expected expression to be nullptr; was not", + source.file_name(), + source.line(), + std::move(expr), + std::move(expr_str) + } + }; + } + + template + void + assert_equal(T1 const& lhs, + T2 const& rhs, + std::string lhs_expr, + std::string rhs_expr, + std::source_location const& source) + { + if (lhs == rhs) { + return; + } + + std::string lhs_val; + std::string rhs_val; + + if constexpr (has_output_operator) { + std::stringstream str; + str << lhs; + lhs_val = str.str(); + } + + if constexpr (has_output_operator) { + std::stringstream str; + str << rhs; + rhs_val = str.str(); + } + + throw Fail{ + Binary_expression_failure{ + "expected lhs to equal rhs; they did not", + source.file_name(), + source.line(), + std::move(lhs_expr), + std::move(lhs_val), + std::move(rhs_expr), + std::move(rhs_val) + } + }; + } + +} // namespace code::validation::Assert diff --git a/code/validation/buildfile b/code/validation/buildfile new file mode 100644 index 0000000..edd7852 --- /dev/null +++ b/code/validation/buildfile @@ -0,0 +1,62 @@ +intf_libs = # Interface dependencies. +impl_libs = # Implementation dependencies. + +./: lib{code-validation}: libul{code-validation} + +libul{code-validation}: {hxx ixx txx cxx}{** -**.test... -version} \ + {hxx }{ version} + +libul{code-validation}: $impl_libs $intf_libs + +# Unit tests. +# +exe{*.test}: +{ + test = true + install = false +} + +for t: cxx{**.test...} +{ + d = $directory($t) + n = $name($t)... + + ./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n} + $d/exe{$n}: libul{code-validation}: bin.whole = false +} + +hxx{version}: in{version} $src_root/manifest +{ + dist = true + clean = ($src_root != $out_root) +} + +# Build options. +# +cxx.poptions =+ "-I$out_root" "-I$src_root" + +# Export options. +# +lib{code-validation}: +{ + cxx.export.poptions = "-I$out_root" "-I$src_root" + cxx.export.libs = $intf_libs +} + +# For pre-releases use the complete version to make sure they cannot +# be used in place of another pre-release or the final version. See +# the version module for details on the version.* variable values. +# +if $version.pre_release + lib{code-validation}: bin.lib.version = "-$version.project_id" +else + lib{code-validation}: bin.lib.version = "-$version.major.$version.minor" + +# Install into the code/validation/ subdirectory of, say, /usr/include/ +# recreating subdirectories. +# +{hxx ixx txx}{*}: +{ + install = include/code/validation/ + install.subdirs = true +} diff --git a/code/validation/except.hxx b/code/validation/except.hxx new file mode 100644 index 0000000..98024dc --- /dev/null +++ b/code/validation/except.hxx @@ -0,0 +1,29 @@ +#ifndef code__validation__except_hxx_ +#define code__validation__except_hxx_ + +#include + +namespace code::validation +{ + + class Fail + { + public: + Fail(Test_fail_extras extras) + : extras_{std::move(extras)} + {} + + Test_fail_extras const& + extras() const + { + return extras_; + } + + private: + Test_fail_extras extras_; + + }; + +} // namespace code::validation + +#endif diff --git a/code/validation/exec.cxx b/code/validation/exec.cxx new file mode 100644 index 0000000..032386e --- /dev/null +++ b/code/validation/exec.cxx @@ -0,0 +1,118 @@ +#include + +#include +#include +#include +#include +#include +#include + +namespace code::validation +{ + + static + void + exec_setup() + { + auto current = Setup::first(); + + if (!current) { + return; + } + + do { + // fixme: report setup exceptions. fail entire unit test on exception. + // + current->run(); + current = current->next(); + } while (current && current != Setup::first()); + } + + static + void + exec_teardown() + { + auto current = Teardown::first(); + + if (!current) { + return; + } + + do { + // fixme: report teardown exceptions. fail entire unit test on exception. + // + current->run(); + current = current->next(); + } while (current && current != Teardown::first()); + } + + static + bool + exec_test_case(Test_summary& summary, Test_case& test_case) + { + try { + Utility::Current current{&test_case}; + + exec_setup(); + + try { + test_case.run(); + } + catch (...) { + exec_teardown(); + throw; + } + + exec_teardown(); + + summary.append({ + test_case.description(), + test_case.file(), + test_case.line(), + "passed" + }); + + summary.inc_pass(); + + return true; + } + catch (Fail const& fail) { + summary.append({ + test_case.description(), + test_case.file(), + test_case.line(), + "failed", + fail.extras() + }); + + summary.inc_fail(); + } + + return false; + } + + bool + exec(Test_summary& summary) + { + auto current = &Test_case::first(); + + if (!current) { + return true; + } + + bool all_passed{true}; + + do { + bool passed = exec_test_case(summary, *current); + + if (!passed) { + all_passed = false; + } + + current = ¤t->next(); + } while (current != &Test_case::first()); + + return all_passed; + } + +} // namespace code::validation diff --git a/code/validation/exec.hxx b/code/validation/exec.hxx new file mode 100644 index 0000000..6d62b07 --- /dev/null +++ b/code/validation/exec.hxx @@ -0,0 +1,16 @@ +#ifndef code__validation__exec_hxx_ +#define code__validation__exec_hxx_ + +#include + +#include + +namespace code::validation +{ + + bool + exec(Test_summary& summary); + +} // namespace code::validation + +#endif diff --git a/code/validation/macros.hxx b/code/validation/macros.hxx new file mode 100644 index 0000000..ba6dd3b --- /dev/null +++ b/code/validation/macros.hxx @@ -0,0 +1,54 @@ +#ifndef code__validation__macros_hxx_ +#define code__validation__macros_hxx_ + +#include +#include +#include +#include + +#define VALIDATION_TEST_SETUP_2(file, line) \ + static void test_setup_func_##line(); \ + code::validation::Setup test_setup_##line(file, line, &test_setup_func_##line); \ + void test_setup_func_##line() + +#define VALIDATION_TEST_SETUP_1(file, line) \ + VALIDATION_TEST_SETUP_2(file, line) + +#define VALIDATION_TEST_SETUP \ + VALIDATION_TEST_SETUP_1(__FILE__, __LINE__) + +#define VALIDATION_TEST_TEARDOWN_2(file, line) \ + static void test_teardown_func_##line(); \ + code::validation::Teardown test_teardown_##line(file, line, &test_teardown_func_##line); \ + void test_teardown_func_##line() + +#define VALIDATION_TEST_TEARDOWN_1(file, line) \ + VALIDATION_TEST_TEARDOWN_2(file, line) + +#define VALIDATION_TEST_TEARDOWN \ + VALIDATION_TEST_TEARDOWN_1(__FILE__, __LINE__) + +#define VALIDATION_TEST_2(name, file, line) \ + void test_func_##line(); \ + code::validation::Test_case test_##line(#name, (file), (line), &test_func_##line); \ + void test_func_##line() + +#define VALIDATION_TEST_1(name, file, line) \ + VALIDATION_TEST_2(name, file, line) + +#define VALIDATION_TEST(name) \ + VALIDATION_TEST_1(name, __FILE__, __LINE__) + +#define VALIDATION_ASSERT_TRUE(expr) \ + ::code::validation::Assert::assert_true((expr), #expr) + +#define VALIDATION_ASSERT_FALSE(expr) \ + ::code::validation::Assert::assert_false((expr), #expr) + +#define VALIDATION_ASSERT_NULL(expr) \ + ::code::validation::Assert::assert_null((expr), #expr) + +#define VALIDATION_ASSERT_EQUAL(lhs, rhs) \ + ::code::validation::Assert::assert_equal((lhs), (rhs), #lhs, #rhs) + +#endif diff --git a/code/validation/main.cxx b/code/validation/main.cxx new file mode 100644 index 0000000..8f8ffb8 --- /dev/null +++ b/code/validation/main.cxx @@ -0,0 +1,43 @@ +#include + +#include +#include + +#include + +namespace code::validation +{ + + int + main(int argc, char* argv[]) + { + int verbosity{0}; + bool print_stats{false}; + + for (int i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--verbose") == 0) { + ++verbosity; + } + else if (strcmp(argv[i], "-v") == 0) { + ++verbosity; + } + else if (strcmp(argv[i], "--stats") == 0) { + print_stats = true; + } + } + + Test_summary summary; + + bool success = exec(summary); + + bool print_summary = verbosity > 0; + bool print_extras = verbosity > 1; + + if (print_summary) { + summary.print(std::cout, print_extras, print_stats); + } + + return success ? 0 : -1; + } + +} // namespace code::validation diff --git a/code/validation/main.hxx b/code/validation/main.hxx new file mode 100644 index 0000000..12e5d6c --- /dev/null +++ b/code/validation/main.hxx @@ -0,0 +1,15 @@ +#ifndef code__validation__main_hxx_ +#define code__validation__main_hxx_ + +#include +#include + +namespace code::validation +{ + + int + main(int argc, char* argv[]); + +} // namespace code::validation + +#endif diff --git a/code/validation/setup.cxx b/code/validation/setup.cxx new file mode 100644 index 0000000..99fdeb7 --- /dev/null +++ b/code/validation/setup.cxx @@ -0,0 +1,60 @@ +#include + +namespace code::validation +{ + + Setup:: + Setup(std::string file, int line, std::function function) + : file_{std::move(file)}, line_{line}, function_{function} + {} + + std::string const& + Setup:: + file() const + { + return file_; + } + + int + Setup:: + line() const + { + return line_; + } + + Setup* + Setup:: + next() + { + return Utility::Intrusive_list::next(); + } + + Setup* + Setup:: + previous() + { + return Utility::Intrusive_list::previous(); + } + + void + Setup:: + run() + { + function_(); + } + + Setup* + Setup:: + first() + { + return Utility::Intrusive_list::first(); + } + + Setup* + Setup:: + last() + { + return Utility::Intrusive_list::last(); + } + +} // namespace code::validation diff --git a/code/validation/setup.hxx b/code/validation/setup.hxx new file mode 100644 index 0000000..945ce4a --- /dev/null +++ b/code/validation/setup.hxx @@ -0,0 +1,54 @@ +#ifndef code__validation__setup_hxx_ +#define code__validation__setup_hxx_ + +#include + +#include +#include +#include + +namespace code::validation +{ + + class Setup + : Utility::Intrusive_list + { + friend Utility::Intrusive_list; + + public: + Setup(std::string, int, std::function function); + + std::string const& + file() const; + + int + line() const; + + Setup* + next(); + + Setup* + previous(); + + void + run(); + + static + Setup* + first(); + + static + Setup* + last(); + + private: + std::string file_; + int line_; + std::function function_; + + }; + +} // namespace code::validation + +#endif + diff --git a/code/validation/teardown.cxx b/code/validation/teardown.cxx new file mode 100644 index 0000000..f673e79 --- /dev/null +++ b/code/validation/teardown.cxx @@ -0,0 +1,60 @@ +#include + +namespace code::validation +{ + + Teardown:: + Teardown(std::string file, int line, std::function function) + : file_{std::move(file)}, line_{line}, function_{function} + {} + + std::string const& + Teardown:: + file() const + { + return file_; + } + + int + Teardown:: + line() const + { + return line_; + } + + Teardown* + Teardown:: + next() + { + return Utility::Intrusive_list::next(); + } + + Teardown* + Teardown:: + previous() + { + return Utility::Intrusive_list::previous(); + } + + void + Teardown:: + run() + { + function_(); + } + + Teardown* + Teardown:: + first() + { + return Utility::Intrusive_list::first(); + } + + Teardown* + Teardown:: + last() + { + return Utility::Intrusive_list::last(); + } + +} // namespace code::validation diff --git a/code/validation/teardown.hxx b/code/validation/teardown.hxx new file mode 100644 index 0000000..d0e622c --- /dev/null +++ b/code/validation/teardown.hxx @@ -0,0 +1,54 @@ +#ifndef code__validation__teardown_hxx_ +#define code__validation__teardown_hxx_ + +#include + +#include +#include +#include + +namespace code::validation +{ + + class Teardown + : Utility::Intrusive_list + { + friend Utility::Intrusive_list; + + public: + Teardown(std::string, int, std::function function); + + std::string const& + file() const; + + int + line() const; + + Teardown* + next(); + + Teardown* + previous(); + + void + run(); + + static + Teardown* + first(); + + static + Teardown* + last(); + + private: + std::string file_; + int line_; + std::function function_; + + }; + +} // namespace code::validation + +#endif + diff --git a/code/validation/test-case.cxx b/code/validation/test-case.cxx new file mode 100644 index 0000000..e95e970 --- /dev/null +++ b/code/validation/test-case.cxx @@ -0,0 +1,73 @@ +#include + +namespace code::validation +{ + + Test_case:: + Test_case(std::string description, + std::string file, + int line, + std::function function) + : description_{std::move(description)}, + file_{std::move(file)}, + line_{line}, + function_{function} + {} + + std::string const& + Test_case:: + description() const + { + return description_; + } + + std::string const& + Test_case:: + file() const + { + return file_; + } + + int + Test_case:: + line() const + { + return line_; + } + + Test_case& + Test_case:: + next() + { + return *Utility::Intrusive_list::next(); + } + + Test_case& + Test_case:: + previous() + { + return *Utility::Intrusive_list::previous(); + } + + void + Test_case:: + run() + { + function_(); + } + + Test_case& + Test_case:: + first() + { + return *Utility::Intrusive_list::first(); + } + + Test_case& + Test_case:: + last() + { + return *Utility::Intrusive_list::last(); + } + +} // namespace code::validation diff --git a/code/validation/test-case.hxx b/code/validation/test-case.hxx new file mode 100644 index 0000000..063bf47 --- /dev/null +++ b/code/validation/test-case.hxx @@ -0,0 +1,57 @@ +#ifndef code__validation__test_case_hxx_ +#define code__validation__test_case_hxx_ + +#include + +#include +#include +#include + +namespace code::validation +{ + + class Test_case + : Utility::Intrusive_list + { + friend Utility::Intrusive_list; + + public: + Test_case(std::string, std::string, int, std::function function); + + std::string const& + description() const; + + std::string const& + file() const; + + int + line() const; + + Test_case& + next(); + + Test_case& + previous(); + + void + run(); + + static + Test_case& + first(); + + static + Test_case& + last(); + + private: + std::string description_; + std::string file_; + int line_; + std::function function_; + + }; + +} // namespace code::validation + +#endif diff --git a/code/validation/test-fail-extras.cxx b/code/validation/test-fail-extras.cxx new file mode 100644 index 0000000..fb4420a --- /dev/null +++ b/code/validation/test-fail-extras.cxx @@ -0,0 +1,13 @@ +#include + +namespace code::validation +{ + + void + Test_fail_extras:: + print(std::ostream& o) const + { + extras_->do_print(o); + } + +} diff --git a/code/validation/test-fail-extras.hxx b/code/validation/test-fail-extras.hxx new file mode 100644 index 0000000..5841848 --- /dev/null +++ b/code/validation/test-fail-extras.hxx @@ -0,0 +1,58 @@ +#ifndef code__validation__test_fail_extras_hxx_ +#define code__validation__test_fail_extras_hxx_ + +#include +#include + +namespace code::validation +{ + + class Test_fail_extras + { + public: + template + Test_fail_extras(T extras) + : extras_{std::make_shared>(std::move(extras))} + {} + + void + print(std::ostream&) const; + + private: + struct Abstraction + { + virtual + ~Abstraction() noexcept = default; + + virtual + void + do_print(std::ostream&) const = 0; + + }; + + template + struct Wrapper + : Abstraction + { + template + Wrapper(Args&&... args) + : extras{std::forward(args)...} + {} + + void + do_print(std::ostream& o) const override + { + extras.print(o); + } + + T extras; + + }; + + std::shared_ptr extras_; + + }; + +} // namespace code::validation + +#endif diff --git a/code/validation/test-result.cxx b/code/validation/test-result.cxx new file mode 100644 index 0000000..4ee122d --- /dev/null +++ b/code/validation/test-result.cxx @@ -0,0 +1,69 @@ +#include + +namespace code::validation +{ + + Test_result:: + Test_result(std::string description, + std::string file, + int line, + std::string message) + : description_{std::move(description)}, + file_{std::move(file)}, + line_{line_}, + message_{std::move(message)} + {} + + Test_result:: + Test_result(std::string description, + std::string file, + int line, + std::string message, + Test_fail_extras extras) + : description_{std::move(description)}, + file_{std::move(file)}, + line_{line_}, + message_{std::move(message)}, + extras_{extras} + {} + + std::string const& + Test_result:: + description() const + { + return description_; + } + + std::string const& + Test_result:: + file() const + { + return file_; + } + + int + Test_result:: + line() const + { + return line_; + } + + std::string const& + Test_result:: + message() const + { + return message_; + } + + void + Test_result:: + print(std::ostream& o, bool print_extras) const + { + o << "test: " << description() << ": " << message() << '\n'; + + if (print_extras && extras_) { + extras_->print(o); + } + } + +} // namespace code::validation diff --git a/code/validation/test-result.hxx b/code/validation/test-result.hxx new file mode 100644 index 0000000..031446b --- /dev/null +++ b/code/validation/test-result.hxx @@ -0,0 +1,45 @@ +#ifndef code__validation__test_result_hxx_ +#define code__validation__test_result_hxx_ + +#include + +#include +#include + +namespace code::validation +{ + + class Test_result + { + public: + Test_result(std::string, std::string, int, std::string); + + Test_result(std::string, std::string, int, std::string, Test_fail_extras); + + std::string const& + description() const; + + std::string const& + file() const; + + int + line() const; + + std::string const& + message() const; + + void + print(std::ostream&, bool) const; + + private: + std::string description_; + std::string file_; + int line_; + std::string message_; + std::optional extras_; + + }; + +} // namespace code::validation + +#endif diff --git a/code/validation/test-summary.cxx b/code/validation/test-summary.cxx new file mode 100644 index 0000000..1c980ed --- /dev/null +++ b/code/validation/test-summary.cxx @@ -0,0 +1,48 @@ +#include + +#include + +namespace code::validation +{ + + void + Test_summary:: + append(Test_result result) + { + results_.emplace_back(std::move(result)); + } + + void + Test_summary:: + inc_pass() + { + ++pass_; + } + + void + Test_summary:: + inc_fail() + { + ++fail_; + } + + void + Test_summary:: + print(std::ostream& o, bool print_extras, bool stats) const + { + for (auto const& j : results_) { + j.print(o, print_extras); + } + + if (stats) { + auto total = pass_ + fail_; + + o << "test statistics:\n" + << "tests : " << total << '\n' + << "tests passed: " << pass_ << '\n' + << "tests failed: " << fail_ << '\n' + ; + } + } + +} // namespace code::validation diff --git a/code/validation/test-summary.hxx b/code/validation/test-summary.hxx new file mode 100644 index 0000000..a10e502 --- /dev/null +++ b/code/validation/test-summary.hxx @@ -0,0 +1,38 @@ +#ifndef code__validation__test_summary_hxx_ +#define code__validation__test_summary_hxx_ + +#include + +#include +#include + +namespace code::validation +{ + + class Test_summary + { + public: + Test_summary() = default; + + void + append(Test_result); + + void + inc_pass(); + + void + inc_fail(); + + void + print(std::ostream&, bool, bool) const; + + private: + std::vector results_; + int pass_{}; + int fail_{}; + + }; + +} // namespace code::validation + +#endif diff --git a/code/validation/traits.hxx b/code/validation/traits.hxx new file mode 100644 index 0000000..53625e2 --- /dev/null +++ b/code/validation/traits.hxx @@ -0,0 +1,23 @@ +#ifndef code__validation__traits_hxx_ +#define code__validation__traits_hxx_ + +#include +#include + +namespace code::validation +{ + + template + concept has_output_operator = requires(std::ostream& o, T const& value) + { + // check if T can be written to an std::ostream. + // + { + o << value + }; + + }; + +} // namespace code::validation + +#endif diff --git a/code/validation/utility.hxx b/code/validation/utility.hxx new file mode 100644 index 0000000..8991445 --- /dev/null +++ b/code/validation/utility.hxx @@ -0,0 +1,138 @@ +#ifndef code__validation__utility_hxx_ +#define code__validation__utility_hxx_ + +#include + +namespace code::validation::Utility +{ + + template + class Intrusive_list + { + protected: + Intrusive_list() + { + if (!first_ || !last_) { + first_ = this; + last_ = this; + } + + first_->prev_ = this; + last_->next_ = this; + + prev_ = last_; + next_ = first_; + + last_ = this; + + ++counter_; + } + + Intrusive_list(Intrusive_list const&) = delete; + Intrusive_list(Intrusive_list&&) = delete; + + ~Intrusive_list() noexcept + { + if (first_ == this) { + first_ = next_; + } + + if (last_ == this) { + last_ = prev_; + } + + prev_->next_ = next_; + next_->prev_ = prev_; + + --counter_; + } + + Intrusive_list& operator=(Intrusive_list const&) = delete; + Intrusive_list& operator=(Intrusive_list&&) = delete; + + T* + previous() + { + return static_cast(prev_); + } + + T* + next() + { + return static_cast(next_); + } + + static + T* + first() + { + return static_cast(first_); + } + + static + T* + last() + { + return static_cast(last_); + } + + static + std::size_t + count() + { + return counter_; + } + + private: + Intrusive_list* prev_{nullptr}; + Intrusive_list* next_{nullptr}; + + static Intrusive_list* first_; + static Intrusive_list* last_; + + static std::size_t counter_; + + }; + + template + Intrusive_list* Intrusive_list::first_{nullptr}; + + template + Intrusive_list* Intrusive_list::last_{nullptr}; + + template + std::size_t Intrusive_list::counter_{0}; + + template + class Current + { + public: + explicit + Current(T* current) + { + current_ = current; + } + + ~Current() noexcept + { + current_ = nullptr; + } + + static + T* + get() + { + return current_; + } + + private: + static T* current_; + + }; + + template + T* Current::current_{nullptr}; + +} // namespace code::validation::Utility + +#endif diff --git a/code/validation/validate.hxx b/code/validation/validate.hxx new file mode 100644 index 0000000..c55a070 --- /dev/null +++ b/code/validation/validate.hxx @@ -0,0 +1,8 @@ +#ifndef code__validation__validation_hxx_ +#define code__validation__validation_hxx_ + +#include +#include +#include + +#endif diff --git a/code/validation/version.hxx.in b/code/validation/version.hxx.in new file mode 100644 index 0000000..788b81b --- /dev/null +++ b/code/validation/version.hxx.in @@ -0,0 +1,37 @@ +#ifndef code__validation___version_hxx_ +#define code__validation___version_hxx_ + +// The numeric version format is AAAAABBBBBCCCCCDDDE where: +// +// AAAAA - major version number +// BBBBB - minor version number +// CCCCC - bugfix version number +// DDD - alpha / beta (DDD + 500) version number +// E - final (0) / snapshot (1) +// +// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example: +// +// Version AAAAABBBBBCCCCCDDDE +// +// 0.1.0 0000000001000000000 +// 0.1.2 0000000001000020000 +// 1.2.3 0000100002000030000 +// 2.2.0-a.1 0000200001999990010 +// 3.0.0-b.2 0000299999999995020 +// 2.2.0-a.1.z 0000200001999990011 +// +#define LIBCODE_VALIDATION_VERSION $libcode_validation.version.project_number$ULL +#define LIBCODE_VALIDATION_VERSION_STR "$libcode_validation.version.project$" +#define LIBCODE_VALIDATION_VERSION_ID "$libcode_validation.version.project_id$" +#define LIBCODE_VALIDATION_VERSION_FULL "$libcode_validation.version$" + +#define LIBCODE_VALIDATION_VERSION_MAJOR $libcode_validation.version.major$ +#define LIBCODE_VALIDATION_VERSION_MINOR $libcode_validation.version.minor$ +#define LIBCODE_VALIDATION_VERSION_PATCH $libcode_validation.version.patch$ + +#define LIBCODE_VALIDATION_PRE_RELEASE $libcode_validation.version.pre_release$ + +#define LIBCODE_VALIDATION_SNAPSHOT_SN $libcode_validation.version.snapshot_sn$ULL +#define LIBCODE_VALIDATION_SNAPSHOT_ID "$libcode_validation.version.snapshot_id$" + +#endif diff --git a/manifest b/manifest new file mode 100644 index 0000000..73dc262 --- /dev/null +++ b/manifest @@ -0,0 +1,11 @@ +: 1 +name: libcode-validation +version: 0.1.0-a.0.z +language: c++ +summary: libcode-validation C++ library +license: BSD-4-Clause +description-file: README.md +url: https://helloryan.se/code/ +email: ryan@helloryan.se +depends: * build2 >= 0.17.0 +depends: * bpkg >= 0.17.0 diff --git a/repositories.manifest b/repositories.manifest new file mode 100644 index 0000000..dbbd25e --- /dev/null +++ b/repositories.manifest @@ -0,0 +1,2 @@ +: 1 +summary: libcode-validation project repository diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..662178d --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,8 @@ +# Test executables. +# +driver + +# Testscript output directories (can be symlinks). +# +test +test-* diff --git a/tests/basics/.gitignore b/tests/basics/.gitignore new file mode 100644 index 0000000..ac6110e --- /dev/null +++ b/tests/basics/.gitignore @@ -0,0 +1 @@ +basics diff --git a/tests/basics/basics.cxx b/tests/basics/basics.cxx new file mode 100644 index 0000000..72e7a40 --- /dev/null +++ b/tests/basics/basics.cxx @@ -0,0 +1,31 @@ +#include + +VALIDATION_TEST_SETUP +{ +} + +VALIDATION_TEST_TEARDOWN +{ +} + +VALIDATION_TEST(test_true) +{ + VALIDATION_ASSERT_TRUE(true == true); +} + +VALIDATION_TEST(test_false) +{ + VALIDATION_ASSERT_FALSE(true == false); +} + +VALIDATION_TEST(test_null) +{ + int* ptr{nullptr}; + VALIDATION_ASSERT_NULL(ptr); +} + +int +main(int argc, char* argv[]) +{ + return code::validation::main(argc, argv); +} diff --git a/tests/basics/buildfile b/tests/basics/buildfile new file mode 100644 index 0000000..accefee --- /dev/null +++ b/tests/basics/buildfile @@ -0,0 +1,4 @@ +import libs = libcode-validation%lib{code-validation} + +exe{basics}: {hxx ixx txx cxx}{**} $libs testscript{**} +exe{basics}: test.options = -v -v diff --git a/tests/build/.gitignore b/tests/build/.gitignore new file mode 100644 index 0000000..974e01d --- /dev/null +++ b/tests/build/.gitignore @@ -0,0 +1,4 @@ +/config.build +/root/ +/bootstrap/ +build/ diff --git a/tests/build/bootstrap.build b/tests/build/bootstrap.build new file mode 100644 index 0000000..a07b5ea --- /dev/null +++ b/tests/build/bootstrap.build @@ -0,0 +1,5 @@ +project = # Unnamed tests subproject. + +using config +using test +using dist diff --git a/tests/build/root.build b/tests/build/root.build new file mode 100644 index 0000000..a67b2fe --- /dev/null +++ b/tests/build/root.build @@ -0,0 +1,16 @@ +cxx.std = latest + +using cxx + +hxx{*}: extension = hxx +ixx{*}: extension = ixx +txx{*}: extension = txx +cxx{*}: extension = cxx + +# Every exe{} in this subproject is by default a test. +# +exe{*}: test = true + +# The test target for cross-testing (running tests under Wine, etc). +# +test.target = $cxx.target diff --git a/tests/buildfile b/tests/buildfile new file mode 100644 index 0000000..aeeab15 --- /dev/null +++ b/tests/buildfile @@ -0,0 +1 @@ +./: {*/ -build/}