├── .github └── workflows │ └── cupcake.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── conanfile.py ├── cupcake.json ├── include └── autocheck │ ├── arbitrary.hpp │ ├── autocheck.hpp │ ├── check.hpp │ ├── classifier.hpp │ ├── distribution.hpp │ ├── function.hpp │ ├── generator.hpp │ ├── generator_combinators.hpp │ ├── is_one_of.hpp │ ├── largest.hpp │ ├── ostream.hpp │ ├── reporter.hpp │ ├── sequence.hpp │ ├── tuple.hpp │ └── value.hpp └── tests ├── CMakeLists.txt ├── arbitrary.cpp ├── check.cpp ├── factorial.cpp ├── failure.cpp ├── generator.cpp ├── generator_combinators.cpp ├── insert-sorted.cpp ├── is_one_of.cpp ├── largest.cpp ├── reverse.cpp └── value.cpp /.github/workflows/cupcake.yml: -------------------------------------------------------------------------------- 1 | name: nix 2 | on: 3 | pull_request: 4 | push: 5 | branches: '**' 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | build-test-install: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | platform: 16 | - ubuntu 17 | - macos 18 | compiler: 19 | - gcc 20 | - clang 21 | id: [1,2,3,4,5,6] 22 | include: 23 | - id: 1 24 | cmake: '3.29.0' 25 | conan: 1 26 | generator: Unix Makefiles 27 | flavor: release 28 | linkage: static 29 | cppstd: 20 30 | - id: 2 31 | cmake: '3.21.0' 32 | conan: 1 33 | generator: Unix Makefiles 34 | flavor: release 35 | linkage: static 36 | cppstd: 20 37 | - id: 3 38 | cmake: '3.29.0' 39 | conan: 1 40 | generator: Ninja 41 | flavor: release 42 | linkage: static 43 | cppstd: 20 44 | - id: 4 45 | cmake: '3.29.0' 46 | conan: 1 47 | generator: Unix Makefiles 48 | flavor: debug 49 | linkage: static 50 | cppstd: 20 51 | - id: 5 52 | cmake: '3.29.0' 53 | conan: 1 54 | generator: Unix Makefiles 55 | flavor: release 56 | linkage: shared 57 | cppstd: 20 58 | - id: 6 59 | cmake: '3.29.0' 60 | conan: 1 61 | generator: Unix Makefiles 62 | flavor: release 63 | linkage: shared 64 | cppstd: 17 65 | - platform: ubuntu 66 | runner: ubuntu-24.04 67 | - platform: macos 68 | runner: macos-14 69 | - platform: ubuntu 70 | compiler: gcc 71 | profile: 72 | compiler: gcc 73 | version: 13 74 | cc: /usr/bin/gcc-13 75 | cxx: /usr/bin/g++-13 76 | - platform: ubuntu 77 | compiler: clang 78 | profile: 79 | compiler: clang 80 | version: 16 81 | cc: /usr/bin/clang-16 82 | cxx: /usr/bin/clang++-16 83 | - platform: macos 84 | compiler: gcc 85 | profile: 86 | compiler: gcc 87 | version: 13 88 | cc: gcc-13 89 | cxx: g++-13 90 | - platform: macos 91 | compiler: clang 92 | profile: 93 | compiler: apple-clang 94 | version: 15 95 | cc: /usr/bin/clang 96 | cxx: /usr/bin/clang++ 97 | runs-on: ${{ matrix.runner }} 98 | steps: 99 | - name: install Python 100 | uses: actions/setup-python@v5 101 | with: 102 | # The `imp` module is removed in Python 3.12 103 | # but required by Conan 1.x. 104 | python-version: '3.11' 105 | - name: install CMake 106 | uses: jwlawson/actions-setup-cmake@v2 107 | with: 108 | cmake-version: ${{ matrix.cmake }} 109 | - name: install XCode on macOS 110 | if: startsWith(matrix.platform, 'macos') 111 | uses: maxim-lobanov/setup-xcode@v1 112 | with: 113 | xcode-version: '15.3' 114 | - name: install Ninja on Linux 115 | if: matrix.generator == 'Ninja' && startsWith(matrix.platform, 'ubuntu') 116 | run: sudo apt install ninja-build 117 | - name: install Ninja on macOS 118 | if: matrix.generator == 'Ninja' && startsWith(matrix.platform, 'macos') 119 | run: brew install ninja 120 | - name: check compiler 121 | run: | 122 | ${{ matrix.profile.cc }} --version 123 | ${{ matrix.profile.cxx }} --version 124 | - name: checkout 125 | uses: actions/checkout@v4 126 | - name: install Conan and Cupcake 127 | run: pipx install 'conan~=${{ matrix.conan }}.0' cupcake 128 | - name: check environment 129 | run: | 130 | env | sort 131 | echo ${PATH} | tr ':' '\n' 132 | python --version 133 | conan --version 134 | cmake --version 135 | cupcake --version 136 | - name: configure Conan 137 | if: matrix.conan == 1 138 | run: | 139 | conan profile new default --detect 140 | conan profile update settings.compiler.cppstd=${{ matrix.cppstd }} default 141 | conan profile update settings.compiler=${{ matrix.profile.compiler }} default 142 | conan profile update settings.compiler.version=${{ matrix.profile.version }} default 143 | conan profile update env.CC=${{ matrix.profile.cc }} default 144 | conan profile update env.CXX=${{ matrix.profile.cxx }} default 145 | conan profile update 'conf.tools.build:compiler_executables={"c": "${{ matrix.profile.cc }}", "cpp": "${{ matrix.profile.cxx }}"}' default 146 | conan remote add github https://conan.jfreeman.dev 147 | - name: configure Conan on macOS 148 | if: runner.os == 'macOS' 149 | run: | 150 | conan profile update 'conf.tools.build:exelinkflags=["-Wl,-ld_classic"]' default 151 | - name: configure Conan for libstdc++ 152 | if: runner.os == 'Linux' || matrix.compiler == 'gcc' 153 | run: | 154 | conan profile update settings.compiler.libcxx=libstdc++11 default 155 | - run: cupcake build --verbose -G '${{ matrix.generator }}' --flavor ${{ matrix.flavor }} --${{ matrix.linkage }} 156 | - run: cupcake test --verbose 157 | - run: cupcake install 158 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # https://cliutils.gitlab.io/modern-cmake/chapters/intro/newcmake.html 2 | cmake_minimum_required(VERSION 3.20) 3 | 4 | project(autocheck 5 | VERSION 1.0.0 6 | LANGUAGES CXX 7 | DESCRIPTION "Header-only C++17 library for property-based testing." 8 | HOMEPAGE_URL "https://github.com/thejohnfreeman/autocheck" 9 | ) 10 | 11 | find_package(cupcake.cmake REQUIRED) 12 | cupcake_project( 13 | LICENSE "ISC" 14 | AUTHORS "John Freeman " 15 | ) 16 | cupcake_add_libraries() 17 | cupcake_enable_testing() 18 | # add_subdirectory(tutorial) 19 | cupcake_install_project() 20 | cupcake_install_cpp_info() 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 John Freeman 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autocheck 2 | 3 | Header-only C++17 library for QuickCheck (and later, SmallCheck) testing. 4 | 5 | Please consult the [wiki][] for documentation. 6 | 7 | [wiki]: http://github.com/thejohnfreeman/autocheck/wiki 8 | 9 | ## Install 10 | 11 | ```sh 12 | conan remote add redirectory https://conan.jfreeman.dev 13 | conan install autocheck/[*]@github/thejohnfreeman 14 | ``` 15 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | from conan import ConanFile, conan_version 2 | from conan.tools.cmake import CMake, cmake_layout 3 | from conan.tools.files import copy 4 | 5 | from functools import cached_property 6 | import json 7 | import pathlib 8 | 9 | class Autocheck(ConanFile): 10 | 11 | @cached_property 12 | def metadata(self): 13 | path = pathlib.Path(self.recipe_folder) / 'cupcake.json' 14 | with open(path, 'r') as file: 15 | return json.load(file) 16 | 17 | def set_name(self): 18 | if self.name is None: 19 | self.name = self.metadata['project']['name'] 20 | 21 | def set_version(self): 22 | if self.version is None: 23 | self.version = self.metadata['project']['version'] 24 | 25 | user = 'github' 26 | channel = 'thejohnfreeman' 27 | 28 | license = 'ISC' 29 | author = 'John Freeman ' 30 | 31 | settings = 'os', 'compiler', 'build_type', 'arch' 32 | options = {'shared': [True, False], 'fPIC': [True, False]} 33 | default_options = {'shared': False, 'fPIC': True} 34 | 35 | requires = [ 36 | # Available at https://conan.jfreeman.dev 37 | 'cupcake.cmake/1.1.1@github/thejohnfreeman', 38 | ] 39 | generators = ['CMakeDeps', 'CMakeToolchain'] 40 | 41 | exports_sources = [ 42 | 'CMakeLists.txt', 43 | 'cupcake.json', 44 | 'cmake/*', 45 | 'external/*', 46 | 'include/*', 47 | 'src/*', 48 | ] 49 | 50 | def export(self): 51 | copy(self, 'cupcake.json', self.recipe_folder, self.export_folder) 52 | 53 | # For out-of-source build. 54 | # https://docs.conan.io/en/latest/reference/build_helpers/cmake.html#configure 55 | no_copy_source = True 56 | 57 | def layout(self): 58 | cmake_layout(self) 59 | 60 | def requirements(self): 61 | methods = { 62 | 'tool': 'tool_requires', 63 | 'test': 'test_requires', 64 | } if conan_version.major.value == 2 else {} 65 | for requirement in self.metadata.get('imports', []): 66 | groups = requirement.get('groups', []) 67 | group = groups[0] if len(groups) == 1 else 'main' 68 | method = methods.get(group, 'requires') 69 | getattr(self, method)(requirement['reference']) 70 | 71 | def config_options(self): 72 | if self.settings.os == 'Windows': 73 | del self.options.fPIC 74 | 75 | def build(self): 76 | cmake = CMake(self) 77 | cmake.configure(variables={'BUILD_TESTING': 'NO'}) 78 | cmake.build() 79 | 80 | def package(self): 81 | cmake = CMake(self) 82 | cmake.install() 83 | 84 | def package_info(self): 85 | path = f'{self.package_folder}/share/{self.name}/cpp_info.py' 86 | with open(path, 'r') as file: 87 | exec(file.read(), {}, {'self': self.cpp_info}) 88 | -------------------------------------------------------------------------------- /cupcake.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "name": "autocheck", 4 | "version": "1.1.1" 5 | }, 6 | "libraries": [ 7 | { 8 | "name": "autocheck" 9 | } 10 | ], 11 | "imports": [ 12 | { 13 | "name": "catch2", 14 | "version": "3.6.0", 15 | "reference": "catch2/3.6.0", 16 | "file": "Catch2", 17 | "targets": [ 18 | "Catch2::Catch2", 19 | "Catch2::Catch2WithMain" 20 | ], 21 | "groups": [ 22 | "test" 23 | ] 24 | }, 25 | { 26 | "name": "gtest", 27 | "version": "1.14.0", 28 | "reference": "gtest/1.14.0", 29 | "file": "GTest", 30 | "targets": [ 31 | "GTest::gtest_main", 32 | "GTest::gtest" 33 | ], 34 | "groups": [ 35 | "test" 36 | ] 37 | } 38 | ], 39 | "tests": [ 40 | { 41 | "name": "arbitrary", 42 | "links": [ 43 | "autocheck.library", 44 | "GTest::gtest_main" 45 | ] 46 | }, 47 | { 48 | "name": "check", 49 | "links": [ 50 | "autocheck.library", 51 | "GTest::gtest_main" 52 | ] 53 | }, 54 | { 55 | "name": "generator_combinators", 56 | "links": [ 57 | "autocheck.library", 58 | "GTest::gtest_main" 59 | ] 60 | }, 61 | { 62 | "name": "generator", 63 | "links": [ 64 | "autocheck.library", 65 | "GTest::gtest_main" 66 | ] 67 | }, 68 | { 69 | "name": "is_one_of", 70 | "links": [ 71 | "autocheck.library", 72 | "GTest::gtest_main" 73 | ] 74 | }, 75 | { 76 | "name": "largest", 77 | "links": [ 78 | "autocheck.library", 79 | "GTest::gtest_main" 80 | ] 81 | }, 82 | { 83 | "name": "value", 84 | "links": [ 85 | "autocheck.library", 86 | "GTest::gtest_main" 87 | ] 88 | }, 89 | { 90 | "name": "reverse", 91 | "links": [ 92 | "${PROJECT_NAME}.library" 93 | ] 94 | }, 95 | { 96 | "name": "insert-sorted", 97 | "links": [ 98 | "${PROJECT_NAME}.library" 99 | ] 100 | }, 101 | { 102 | "name": "factorial", 103 | "links": [ 104 | "${PROJECT_NAME}.library", 105 | "Catch2::Catch2WithMain" 106 | ] 107 | } 108 | ] 109 | } 110 | -------------------------------------------------------------------------------- /include/autocheck/arbitrary.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_ARBITRARY_HPP 2 | #define AUTOCHECK_ARBITRARY_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace autocheck { 13 | 14 | /* Arbitrary produces a finite sequence, always in a tuple, ready for 15 | * application, and counts discards. */ 16 | 17 | template 18 | class arbitrary { 19 | static_assert(sizeof...(Gens) > 0, "cannot create unit arbitrary"); 20 | 21 | /* MSVC has difficulty with a direct `Gens...` parameter pack expansion 22 | * in the 3 public typedefs below, but can be convinced to work with a 23 | * trivial helper struct. */ 24 | template struct ExpandArgs : T {}; 25 | 26 | public: 27 | typedef std::tuple::result_type...> result_type; 28 | 29 | typedef typename predicate::result_type...>::type 30 | discard_t; 31 | typedef std::function::result_type&...)> 32 | prep_t; 33 | 34 | private: 35 | std::tuple gens; 36 | size_t count = 0; 37 | size_t num_discards = 0; 38 | size_t max_discards = 500; 39 | discard_t discard_f; 40 | resize_t resize_f = id(); 41 | prep_t prep_f; 42 | 43 | public: 44 | arbitrary() {} 45 | arbitrary(const Gens&... gens) : gens(gens...) {} 46 | 47 | bool operator() (value& candidate) { 48 | while (num_discards < max_discards) { 49 | /* Size starts at 0 and grows moderately. */ 50 | candidate = generate(gens, resize_f((num_discards + count) >> 1)); 51 | if (prep_f) { 52 | apply(prep_f, candidate.ref()); 53 | } 54 | if (discard_f && apply(discard_f, candidate.cref())) { 55 | ++num_discards; 56 | } else { 57 | ++count; 58 | return true; 59 | } 60 | } 61 | return false; 62 | } 63 | 64 | arbitrary& reset() { 65 | num_discards = 0; 66 | count = 0; 67 | } 68 | 69 | arbitrary& resize(const resize_t& r) { 70 | resize_f = r; 71 | return *this; 72 | } 73 | 74 | arbitrary& prep(const prep_t& p) { 75 | prep_f = p; 76 | return *this; 77 | } 78 | 79 | arbitrary& discard_if(const discard_t& d) { 80 | discard_f = d; 81 | return *this; 82 | } 83 | 84 | arbitrary& discard_at_most(size_t discards) { 85 | assert(discards > 0); 86 | max_discards = discards; 87 | return *this; 88 | } 89 | }; 90 | 91 | /* Combinators. */ 92 | 93 | template 94 | Arbitrary&& resize(const resize_t& r, Arbitrary&& arb) { 95 | arb.resize(r); 96 | return std::forward(arb); 97 | } 98 | 99 | template 100 | Arbitrary&& prep(const typename Arbitrary::prep_t& p, Arbitrary&& arb) { 101 | arb.prep(p); 102 | return std::forward(arb); 103 | } 104 | 105 | template 106 | Arbitrary&& discard_if(const typename Arbitrary::discard_t& d, 107 | Arbitrary&& arb) 108 | { 109 | arb.discard_if(d); 110 | return std::forward(arb); 111 | } 112 | 113 | template 114 | Arbitrary&& discard_at_most(size_t discards, Arbitrary&& arb) { 115 | arb.discard_at_most(discards); 116 | return std::forward(arb); 117 | } 118 | 119 | /* Factories. */ 120 | 121 | template 122 | arbitrary make_arbitrary(const Gens&... gens) { 123 | return arbitrary(gens...); 124 | } 125 | 126 | template 127 | arbitrary...> make_arbitrary() { 128 | return arbitrary...>(generator()...); 129 | } 130 | 131 | } 132 | 133 | #endif 134 | 135 | -------------------------------------------------------------------------------- /include/autocheck/autocheck.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | -------------------------------------------------------------------------------- /include/autocheck/check.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_CHECK_HPP 2 | #define AUTOCHECK_CHECK_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace autocheck { 14 | 15 | template 16 | T copy(const T& t) { return t; } 17 | 18 | /* Have to split defaults between two functions because of Clang ICE. */ 19 | 20 | template < 21 | typename... Args, 22 | typename Property, 23 | typename Arbitrary 24 | //, typename Classifier = classifier // ICEs Clang 25 | > 26 | void check(Property prop, 27 | size_t max_tests, 28 | Arbitrary arb, 29 | const reporter& rep = ostream_reporter(), 30 | classifier cls = classifier(), 31 | //Classifier cls = Classifier(), 32 | bool verbose = false) 33 | { 34 | assert(max_tests > 0); 35 | 36 | size_t tests = 0; 37 | 38 | typedef std::tuple args_t; 39 | value args; 40 | while (arb(args) && (++tests != max_tests)) { 41 | /* Get what we need from `args` before we let the user modify them. */ 42 | std::ostringstream reason; 43 | reason << std::boolalpha << args; 44 | if (verbose) { 45 | std::cout << reason.str() << std::endl; 46 | } 47 | 48 | cls.check(args.cref()); 49 | if (!apply(prop, args.ref())) { 50 | rep.failure(tests, reason.str().c_str()); 51 | return; 52 | } 53 | } 54 | 55 | rep.success(tests, max_tests, cls.trivial(), cls.distro()); 56 | } 57 | 58 | template 59 | void check(Property prop, size_t max_tests = 100) { 60 | check(std::move(prop), 61 | max_tests, 62 | arbitrary...>(), 63 | ostream_reporter(), 64 | classifier(), 65 | false); 66 | } 67 | 68 | } 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /include/autocheck/classifier.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_CLASSIFIER_HPP 2 | #define AUTOCHECK_CLASSIFIER_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace autocheck { 12 | 13 | template 14 | class classifier { 15 | public: 16 | typedef typename predicate::type pred_t; 17 | typedef std::function tagger_t; 18 | 19 | private: 20 | pred_t is_trivial; 21 | size_t num_trivial; 22 | std::vector taggers; 23 | std::unordered_map tag_cloud; 24 | 25 | public: 26 | classifier() : 27 | is_trivial(never()), num_trivial(0), taggers(), tag_cloud() {} 28 | 29 | classifier& trivial(const pred_t& pred) { 30 | is_trivial = pred; 31 | return *this; 32 | } 33 | 34 | classifier& collect(const tagger_t& tagger) { 35 | taggers.push_back(tagger); 36 | return *this; 37 | } 38 | 39 | template < 40 | typename Func, 41 | typename Enable = typename std::enable_if< 42 | !std::is_convertible< 43 | #if __cplusplus < 201703L 44 | typename std::result_of::type, 45 | #else 46 | typename std::invoke_result::type, 47 | #endif 48 | std::string 49 | >::value 50 | >::type 51 | > 52 | classifier& collect(const Func& func) { 53 | taggers.push_back( 54 | [=] (const Args&... args) -> std::string { 55 | std::ostringstream ss; 56 | ss << func(args...); 57 | return ss.str(); 58 | }); 59 | return *this; 60 | } 61 | 62 | classifier& classify(const pred_t& pred, const std::string& label) { 63 | taggers.push_back( 64 | [=] (const Args&... args) { 65 | return (pred(args...)) ? label : ""; 66 | }); 67 | return *this; 68 | } 69 | 70 | void check(const std::tuple& args) { 71 | if (apply(is_trivial, args)) ++num_trivial; 72 | 73 | std::string tags; 74 | for (tagger_t& tagger : taggers) { 75 | std::string tag = apply(tagger, args); 76 | if (tag.empty()) continue; 77 | if (!tags.empty()) tags += ", "; 78 | tags += tag; 79 | } 80 | if (!tags.empty()) ++tag_cloud[tags]; 81 | } 82 | 83 | size_t trivial() const { return num_trivial; } 84 | 85 | distribution distro() const { 86 | return distribution(tag_cloud.begin(), tag_cloud.end()); 87 | } 88 | }; 89 | 90 | template 91 | Classifier&& trivial( const typename Classifier::pred_t& pred, 92 | Classifier&& cls) 93 | { 94 | cls.trivial(pred); 95 | return std::forward(cls); 96 | } 97 | 98 | template 99 | Classifier&& collect(const typename Classifier::tagger_t& tagger, 100 | Classifier&& cls) 101 | { 102 | cls.collect(tagger); 103 | return std::forward(cls); 104 | } 105 | 106 | /* Missing lexical casting collect combinator, but no need until the 107 | * combinators can be used. */ 108 | 109 | template 110 | Classifier&& classify(const typename Classifier::pred_t& pred, 111 | const std::string& label, 112 | Classifier&& cls) 113 | { 114 | cls.classify(pred, label); 115 | return std::forward(cls); 116 | } 117 | 118 | } 119 | 120 | #endif 121 | 122 | -------------------------------------------------------------------------------- /include/autocheck/distribution.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_DISTRIBUTION_HPP 2 | #define AUTOCHECK_DISTRIBUTION_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace autocheck { 9 | 10 | typedef std::tuple dist_tag; 11 | typedef std::vector distribution; 12 | 13 | } 14 | 15 | #endif 16 | 17 | -------------------------------------------------------------------------------- /include/autocheck/function.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_FUNCTION_HPP 2 | #define AUTOCHECK_FUNCTION_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace autocheck { 8 | 9 | /* Generic identity function. */ 10 | struct id { 11 | template 12 | T&& operator() (T&& t) const { return std::forward(t); } 13 | }; 14 | 15 | struct always { 16 | template 17 | bool operator() (const Args&...) const { return true; } 18 | }; 19 | 20 | struct never { 21 | template 22 | bool operator() (const Args&...) const { return false; } 23 | }; 24 | 25 | template 26 | struct predicate { 27 | typedef std::function type; 28 | }; 29 | 30 | /* Type of functions that adjust size of generated value. */ 31 | typedef std::function resize_t; 32 | 33 | } 34 | 35 | #endif 36 | 37 | -------------------------------------------------------------------------------- /include/autocheck/generator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_GENERATOR_HPP 2 | #define AUTOCHECK_GENERATOR_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace autocheck { 18 | 19 | /* Reusable static standard random number generator. */ 20 | inline std::mt19937& rng() { 21 | static std::random_device rd; 22 | static std::mt19937 rng(rd()); 23 | return rng; 24 | } 25 | 26 | template 27 | T generate(std::tuple& gens, size_t size, 28 | const std::index_sequence&) 29 | { 30 | return T(std::get(gens)(size)...); 31 | } 32 | 33 | template 34 | T generate(std::tuple& gens, size_t size) { 35 | return autocheck::generate(gens, size, 36 | std::make_index_sequence()); 37 | } 38 | 39 | /* Generators produce an infinite sequence. */ 40 | 41 | template 42 | class generator; 43 | 44 | template <> 45 | class generator { 46 | public: 47 | typedef bool result_type; 48 | 49 | result_type operator() (size_t = 0) { 50 | static std::bernoulli_distribution dist(0.5); 51 | return dist(rng()); 52 | } 53 | }; 54 | 55 | namespace detail { 56 | 57 | static const char alnums[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 58 | "abcdefghijklmnopqrstuvwxyz" 59 | "0123456789"; 60 | /* Subtract 1 for NUL terminator. */ 61 | static const size_t nalnums = sizeof(alnums) - 1; 62 | static const size_t nprint = '~' - ' ' + 1; 63 | 64 | } 65 | 66 | enum CharCategory { 67 | ccAlphaNumeric, 68 | ccPrintable, 69 | ccAny 70 | }; 71 | 72 | template 73 | class char_generator { 74 | public: 75 | typedef CharType result_type; 76 | 77 | result_type operator() (size_t size = 0) { 78 | if (Category == ccAlphaNumeric || size < detail::nalnums) { 79 | size = detail::nalnums - 1; 80 | } else if (Category == ccPrintable || size < detail::nprint) { 81 | size = detail::nprint - 1; 82 | } else { 83 | size = std::numeric_limits::max(); 84 | } 85 | /* Distribution is non-static. */ 86 | std::uniform_int_distribution dist(0, size); 87 | auto i = dist(rng()); 88 | auto rv = 89 | (size < detail::nalnums) ? detail::alnums[i] : 90 | ((size < detail::nprint) ? ' ' + i : 91 | i); 92 | return rv; 93 | } 94 | }; 95 | 96 | /* WARNING: wchar_t, char16_t, char32_t, and family are typedefs. They 97 | * cannot be distinguished from regular (un)signed integrals, meaning we 98 | * cannot provide a general `generator` for them with the special char 99 | * consideration of size. If you want that consideration, use 100 | * char_generator specifically. */ 101 | 102 | template 103 | class generator< 104 | CharType, 105 | typename std::enable_if< 106 | is_one_of::value 107 | >::type 108 | > : public char_generator {}; 109 | 110 | template 111 | class generator< 112 | UnsignedIntegral, 113 | typename std::enable_if< 114 | is_one_of::value 116 | >::type 117 | > 118 | { 119 | public: 120 | typedef UnsignedIntegral result_type; 121 | 122 | result_type operator() (size_t size = 0) { 123 | /* Distribution is non-static. */ 124 | std::uniform_int_distribution dist(0, size); 125 | auto rv = dist(rng()); 126 | return rv; 127 | } 128 | }; 129 | 130 | template 131 | class generator< 132 | SignedIntegral, 133 | typename std::enable_if< 134 | is_one_of::value 135 | >::type 136 | > 137 | { 138 | public: 139 | typedef SignedIntegral result_type; 140 | 141 | result_type operator() (size_t size = 0) { 142 | auto s = static_cast(size >> 1); 143 | /* Distribution is non-static. */ 144 | std::uniform_int_distribution dist(-s, s); 145 | auto rv = dist(rng()); 146 | return rv; 147 | } 148 | }; 149 | 150 | /** Generate values in the range [low, high). */ 151 | template 152 | class range_generator { 153 | private: 154 | SignedIntegral low; 155 | SignedIntegral len; 156 | generator igen; 157 | 158 | public: 159 | range_generator(const SignedIntegral& low, const SignedIntegral& high) : 160 | low(low), len(high - low) 161 | { 162 | assert(len > 0); 163 | } 164 | 165 | typedef SignedIntegral result_type; 166 | 167 | result_type operator() (size_t size = 0) { 168 | auto i = igen(size) % len; 169 | i = (i > 0) ? i : (i + len); 170 | return low + i; 171 | } 172 | }; 173 | 174 | template 175 | range_generator range( 176 | const SignedIntegral& low, const SignedIntegral& high) 177 | { 178 | return range_generator(low, high); 179 | } 180 | 181 | template 182 | range_generator range(const SignedIntegral& high) { 183 | return range(0, high); 184 | } 185 | 186 | template 187 | class generator< 188 | Floating, 189 | typename std::enable_if< 190 | is_one_of::value 191 | >::type 192 | > 193 | { 194 | public: 195 | typedef Floating result_type; 196 | 197 | result_type operator() (size_t size = 0) { 198 | /* Distribution is non-static. */ 199 | Floating f_size = static_cast(size); 200 | std::uniform_real_distribution dist(-f_size, f_size); 201 | auto rv = dist(rng()); 202 | return rv; 203 | } 204 | }; 205 | 206 | template > 207 | class string_generator { 208 | private: 209 | CharGen chargen; 210 | 211 | public: 212 | string_generator(const CharGen& chargen = CharGen()) : 213 | chargen(chargen) {} 214 | 215 | typedef std::basic_string result_type; 216 | 217 | result_type operator() (size_t size = 0) { 218 | result_type rv; 219 | rv.reserve(size); 220 | std::generate_n(std::back_insert_iterator(rv), size, 221 | /* Scale characters faster than string size. */ 222 | fix(size << 2, chargen)); 223 | return rv; 224 | } 225 | }; 226 | 227 | template < 228 | CharCategory Category = ccPrintable, 229 | typename CharType = char 230 | > 231 | string_generator> string() { 232 | return string_generator>(); 233 | } 234 | 235 | template 236 | string_generator string(const CharGen& chargen) { 237 | return string_generator(); 238 | } 239 | 240 | template 241 | class generator> : 242 | public string_generator> {}; 243 | 244 | /* TODO: Generic sequence generator. */ 245 | 246 | template 247 | class list_generator { 248 | private: 249 | Gen eltgen; 250 | 251 | public: 252 | list_generator(const Gen& eltgen = Gen()) : 253 | eltgen(eltgen) {} 254 | 255 | typedef std::vector result_type; 256 | 257 | result_type operator() (size_t size = 0) { 258 | result_type rv; 259 | rv.reserve(size); 260 | std::generate_n(std::back_insert_iterator(rv), size, 261 | fix(size, eltgen)); 262 | return rv; 263 | } 264 | }; 265 | 266 | template 267 | list_generator list_of(const Gen& gen) { 268 | return list_generator(gen); 269 | } 270 | 271 | template > 272 | list_generator list_of() { 273 | return list_of(Gen()); 274 | } 275 | 276 | template 277 | class generator> : public list_generator> {}; 278 | 279 | /* Ordered list combinator. */ 280 | 281 | namespace detail { 282 | 283 | struct sorter { 284 | template 285 | std::vector operator() (std::vector&& a, size_t) { 286 | std::sort(a.begin(), a.end()); 287 | return a; 288 | } 289 | }; 290 | 291 | } 292 | 293 | template 294 | detail::mapped_generator> 295 | ordered_list(const Gen& gen) { 296 | return map(detail::sorter(), list_of(gen)); 297 | } 298 | 299 | template > 300 | detail::mapped_generator> 301 | ordered_list() { 302 | return ordered_list(Gen()); 303 | } 304 | 305 | /* Generic type generator (by construction). */ 306 | 307 | template 308 | class cons_generator { 309 | private: 310 | std::tuple gens; 311 | 312 | public: 313 | cons_generator() 314 | #ifndef _MSC_VER 315 | : gens(Gens()...) 316 | #endif 317 | {} 318 | 319 | cons_generator(const Gens&... gens) : 320 | gens(gens...) {} 321 | 322 | typedef T result_type; 323 | 324 | result_type operator() (size_t size = 0) { 325 | return autocheck::generate(gens, (size > 0) ? (size - 1) : size); 326 | } 327 | }; 328 | 329 | template 330 | cons_generator cons(const Gens&... gens) { 331 | return cons_generator(gens...); 332 | } 333 | 334 | template 335 | cons_generator...> cons() { 336 | return cons_generator...>(); 337 | } 338 | } 339 | 340 | #endif 341 | 342 | -------------------------------------------------------------------------------- /include/autocheck/generator_combinators.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_GENERATOR_COMBINATORS_HPP 2 | #define AUTOCHECK_GENERATOR_COMBINATORS_HPP 3 | 4 | #include 5 | 6 | namespace autocheck { 7 | 8 | namespace detail { 9 | 10 | /* Wrappers used by combinators. */ 11 | 12 | template 13 | class mapped_generator { 14 | public: 15 | 16 | 17 | typedef 18 | typename 19 | #if __cplusplus < 201703L 20 | std::result_of 21 | #else 22 | std::invoke_result 23 | #endif 24 | ::type 25 | result_type; 26 | 27 | private: 28 | Func func; 29 | Gen gen; 30 | 31 | public: 32 | mapped_generator(const Func& func, const Gen& gen) : 33 | func(func), gen(gen) {} 34 | 35 | result_type operator() (size_t size = 0) { 36 | return func(gen(size), size); 37 | } 38 | }; 39 | 40 | template 41 | class filtered_generator { 42 | public: 43 | typedef typename Gen::result_type result_type; 44 | 45 | private: 46 | Pred pred; 47 | Gen gen; 48 | 49 | public: 50 | filtered_generator(const Pred& pred, const Gen& gen) : 51 | pred(pred), gen(gen) {} 52 | 53 | result_type operator() (size_t size = 0) { 54 | while (true) { 55 | result_type rv(gen(size)); 56 | if (pred(rv)) return rv; 57 | } 58 | } 59 | }; 60 | 61 | template 62 | class resized_generator { 63 | public: 64 | typedef typename Gen::result_type result_type; 65 | 66 | private: 67 | Resize resizer; 68 | Gen gen; 69 | 70 | public: 71 | resized_generator(const Resize& resizer, const Gen& gen) : 72 | resizer(resizer), gen(gen) {} 73 | 74 | result_type operator() (size_t size = 0) { 75 | return gen(resizer(size)); 76 | } 77 | }; 78 | 79 | template 80 | class fixed_size_generator { 81 | private: 82 | size_t size; 83 | Gen gen; 84 | 85 | public: 86 | typedef typename Gen::result_type result_type; 87 | 88 | fixed_size_generator(size_t size, const Gen& gen) : 89 | size(size), gen(gen) {} 90 | 91 | result_type operator() (size_t = 0) { 92 | return gen(size); 93 | } 94 | }; 95 | 96 | } 97 | 98 | /* Generator combinators. */ 99 | 100 | template 101 | detail::mapped_generator map(const Func& func, const Gen& gen) { 102 | return detail::mapped_generator(func, gen); 103 | } 104 | 105 | template 106 | detail::filtered_generator such_that(const Pred& pred, 107 | const Gen& gen) 108 | { 109 | return detail::filtered_generator(pred, gen); 110 | } 111 | 112 | template 113 | detail::resized_generator resize(const Resize& resizer, 114 | const Gen& gen) 115 | { 116 | return detail::resized_generator(resizer, gen); 117 | } 118 | 119 | template 120 | detail::fixed_size_generator fix(size_t size, const Gen& gen) { 121 | return detail::fixed_size_generator(size, gen); 122 | } 123 | 124 | } 125 | 126 | #endif 127 | 128 | -------------------------------------------------------------------------------- /include/autocheck/is_one_of.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_IS_ONE_OF_HPP 2 | #define AUTOCHECK_IS_ONE_OF_HPP 3 | 4 | #include 5 | 6 | /* Template metaprogram to find a type in a list of types. */ 7 | 8 | namespace autocheck { 9 | 10 | template 11 | struct is_one_of : public std::disjunction< 12 | std::is_same< 13 | typename std::decay::type, 14 | typename std::decay::type 15 | >... 16 | > {}; 17 | 18 | } 19 | 20 | #endif 21 | 22 | -------------------------------------------------------------------------------- /include/autocheck/largest.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_LARGEST_HPP 2 | #define AUTOCHECK_LARGEST_HPP 3 | 4 | #include 5 | 6 | /* Template metaprogram to find the largest in a list of types. */ 7 | 8 | namespace autocheck { 9 | 10 | template 11 | struct largest1 : 12 | std::conditional< 13 | (sizeof(Head) > sizeof(typename largest1::type)), 14 | Head, 15 | typename largest1::type 16 | > 17 | {}; 18 | 19 | template 20 | struct largest1 { 21 | typedef Only type; 22 | }; 23 | 24 | template 25 | struct largest : largest1 { 26 | enum { size = sizeof(typename largest::type) }; 27 | }; 28 | 29 | } 30 | 31 | #endif 32 | 33 | -------------------------------------------------------------------------------- /include/autocheck/ostream.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_OSTREAM_HPP 2 | #define AUTOCHECK_OSTREAM_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /* Forward declarations of output stream operators. */ 9 | 10 | namespace autocheck { 11 | 12 | template 13 | std::ostream& operator<< (std::ostream& out, const std::tuple& tup); 14 | 15 | template 16 | std::ostream& operator<< (std::ostream& out, const std::vector& seq); 17 | 18 | } 19 | 20 | #endif 21 | 22 | -------------------------------------------------------------------------------- /include/autocheck/reporter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_REPORTER_HPP 2 | #define AUTOCHECK_REPORTER_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace autocheck { 13 | 14 | class reporter { 15 | public: 16 | virtual void success(size_t tests, size_t max_tests, 17 | size_t trivial = 0, 18 | distribution&& dist = distribution()) const = 0; 19 | virtual void failure(size_t tests, const char* reason) const = 0; 20 | virtual ~reporter() {} 21 | }; 22 | 23 | inline int round_percentage(size_t a, size_t b) { 24 | return static_cast(ceil(100.0 * a / b)); 25 | } 26 | 27 | inline void report_success(std::ostream& out, size_t tests, size_t max_tests, 28 | size_t trivial, distribution&& dist) 29 | { 30 | if (tests < max_tests) { 31 | out << "Arguments exhausted after " << tests << " tests." 32 | << std::endl; 33 | } else { 34 | assert(tests == max_tests); 35 | out << "OK, passed " << tests << " tests"; 36 | if (trivial) { 37 | out << " (" << round_percentage(trivial, tests) << "% trivial)"; 38 | } 39 | out << "." << std::endl; 40 | } 41 | 42 | /* Sort tags in descending order by size. */ 43 | std::sort(dist.begin(), dist.end(), 44 | [] (const dist_tag& a, const dist_tag& b) { 45 | return (std::get<1>(a) == std::get<1>(b)) 46 | ? (std::get<0>(a) < std::get<0>(b)) 47 | : (std::get<1>(a) > std::get<1>(b)); 48 | }); 49 | for (const dist_tag& tag : dist) { 50 | out << round_percentage(std::get<1>(tag), tests) << "% " 51 | << std::get<0>(tag) << "." << std::endl; 52 | } 53 | } 54 | 55 | inline void report_failure(std::ostream& out, size_t tests, const char* reason) { 56 | out << "Falsifiable, after " << tests << " tests:" << std::endl 57 | << reason << std::endl; 58 | } 59 | 60 | class ostream_reporter : public reporter { 61 | private: 62 | std::ostream& out; 63 | 64 | public: 65 | ostream_reporter(std::ostream& out = std::cout) : out(out) {} 66 | 67 | virtual void success(size_t tests, size_t max_tests, 68 | size_t trivial, distribution&& dist) const 69 | { 70 | report_success(out, tests, max_tests, trivial, std::move(dist)); 71 | } 72 | 73 | virtual void failure(size_t tests, const char* reason) const { 74 | report_failure(out, tests, reason); 75 | } 76 | }; 77 | 78 | #ifdef ASSERT_TRUE 79 | 80 | class gtest_reporter : public reporter { 81 | public: 82 | virtual void success(size_t tests, size_t max_tests, 83 | size_t trivial, distribution&& dist) const 84 | { 85 | report_success(std::clog, tests, max_tests, trivial, std::move(dist)); 86 | ASSERT_TRUE(true); 87 | } 88 | 89 | virtual void failure(size_t tests, const char* reason) const { 90 | std::ostringstream out; 91 | report_failure(out, tests, reason); 92 | static const bool AUTOCHECK_SUCCESS = false; 93 | ASSERT_TRUE(AUTOCHECK_SUCCESS) << out.str(); 94 | } 95 | }; 96 | 97 | #endif // ASSERT_TRUE 98 | 99 | #if defined(TEST_CASE) || defined(CATCH_TEST_CASE) 100 | 101 | class catch_reporter : public reporter { 102 | public: 103 | virtual void success(size_t tests, size_t max_tests, 104 | size_t trivial, distribution&& dist) const 105 | { 106 | report_success(std::clog, tests, max_tests, trivial, std::move(dist)); 107 | REQUIRE(true); 108 | } 109 | 110 | virtual void failure(size_t tests, const char* reason) const { 111 | std::ostringstream out; 112 | report_failure(out, tests, reason); 113 | FAIL(out.str()); 114 | } 115 | }; 116 | 117 | #endif // TEST_CASE 118 | 119 | } 120 | 121 | #endif 122 | 123 | -------------------------------------------------------------------------------- /include/autocheck/sequence.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_SEQUENCE_HPP 2 | #define AUTOCHECK_SEQUENCE_HPP 3 | 4 | #include 5 | 6 | namespace autocheck { 7 | 8 | template 9 | std::ostream& operator<< (std::ostream& out, const std::vector& seq) { 10 | out << "["; 11 | bool first = true; 12 | for (auto b = seq.begin(), e = seq.end(); b != e; ++b) { 13 | if (first) { 14 | first = false; 15 | } else { 16 | out << ", "; 17 | } 18 | out << *b; 19 | } 20 | out << "]"; 21 | return out; 22 | } 23 | 24 | } 25 | 26 | #endif 27 | 28 | -------------------------------------------------------------------------------- /include/autocheck/tuple.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_TUPLE_HPP 2 | #define AUTOCHECK_TUPLE_HPP 3 | 4 | #include 5 | 6 | namespace autocheck { 7 | namespace detail { 8 | 9 | template 10 | void print(std::ostream& out, const std::tuple& tup, 11 | const std::integral_constant&) 12 | { 13 | out << std::get<0>(tup); 14 | } 15 | 16 | template 17 | void print(std::ostream& out, const std::tuple& tup, 18 | const std::integral_constant& 19 | = std::integral_constant()) 20 | { 21 | print(out, tup, std::integral_constant()); 22 | out << ", "; 23 | out << std::get(tup); 24 | } 25 | 26 | } 27 | 28 | template 29 | std::ostream& operator<< (std::ostream& out, const std::tuple& tup) { 30 | out << "("; 31 | autocheck::detail::print(out, tup, 32 | std::integral_constant()); 33 | out << ")"; 34 | return out; 35 | } 36 | 37 | } 38 | 39 | #endif 40 | 41 | -------------------------------------------------------------------------------- /include/autocheck/value.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOCHECK_VALUE_HPP 2 | #define AUTOCHECK_VALUE_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | template 9 | void unused(T& x) {} 10 | 11 | namespace autocheck { 12 | 13 | template 14 | class value { 15 | private: 16 | // Visual Studio before 2015 doesn't support unrestricted unions. 17 | // However, if we declare `T object` as a member, 18 | // then we must declare the allocation as `Static`, 19 | // or else `object` will leak when we start to generate test values. 20 | enum { 21 | None, 22 | Static, 23 | Heap 24 | } allocation = 25 | #if !defined(_MSC_VER) || _MSC_VER >= 1900 26 | None; 27 | #else 28 | Static; 29 | #endif 30 | 31 | #if !defined(_MSC_VER) || _MSC_VER >= 1900 32 | union { 33 | #endif 34 | T* pointer = nullptr; 35 | T object; 36 | #if !defined(_MSC_VER) || _MSC_VER >= 1900 37 | }; 38 | #endif 39 | 40 | public: 41 | value() {} 42 | 43 | value(const value& copy) { *this = copy; } 44 | 45 | value& operator= (const value& rhs) { 46 | if (this == &rhs) return *this; 47 | 48 | if (rhs.allocation == Static) { 49 | construct(rhs.cref()); 50 | } else if (rhs.allocation == Heap) { 51 | ptr(new T(rhs.cref())); 52 | } 53 | 54 | return *this; 55 | } 56 | 57 | value& operator= (const T& rhs) { 58 | construct(rhs); 59 | return *this; 60 | } 61 | 62 | value& operator= (T* rhs) { 63 | ptr(rhs); 64 | return *this; 65 | } 66 | 67 | bool empty() const { return allocation == None; } 68 | 69 | template 70 | void construct(const Args&... args) { 71 | clear(); 72 | T* p = new (&object) T(args...); 73 | assert(p == &object); 74 | unused(p); 75 | allocation = Static; 76 | } 77 | 78 | const T* ptr() const { 79 | return (allocation == Heap) ? pointer : &object; 80 | } 81 | 82 | T* ptr() { 83 | return (allocation == Heap) ? pointer : &object; 84 | } 85 | 86 | void ptr(T* p) { 87 | clear(); 88 | pointer = p; 89 | allocation = p ? Heap : None; 90 | } 91 | 92 | T* operator-> () { return ptr(); } 93 | const T* operator-> () const { return ptr(); } 94 | 95 | T& ref() { return *ptr(); } 96 | const T& ref() const { return *ptr(); } 97 | const T& cref() const { return *ptr(); } 98 | 99 | operator T& () { return ref(); } 100 | operator const T& () const { return cref(); } 101 | 102 | void clear() { 103 | if (allocation == Heap) { 104 | delete ptr(); 105 | } else if (allocation == Static) { 106 | ptr()->~T(); 107 | } 108 | allocation = None; 109 | } 110 | 111 | ~value() { clear(); } 112 | 113 | }; 114 | 115 | template 116 | std::ostream& operator<< (std::ostream& out, const value& v) { 117 | return out << v.cref(); 118 | } 119 | 120 | } 121 | 122 | #endif 123 | 124 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cupcake_find_packages(test PRIVATE) 2 | cupcake_link_libraries(${PROJECT_NAME}.imports.test INTERFACE test) 3 | cupcake_add_tests() 4 | -------------------------------------------------------------------------------- /tests/arbitrary.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace ac = autocheck; 9 | 10 | static const size_t limit = 20; 11 | 12 | TEST(ArbitraryBool, Size) { 13 | auto arb = ac::make_arbitrary(); 14 | std::clog << "sizeof(arb) = " << sizeof(arb) << std::endl; 15 | } 16 | 17 | TEST(ArbitraryInt, Size) { 18 | auto arb = ac::make_arbitrary(); 19 | std::clog << "sizeof(arb) = " << sizeof(arb) << std::endl; 20 | } 21 | 22 | TEST(Arbitrary, Generating) { 23 | auto arb = ac::make_arbitrary(); 24 | 25 | ac::value> args; 26 | std::clog << std::boolalpha; 27 | for (int i = 0; i < limit; ++i) { 28 | ASSERT_TRUE(arb(args)); 29 | if (i > 0) std::clog << ", "; 30 | std::clog << args; 31 | } 32 | std::clog << std::endl; 33 | 34 | ASSERT_TRUE(arb(args)); 35 | } 36 | 37 | TEST(Arbitrary, Only) { 38 | auto arb = ac::make_arbitrary(); 39 | arb.discard_if([] (bool b) { return b; }); 40 | 41 | ac::value> b; 42 | std::clog << std::boolalpha; 43 | for (int i = 0; i < limit; ++i) { 44 | arb(b); 45 | ASSERT_FALSE(std::get<0>(b.cref())); 46 | if (i > 0) std::clog << ", "; 47 | std::clog << b; 48 | } 49 | std::clog << std::endl; 50 | } 51 | 52 | TEST(Arbitrary, AtMost) { 53 | auto arb = ac::make_arbitrary(); 54 | arb.discard_if([] (bool b) { return b; }); 55 | arb.discard_at_most(limit); 56 | 57 | ac::value> b; 58 | std::clog << std::boolalpha; 59 | bool first = true; 60 | while (arb(b)) { 61 | if (first) { 62 | first = false; 63 | } else { 64 | std::clog << ", "; 65 | } 66 | ASSERT_FALSE(std::get<0>(b.cref())); 67 | std::clog << b; 68 | } 69 | std::clog << std::endl; 70 | 71 | ASSERT_FALSE(arb(b)); 72 | } 73 | 74 | TEST(Arbitrary, Combinator) { 75 | auto arb = 76 | ac::discard_if([] (bool b) { return !b; }, 77 | ac::discard_at_most(limit, 78 | ac::make_arbitrary())); 79 | 80 | ac::value> b; 81 | while (arb(b)) { 82 | ASSERT_TRUE(std::get<0>(b.cref())); 83 | } 84 | 85 | ASSERT_FALSE(arb(b)); 86 | } 87 | 88 | -------------------------------------------------------------------------------- /tests/check.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace ac = autocheck; 10 | 11 | struct reverse_prop_t { 12 | template 13 | bool operator() (Container& xs) const { 14 | Container ys(xs); 15 | std::reverse(xs.begin(), xs.end()); 16 | std::reverse(xs.begin(), xs.end()); 17 | return xs == ys; 18 | } 19 | }; 20 | 21 | template 22 | void insert_sorted(const T& x, std::vector& xs) { 23 | xs.push_back(x); 24 | if (xs.size() == 1) return; 25 | for (std::vector::reverse_iterator 26 | b = xs.rbegin(), a = b++, e = xs.rend(); (b != e) && (*a < *b); ++b, ++a) 27 | { 28 | std::iter_swap(a, b); 29 | } 30 | } 31 | 32 | TEST(Check, Compiles) { 33 | ac::gtest_reporter rep; 34 | 35 | ac::check([] (bool x) { return true; }, 100, 36 | ac::make_arbitrary(), rep); 37 | 38 | ac::check([] (bool x) { return true; }); 39 | 40 | reverse_prop_t reverse_prop; 41 | 42 | ac::check>(reverse_prop, 100, 43 | ac::make_arbitrary(ac::list_of()), rep); 44 | ac::check>(reverse_prop, 100, 45 | ac::make_arbitrary(ac::cons, unsigned int, int>()), 46 | rep); 47 | 48 | ac::check>(reverse_prop, 100, 49 | ac::make_arbitrary(ac::list_of(ac::generator())), rep); 50 | ac::check>(reverse_prop, 100, 51 | ac::make_arbitrary(ac::list_of()), rep); 52 | 53 | ac::check(reverse_prop, 100, 54 | ac::make_arbitrary(), rep); 55 | ac::check(reverse_prop, 100, 56 | ac::make_arbitrary(ac::string<>()), 57 | rep); 58 | 59 | /* Chaining, ... */ 60 | //auto arb = ac::make_arbitrary(ac::generator(), ac::ordered_list()) 61 | //.discard_if([] (int, const std::vector& xs) -> bool { return !std::is_sorted(xs.begin(), xs.end()); }) 62 | //.discard_at_most(100); 63 | 64 | /* ... or combinators. */ 65 | auto arb = 66 | ac::discard_if([] (int, const std::vector& xs) -> bool { return !std::is_sorted(xs.begin(), xs.end()); }, 67 | ac::discard_at_most(100, 68 | //ac::make_arbitrary(ac::generator(), ac::ordered_list()))); 69 | ac::make_arbitrary>())); 70 | //ac::make_arbitrary(ac::generator(), ac::list_of()))); 71 | 72 | ac::classifier> cls; 73 | cls.trivial([] (int, const std::vector& xs) { return xs.empty(); }); 74 | cls.collect([] (int x, const std::vector& xs) { return xs.size(); }); 75 | cls.classify([] (int x, const std::vector& xs) { return xs.empty() || (x < xs.front()); }, "at-head"); 76 | cls.classify([] (int x, const std::vector& xs) { return xs.empty() || (xs.back() < x); }, "at-tail"); 77 | 78 | /* Can't use combinators here because it breaks template deduction (?). */ 79 | //ac::classifier> cls( 80 | //ac::trivial([] (int, const std::vector& xs) { return xs.empty(); }, 81 | //ac::collect([] (int x, const std::vector& xs) { return xs.size(); }, 82 | //ac::classify([] (int x, const std::vector& xs) { return xs.empty() || (x < xs.front()); }, "at-head", 83 | //ac::classify([] (int x, const std::vector& xs) { return xs.empty() || (xs.back() < x); }, "at-tail", 84 | //ac::classifier>())))); 85 | 86 | ac::check>( 87 | [] (int x, std::vector& xs) -> bool { 88 | insert_sorted(x, xs); 89 | return std::is_sorted(xs.begin(), xs.end()); 90 | }, 91 | 100, ac::copy(arb), rep, ac::copy(cls)); 92 | } 93 | 94 | -------------------------------------------------------------------------------- /tests/factorial.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | std::size_t Factorial( const std::size_t number ) { 7 | #define MAKE_IT_FAIL 0 8 | #if MAKE_IT_FAIL 9 | return number <= 1 ? number : Factorial(number-1)*number; 10 | #else 11 | return number > 1 ? Factorial(number-1)*number : 1; 12 | #endif 13 | } 14 | 15 | TEST_CASE( "Factorials are computed", "[factorial]" ) { 16 | SECTION( "Examples" ) { 17 | REQUIRE( Factorial(1) == 1 ); 18 | REQUIRE( Factorial(2) == 2 ); 19 | REQUIRE( Factorial(3) == 6 ); 20 | REQUIRE( Factorial(10) == 3628800 ); 21 | } 22 | SECTION( "Properties" ) { 23 | SECTION( "Factorial is always greater than 1" ) { 24 | autocheck::catch_reporter rep; 25 | autocheck::check( 26 | [] (size_t x) { return Factorial(x) > 0; }, 27 | 100, 28 | autocheck::discard_if([](const size_t s){ return s > 30;}, 29 | autocheck::make_arbitrary()), 30 | rep); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/failure.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace ac = autocheck; 7 | 8 | TEST(Check, DemonstrateFailingTest) { 9 | ac::gtest_reporter rep; 10 | /* This tests that every signed integer is positive. It is intended to 11 | * demonstrate failure behavior. */ 12 | ac::check([] (int x) { return x >= 0; }, 100, 13 | ac::make_arbitrary(), rep); 14 | } 15 | -------------------------------------------------------------------------------- /tests/generator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace ac = autocheck; 9 | 10 | static const size_t limit = 10; 11 | 12 | template 13 | void generate() { 14 | auto gen = ac::generator(); 15 | std::clog << "sizeof(gen) = " << sizeof(gen) << std::endl; 16 | 17 | std::clog << std::boolalpha; 18 | for (int i = 0; i < limit; ++i) { 19 | if (i > 0) std::clog << ", "; 20 | using namespace autocheck; 21 | std::clog << gen(i); 22 | } 23 | std::clog << '\n'; 24 | } 25 | 26 | #define TEST_GENERATOR(name, type)\ 27 | TEST(Generator, name) {\ 28 | generate();\ 29 | } 30 | 31 | TEST_GENERATOR(Bool, bool); 32 | 33 | TEST_GENERATOR(Char, char); 34 | TEST_GENERATOR(SignedChar, signed char); 35 | TEST_GENERATOR(UnsignedChar, unsigned char); 36 | 37 | TEST_GENERATOR(Short, short); 38 | TEST_GENERATOR(SignedShort, signed short); 39 | TEST_GENERATOR(UnsignedShort, unsigned short); 40 | 41 | TEST_GENERATOR(Int, int); 42 | TEST_GENERATOR(SignedInt, signed int); 43 | TEST_GENERATOR(UnsignedInt, unsigned int); 44 | 45 | TEST_GENERATOR(Long, long); 46 | TEST_GENERATOR(SignedLong, signed long); 47 | TEST_GENERATOR(UnsignedLong, unsigned long); 48 | 49 | TEST_GENERATOR(Float, float); 50 | TEST_GENERATOR(Double, double); 51 | 52 | TEST_GENERATOR(String, std::string); 53 | 54 | TEST_GENERATOR(VectorOfInt, std::vector); 55 | 56 | -------------------------------------------------------------------------------- /tests/generator_combinators.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace ac = autocheck; 8 | 9 | static const size_t limit = 10; 10 | 11 | TEST(Generator, Map) { 12 | auto gen = ac::map([] (bool, size_t n) { return n; }, 13 | ac::generator()); 14 | std::clog << "sizeof(gen) = " << sizeof(gen) << std::endl; 15 | 16 | const int n = 42; 17 | for (int i = 0; i < limit; ++i) { 18 | ASSERT_EQ(n, gen(n)); 19 | } 20 | } 21 | 22 | TEST(Generator, SuchThat) { 23 | auto gen = ac::such_that([] (bool b) { return b; }, 24 | ac::generator()); 25 | std::clog << "sizeof(gen) = " << sizeof(gen) << std::endl; 26 | 27 | for (int i = 0; i < limit; ++i) { 28 | ASSERT_TRUE(gen()); 29 | } 30 | } 31 | 32 | TEST(Generator, Resize) { 33 | auto gen = ac::resize([] (size_t size) { return size * 2; }, 34 | ac::generator()); 35 | std::clog << "sizeof(gen) = " << sizeof(gen) << std::endl; 36 | 37 | for (int i = 0; i < limit; ++i) { 38 | gen(); 39 | } 40 | } 41 | 42 | TEST(Generator, Composition) { 43 | const int factor = 2; 44 | auto gen = ac::resize([factor] (size_t size) { return size * factor; }, 45 | ac::map([] (bool, size_t n) { return n; }, 46 | ac::generator())); 47 | std::clog << "sizeof(gen) = " << sizeof(gen) << std::endl; 48 | 49 | const int n = 42; 50 | for (int i = 0; i < limit; ++i) { 51 | ASSERT_EQ(n * factor, gen(n)); 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /tests/insert-sorted.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | namespace ac = autocheck; 5 | 6 | template 7 | void insert_sorted(const T& x, std::vector& xs) { 8 | xs.push_back(x); 9 | if (xs.size() == 1) return; 10 | for (std::vector::reverse_iterator 11 | b = xs.rbegin(), a = b++, e = xs.rend(); (b != e) && (*a < *b); ++b, ++a) 12 | { 13 | std::iter_swap(a, b); 14 | } 15 | } 16 | 17 | struct prop_insert_sorted_t { 18 | template 19 | bool operator() (const T& x, std::vector& xs) { 20 | insert_sorted(x, xs); 21 | return std::is_sorted(xs.begin(), xs.end()); 22 | } 23 | }; 24 | 25 | template 26 | struct ordered_list_gen { 27 | ac::generator> source; 28 | 29 | typedef std::vector result_type; 30 | 31 | std::vector operator() (size_t size) { 32 | result_type xs(source(size)); 33 | std::sort(xs.begin(), xs.end()); 34 | return std::move(xs); 35 | } 36 | }; 37 | 38 | int main() { 39 | ac::check>(prop_insert_sorted_t(), 40 | 100, ac::make_arbitrary>()); 41 | 42 | auto arb = ac::make_arbitrary>(); 43 | arb.discard_if( 44 | [] (int, const std::vector& xs) { 45 | return !std::is_sorted(xs.begin(), xs.end()); 46 | }); 47 | 48 | ac::check>(prop_insert_sorted_t(), 49 | 100, ac::copy(arb)); 50 | 51 | arb.discard_at_most(2000); 52 | 53 | ac::check>(prop_insert_sorted_t(), 54 | 100, ac::copy(arb)); 55 | 56 | arb.prep( 57 | [] (int, std::vector& xs) { 58 | std::sort(xs.begin(), xs.end()); 59 | }); 60 | 61 | ac::check>(prop_insert_sorted_t(), 62 | 100, ac::copy(arb)); 63 | 64 | ac::check>(prop_insert_sorted_t(), 100, 65 | ac::make_arbitrary(ac::generator(), ordered_list_gen())); 66 | 67 | ac::check>(prop_insert_sorted_t(), 100, 68 | ac::make_arbitrary(ac::generator(), ac::ordered_list())); 69 | 70 | ac::classifier> cls; 71 | cls.trivial( 72 | [] (int, const std::vector& xs) { 73 | return xs.empty(); 74 | }); 75 | 76 | ac::ostream_reporter rep; 77 | ac::check>(prop_insert_sorted_t(), 78 | 100, ac::copy(arb), ac::copy(rep), ac::copy(cls)); 79 | 80 | cls.collect([] (int x, const std::vector& xs) { return xs.size(); }); 81 | 82 | ac::check>(prop_insert_sorted_t(), 83 | 100, ac::copy(arb), ac::copy(rep), ac::copy(cls)); 84 | 85 | cls.classify([] (int x, const std::vector& xs) { return xs.empty() || (x < xs.front()); }, "at-head"); 86 | cls.classify([] (int x, const std::vector& xs) { return xs.empty() || (xs.back() < x); }, "at-tail"); 87 | 88 | ac::check>(prop_insert_sorted_t(), 89 | 100, ac::copy(arb), ac::copy(rep), ac::copy(cls)); 90 | 91 | return 0; 92 | } 93 | 94 | -------------------------------------------------------------------------------- /tests/is_one_of.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace ac = autocheck; 6 | 7 | TEST(IsOneOf, Same) { 8 | ASSERT_TRUE((ac::is_one_of::value)); 9 | } 10 | 11 | TEST(IsOneOf, DecayReference) { 12 | ASSERT_TRUE((ac::is_one_of::value)); 13 | ASSERT_TRUE((ac::is_one_of::value)); 14 | } 15 | 16 | TEST(IsOneOf, DecayArray) { 17 | ASSERT_TRUE((ac::is_one_of::value)); 18 | ASSERT_TRUE((ac::is_one_of::value)); 19 | } 20 | 21 | TEST(IsOneOf, Signed) { 22 | ASSERT_TRUE((ac::is_one_of::value)); 23 | ASSERT_TRUE((ac::is_one_of::value)); 24 | } 25 | 26 | TEST(IsOneOf, Unsigned) { 27 | ASSERT_FALSE((ac::is_one_of::value)); 28 | ASSERT_FALSE((ac::is_one_of::value)); 29 | } 30 | 31 | TEST(IsOneOf, SearchSuccess) { 32 | ASSERT_TRUE((ac::is_one_of::value)); 33 | } 34 | 35 | TEST(IsOneOf, SearchFailure) { 36 | ASSERT_FALSE((ac::is_one_of::value)); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /tests/largest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace ac = autocheck; 6 | 7 | TEST(Largest, One) { 8 | ASSERT_EQ(sizeof(char), ac::largest::size); 9 | } 10 | 11 | TEST(Largest, Two) { 12 | ASSERT_EQ(sizeof(int), (ac::largest::size)); 13 | } 14 | 15 | TEST(Largest, Three) { 16 | ASSERT_EQ(sizeof(long), (ac::largest::size)); 17 | } 18 | 19 | TEST(Largest, Four) { 20 | ASSERT_EQ(sizeof(long long), 21 | (ac::largest::size)); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /tests/reverse.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | namespace ac = autocheck; 5 | 6 | template 7 | bool prop_reverse_f(const Container& xs) { 8 | Container ys(xs); 9 | std::reverse(ys.begin(), ys.end()); 10 | std::reverse(ys.begin(), ys.end()); 11 | return xs == ys; 12 | } 13 | 14 | struct prop_reverse_t { 15 | template 16 | bool operator() (const Container& xs) const { 17 | return prop_reverse_f(xs); 18 | } 19 | }; 20 | 21 | struct bad_prop_reverse_t { 22 | template 23 | bool operator() (const Container& xs) const { 24 | Container ys(xs); 25 | std::reverse(ys.begin(), ys.end()); 26 | return xs == ys; 27 | } 28 | }; 29 | 30 | int main() { 31 | ac::check>(prop_reverse_t()); 32 | 33 | ac::check>(prop_reverse_t(), 300); 34 | 35 | ac::check>(bad_prop_reverse_t()); 36 | 37 | return 0; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /tests/value.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | struct leaker { 6 | static int alive; 7 | leaker() { ++alive; } 8 | leaker(int*) { ++alive; } 9 | leaker(const leaker&) { ++alive; } 10 | ~leaker() { --alive; } 11 | }; 12 | 13 | int leaker::alive = 0; 14 | 15 | namespace ac = autocheck; 16 | 17 | struct Value : public ::testing::Test { 18 | Value() { leaker::alive = 0; } 19 | 20 | void TearDown() { 21 | ASSERT_EQ(0, leaker::alive); 22 | } 23 | }; 24 | 25 | TEST_F(Value, Default) { 26 | ac::value val; 27 | } 28 | 29 | TEST_F(Value, ConstructDefault) { 30 | ac::value val; 31 | val.construct(); 32 | } 33 | 34 | TEST_F(Value, ConstructUnary) { 35 | ac::value val; 36 | val.construct(nullptr); 37 | } 38 | 39 | TEST_F(Value, Heap) { 40 | ac::value val; 41 | val.ptr(new leaker()); 42 | } 43 | 44 | TEST_F(Value, Assign) { 45 | ac::value val; 46 | val = new leaker(); 47 | val = leaker(); 48 | val = leaker(); 49 | val = new leaker(); 50 | } 51 | 52 | TEST_F(Value, Read) { 53 | ac::value val; 54 | val = 1; 55 | ASSERT_EQ(1, val); 56 | val = 2; 57 | ASSERT_EQ(2, val); 58 | val = new int(3); 59 | ASSERT_EQ(3, val); 60 | val = 4; 61 | ASSERT_EQ(4, val); 62 | } 63 | 64 | --------------------------------------------------------------------------------