pax_global_header00006660000000000000000000000064142604212140014506gustar00rootroot0000000000000052 comment=ea8c36c65bf9cf83aaf6b0db971248c6ae3686cf libcuckoo-0.3.1/000077500000000000000000000000001426042121400134615ustar00rootroot00000000000000libcuckoo-0.3.1/.clang-format000066400000000000000000000053351426042121400160420ustar00rootroot00000000000000--- Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: false AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false IndentBraces: false BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '$' IndentCaseLabels: false IndentWidth: 2 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: true SortIncludes: true SpaceAfterCStyleCast: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 8 UseTab: Never ... libcuckoo-0.3.1/.gitignore000066400000000000000000000000061426042121400154450ustar00rootroot00000000000000*.swp libcuckoo-0.3.1/CMakeLists.txt000066400000000000000000000007701426042121400162250ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) project(libcuckoo VERSION 0.3.1 LANGUAGES C CXX) # put these in the cache so they show up in ccmake option (BUILD_EXAMPLES "build example libcuckoo programs") # Add the libcuckoo interface target add_subdirectory(libcuckoo) # Add C interface target add_subdirectory(libcuckoo-c) # Build examples if(BUILD_EXAMPLES) add_subdirectory(examples) endif() # Build tests -- this only builds tests that were specified enable_testing() add_subdirectory(tests) libcuckoo-0.3.1/LICENSE000066400000000000000000000013071426042121400144670ustar00rootroot00000000000000Copyright (C) 2013, Carnegie Mellon University and Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --------------------------- The third-party libraries have their own licenses, as detailed in their source files. libcuckoo-0.3.1/README.md000066400000000000000000000120101426042121400147320ustar00rootroot00000000000000libcuckoo ========= libcuckoo provides a high-performance, compact hash table that allows multiple concurrent reader and writer threads. The Doxygen-generated documentation is available at the [project page](http://efficient.github.io/libcuckoo/). Authors: Manu Goyal, Bin Fan, Xiaozhou Li, David G. Andersen, and Michael Kaminsky For details about this algorithm and citations, please refer to our papers in [NSDI 2013][1] and [EuroSys 2014][2]. Some of the details of the hashing algorithm have been improved since that work (e.g., the previous algorithm in [1] serializes all writer threads, while our current implementation supports multiple concurrent writers), however, and this source code is now the definitive reference. [1]: http://www.cs.cmu.edu/~dga/papers/memc3-nsdi2013.pdf "MemC3: Compact and Concurrent Memcache with Dumber Caching and Smarter Hashing" [2]: http://www.cs.princeton.edu/~mfreed/docs/cuckoo-eurosys14.pdf "Algorithmic Improvements for Fast Concurrent Cuckoo Hashing" Requirements ================ This library has been tested on Mac OSX >= 10.8 and Ubuntu >= 12.04. It compiles with clang++ >= 3.3 and g++ >= 4.8, however we strongly suggest using the latest versions of both compilers, as they have greatly improved support for atomic operations. Building the library requires CMake version >= 3.1.0. To install it on Ubuntu $ sudo apt-get update && sudo apt-get install cmake Building ========== libcuckoo is a header-only library, so in order to get going, just add the `libcuckoo` subdirectory to your include path. These directions cover installing the library to a particular location on your system, and also building any the examples and tests included in the repository. We suggest you build out of source, in a separate `build` directory: $ mkdir build $ cd build There are numerous flags you can pass to `CMake` to set which parts of the repository it builds. `-DCMAKE_INSTALL_PREFIX` : set the location where the libcuckoo header files are installed `-DCMAKE_BUILD_TYPE` : enable different types of build flags for different purposes `-DBUILD_EXAMPLES=1` : tell `CMake` to build the `examples` directory `-DBUILD_TESTS=1` : build all tests in the `tests` directory `-DBUILD_STRESS_TESTS=1` : build all tests in the `tests/stress-tests` directory `-DBUILD_UNIT_TESTS=1` : build all tests in the `tests/unit-tests` directory `-DBUILD_UNIVERSAL_BENCHMARK=1` : build the universal benchmark in the `tests/universal-benchmark` directory. This benchmark allows you to test a variety of operations in arbitrary percentages, with specific keys and values. Consult the `README` in the benchmark directory for more details. So, if, for example, we want to build all examples and all tests into a local installation directory, we'd run the following command from the `build` directory. $ cmake -DCMAKE_INSTALL_PREFIX=../install -DBUILD_EXAMPLES=1 -DBUILD_TESTS=1 .. $ make all $ make install Usage ========== When compiling your own files with `libcuckoo`, always remember to enable C++11 features on your compiler. On `g++`, this would be `-std=c++11`, and on `clang++`, this would be `-std=c++11 -stdlib=libc++`. Once you have installed the header files and the install location has been added to your search path, you can include ``, and any of the other headers you installed, into your source file. There is also a C wrapper around the table that can be leveraged to use `libcuckoo` in a C program. The interface consists of a template header and implementation file that can be used to generate instances of the hashtable for different key-value types. See the `examples` directory for a demonstration of all of these features. Tests ========== The `tests` directory contains a number of tests and benchmarks of the hash table, which also can serve as useful examples of how to use the table's various features. Make sure to enable the tests you want to build with the corresponding `CMake` flags. The test suite can be run with the `make test` command. The test executables can be run individually as well. Issue Report ============ To let us know your questions or issues, we recommend you [report an issue](https://github.com/efficient/libcuckoo/issues) on github. You can also email us at [libcuckoo-dev@googlegroups.com](mailto:libcuckoo-dev@googlegroups.com). Licence =========== Copyright (C) 2013, Carnegie Mellon University and Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --------------------------- The third-party libraries have their own licenses, as detailed in their source files. libcuckoo-0.3.1/examples/000077500000000000000000000000001426042121400152775ustar00rootroot00000000000000libcuckoo-0.3.1/examples/CMakeLists.txt000066400000000000000000000011411426042121400200340ustar00rootroot00000000000000add_executable(nested_table nested_table.cc) target_link_libraries(nested_table libcuckoo) add_executable(hellohash hellohash.cc) target_link_libraries(hellohash libcuckoo) add_executable(count_freq count_freq.cc) target_link_libraries(count_freq libcuckoo) add_library(int_str_table STATIC int_str_table.cc) target_link_libraries(int_str_table libcuckoo) add_library(blob_blob_table STATIC blob_blob_table.cc) target_link_libraries(blob_blob_table libcuckoo) add_executable(c_hash c_hash.c) target_link_libraries(c_hash int_str_table blob_blob_table) set_property(TARGET c_hash PROPERTY C_STANDARD 99) libcuckoo-0.3.1/examples/blob_blob_table.cc000066400000000000000000000016571426042121400207020ustar00rootroot00000000000000// This table was created to illustrate usage of the table with untyped blobs. // See `int_str_table.cc` for more comments regarding the semantics of creating // implementation files for use with C programs. extern "C" { #include "blob_blob_table.h" } // The hashtable uses the generic std::hash template wrapper as the hash // function and std::equal_to as the equality function for key hashing and // comparison. Since neither of these templates are specialized for our custom // key_blob type, we must create our own specializations. #include #include namespace std { template <> struct hash { size_t operator()(const key_blob &kb) const { return *(size_t *)kb.blob; } }; template <> struct equal_to { bool operator()(const key_blob &lhs, const key_blob &rhs) const { return memcmp(lhs.blob, rhs.blob, sizeof(lhs.blob)) == 0; } }; } #include libcuckoo-0.3.1/examples/blob_blob_table.h000066400000000000000000000014221426042121400205320ustar00rootroot00000000000000// This table was created to illustrate usage of the table with untyped blobs. // See `int_str_table.h` for more comments regarding the semantics of creating // header files for use with C programs. #ifndef BLOB_BLOB_TABLE_H #define BLOB_BLOB_TABLE_H // In C, assignment is not defined for raw arrays, but it is defined for all // structs, including those containing arrays. Thus, since the hashtable // requires that assignment is defined for key and mapped types, we wrap the // blobs in a struct. typedef struct { char blob[8]; } key_blob; typedef struct { char blob[255]; } mapped_blob; #define CUCKOO_TABLE_NAME blob_blob_table #define CUCKOO_KEY_TYPE key_blob #define CUCKOO_MAPPED_TYPE mapped_blob #include #endif // BLOB_BLOB_TABLE_H libcuckoo-0.3.1/examples/c_hash.c000066400000000000000000000104661426042121400166770ustar00rootroot00000000000000// This is a C program which uses some basic features of the C wrapper around // libcuckoo. The C wrapper includes nearly all the features of the C++ // version, and most of the C functions have direct equivalents to C++ methods. #include #include #include // Includes the interface for the int->string table #include "int_str_table.h" // Includes the interface for the key_blob->mapped_blob table. Since both // interface files define the same three macros, we must first undefine them // before including the next interface. This undefining will have no effect on // the names generated by the interface templates. #undef CUCKOO_TABLE_NAME #undef CUCKOO_KEY_TYPE #undef CUCKOO_MAPPED_TYPE #include "blob_blob_table.h" int main() { // Allocate a new int->str table, which must be freed at the end of the // program int_str_table *tbl = int_str_table_init(0); // Since the insert function only accepts a pointer to the key and mapped // values, we must make sure they exist as lvalues const char *insert_item = "hello"; for (int i = 0; i < 10; i++) { int_str_table_insert(tbl, &i, &insert_item); } // This loop will search for keys 1-10 (of which 10 will not be found), and // print out what it finds printf("int -> str table:\n"); for (int i = 0; i < 11; i++) { // We need to store the found value in some variable const char *find_item; // The find function will return true if the key was found, and false // otherwise if (int_str_table_find(tbl, &i, &find_item)) { printf("%d %s\n", i, find_item); } else { printf("%d NOT FOUND\n", i); } } // Allocate a new blob->blob table, which must be freed at the end of the // program blob_blob_table *tbl2 = blob_blob_table_init(0); // The table functions as a fully-functional single-threaded hashtable in // locked_table mode, so we lock the table now in order to showcase some of // that functionality. blob_blob_table_locked_table *ltbl = blob_blob_table_lock_table(tbl2); key_blob k; mapped_blob v; for (int i = 0; i < 10; ++i) { // The key we store is a binary representation of the integer memcpy(k.blob, &i, sizeof(i)); // The mapped value we store is the integer converted to ascii characters int num = i; int j = 0; if (num == 0) { v.blob[j++] = '0'; } else { while (num > 0) { v.blob[j++] = (num % 10) + '0'; num /= 10; } } v.blob[j] = '\0'; // To insert into a locked_table, we pass the table, address of key and // value, and, optionally, an iterator, which will be set to the location // of the inserted key-value pair. We pass NULL here, indicating we don't // have an iterator to set to the insert location. blob_blob_table_locked_table_insert(ltbl, &k, &v, NULL); } // We must allocate iterator objects for the beginning and end of the table // to iterate through it. Since we are not modifying the elements of the // table, we use const_iterators, though regular iterators would also work // here. blob_blob_table_const_iterator *it = blob_blob_table_locked_table_cbegin(ltbl); blob_blob_table_const_iterator *end = blob_blob_table_locked_table_cend(ltbl); // This loop walks through the table and prints out each key-value pair. // Since `it` is already set to the beginning, there is no need for an // initialization statement. The condition tests whether `it` equals `end`, // which, when it does, will mean we've moved past the end of the table. The // step statement advances `it` forward to the next key-value pair. printf("blob -> blob table:\n"); for (; !blob_blob_table_const_iterator_equal(it, end); blob_blob_table_const_iterator_increment(it)) { // Here we dereference the key and mapped values from the iterator and // store them into our previously-declared key_blob and mapped_blob // variables. k = *blob_blob_table_const_iterator_key(it); v = *blob_blob_table_const_iterator_mapped(it); printf("%d -> %s\n", *(int *)k.blob, v.blob); } // We must free all the allocated objects, which are the iterators, locked // table, and two table objects. blob_blob_table_const_iterator_free(end); blob_blob_table_const_iterator_free(it); blob_blob_table_locked_table_free(ltbl); blob_blob_table_free(tbl2); int_str_table_free(tbl); } libcuckoo-0.3.1/examples/count_freq.cc000066400000000000000000000037771426042121400177710ustar00rootroot00000000000000/* A simple example of using the hash table that counts the * frequencies of a sequence of random numbers. */ #include #include #include #include #include #include #include #include #include #include typedef uint32_t KeyType; typedef libcuckoo::cuckoohash_map Table; const size_t thread_num = 8; const size_t total_inserts = 10000000; void do_inserts(Table &freq_map) { std::mt19937_64 gen( std::chrono::system_clock::now().time_since_epoch().count()); std::uniform_int_distribution dist( std::numeric_limits::min(), std::numeric_limits::max()); auto updatefn = [](size_t &num) { ++num; }; for (size_t i = 0; i < total_inserts / thread_num; i++) { KeyType num = dist(gen); // If the number is already in the table, it will increment // its count by one. Otherwise it will insert a new entry in // the table with count one. freq_map.upsert(num, updatefn, 1); } } int main() { Table freq_map; freq_map.reserve(total_inserts); // Run the inserts in thread_num threads std::vector threads; for (size_t i = 0; i < thread_num; i++) { threads.emplace_back(do_inserts, std::ref(freq_map)); } for (size_t i = 0; i < thread_num; i++) { threads[i].join(); } // We iterate through the table and print out the element with the // maximum number of occurrences. KeyType maxkey; size_t maxval = 0; { auto lt = freq_map.lock_table(); for (const auto &it : lt) { if (it.second > maxval) { maxkey = it.first; maxval = it.second; } } } std::cout << maxkey << " occurred " << maxval << " times." << std::endl; // Print some information about the table std::cout << "Table size: " << freq_map.size() << std::endl; std::cout << "Bucket count: " << freq_map.bucket_count() << std::endl; std::cout << "Load factor: " << freq_map.load_factor() << std::endl; } libcuckoo-0.3.1/examples/hellohash.cc000066400000000000000000000006541426042121400175620ustar00rootroot00000000000000#include #include #include int main() { libcuckoo::cuckoohash_map Table; for (int i = 0; i < 100; i++) { Table.insert(i, "hello"); } for (int i = 0; i < 101; i++) { std::string out; if (Table.find(i, out)) { std::cout << i << " " << out << std::endl; } else { std::cout << i << " NOT FOUND" << std::endl; } } } libcuckoo-0.3.1/examples/int_str_table.cc000066400000000000000000000007231426042121400204410ustar00rootroot00000000000000// Include the header file with "C" linkage extern "C" { #include "int_str_table.h" } // Include the implementation template, which uses the constants defined in // `int_str_table.h`. The implementation file defines all functions under "C" // linkage, and should be linked with the corresponding header to generate a // linkable library. See `CMakeLists.txt` for an example of how this is done to // create `c_hash`. #include libcuckoo-0.3.1/examples/int_str_table.h000066400000000000000000000012631426042121400203030ustar00rootroot00000000000000// Include guard #ifndef INT_STR_TABLE_H #define INT_STR_TABLE_H // Below we define the constants the table template uses to fill in the // interface. // // All table functions will be prefixed with `int_str_table` #define CUCKOO_TABLE_NAME int_str_table // The type of the key is `int` #define CUCKOO_KEY_TYPE int // The type of the mapped value is `const char *` #define CUCKOO_MAPPED_TYPE const char * // Including the header after filling in the constants will populate the // interface. See the template file itself for specific function names; most of // them correspond to methods in the C++ implementation. #include #endif // INT_STR_TABLE_H libcuckoo-0.3.1/examples/nested_table.cc000066400000000000000000000025271426042121400202450ustar00rootroot00000000000000/* We demonstrate how to nest hash tables within one another, to store * unstructured data, kind of like JSON. There's still the limitation that it's * statically typed. */ #include #include #include #include #include typedef libcuckoo::cuckoohash_map InnerTable; typedef libcuckoo::cuckoohash_map> OuterTable; int main() { OuterTable tbl; tbl.insert("bob", std::unique_ptr(new InnerTable)); tbl.update_fn("bob", [](std::unique_ptr &innerTbl) { innerTbl->insert("nickname", "jimmy"); innerTbl->insert("pet", "dog"); innerTbl->insert("food", "bagels"); }); tbl.insert("jack", std::unique_ptr(new InnerTable)); tbl.update_fn("jack", [](std::unique_ptr &innerTbl) { innerTbl->insert("friend", "bob"); innerTbl->insert("activity", "sleeping"); innerTbl->insert("language", "javascript"); }); { auto lt = tbl.lock_table(); for (const auto &item : lt) { std::cout << "Properties for " << item.first << std::endl; auto innerLt = item.second->lock_table(); for (auto innerItem : innerLt) { std::cout << "\t" << innerItem.first << " = " << innerItem.second << std::endl; } } } } libcuckoo-0.3.1/hooks/000077500000000000000000000000001426042121400146045ustar00rootroot00000000000000libcuckoo-0.3.1/hooks/pre-commit000077500000000000000000000010111426042121400165770ustar00rootroot00000000000000#!/bin/sh # clang-format pre-commit hook # # To use, store as .git/hooks/pre-commit inside your repository and make sure # it has execute permissions. # # This script does not handle file names that contain spaces. cfiles=$(git diff --name-only HEAD --diff-filter=d | grep '\.\(cc\|hh\|c\|h\)$') numerrors=0 for f in $cfiles; do diffoutput=$(clang-format $f | diff $f -) if [ -n "$diffoutput" ]; then [ $numerrors -eq 0 ] && echo >&2 "Unformatted files:"; echo >&2 "$f"; ((numerrors++)) fi done exit $numerrors libcuckoo-0.3.1/libcuckoo-c/000077500000000000000000000000001426042121400156535ustar00rootroot00000000000000libcuckoo-0.3.1/libcuckoo-c/CMakeLists.txt000066400000000000000000000002061426042121400204110ustar00rootroot00000000000000install( FILES cuckoo_table_template.h cuckoo_table_template.cc DESTINATION ${CMAKE_INSTALL_PREFIX}/include/libcuckoo-c ) libcuckoo-0.3.1/libcuckoo-c/cuckoo_table_template.cc000066400000000000000000000324371426042121400225200ustar00rootroot00000000000000// To create an implementation of the cuckoo table for a certain key-value type, // include your header file in an extern "C" file as follows: // // extern "C" { // #include "interface_header.h" // } // // Then include this template file #include #include #include #include #include // Helper macros, we take care of undefining these #define PASTE2(a, b) a##b #define PASTE(a, b) PASTE2(a, b) #define CUCKOO(a) PASTE(CUCKOO_TABLE_NAME, a) #ifdef __cplusplus extern "C" { #endif typedef libcuckoo::cuckoohash_map tbl_t; struct CUCKOO_TABLE_NAME { tbl_t t; CUCKOO_TABLE_NAME(size_t n) : t(n) {} }; typedef struct CUCKOO_TABLE_NAME CUCKOO_TABLE_NAME; #define CUCKOO_KEY_ALIAS CUCKOO(_key_type) typedef CUCKOO_KEY_TYPE CUCKOO_KEY_ALIAS; #define CUCKOO_MAPPED_ALIAS CUCKOO(_mapped_type) typedef CUCKOO_MAPPED_TYPE CUCKOO_MAPPED_ALIAS; CUCKOO_TABLE_NAME *CUCKOO(_init)(size_t n) { CUCKOO_TABLE_NAME *tbl; try { tbl = new CUCKOO_TABLE_NAME(n); } catch (std::bad_alloc &) { errno = ENOMEM; return NULL; } tbl->t.minimum_load_factor(0); tbl->t.maximum_hashpower(libcuckoo::NO_MAXIMUM_HASHPOWER); return tbl; } CUCKOO_TABLE_NAME *CUCKOO(_read)(FILE *fp) { size_t tbl_size; if (!fread(&tbl_size, sizeof(size_t), 1, fp)) { return NULL; } CUCKOO_TABLE_NAME *tbl = NULL; try { tbl = new CUCKOO_TABLE_NAME(tbl_size); } catch (std::bad_alloc &) { errno = ENOMEM; return NULL; } CUCKOO_KEY_ALIAS key; CUCKOO_MAPPED_ALIAS mapped; for (size_t i = 0; i < tbl_size; ++i) { if (!fread(&key, sizeof(CUCKOO_KEY_ALIAS), 1, fp)) { delete tbl; return NULL; } if (!fread(&mapped, sizeof(CUCKOO_MAPPED_ALIAS), 1, fp)) { delete tbl; return NULL; } try { tbl->t.insert(key, mapped); } catch (std::bad_alloc &) { delete tbl; errno = ENOMEM; return NULL; } } return tbl; } void CUCKOO(_free)(CUCKOO_TABLE_NAME *tbl) { delete tbl; } // hashpower size_t CUCKOO(_hashpower)(const CUCKOO_TABLE_NAME *tbl) { return tbl->t.hashpower(); } // bucket_count size_t CUCKOO(_bucket_count)(const CUCKOO_TABLE_NAME *tbl) { return tbl->t.bucket_count(); } // empty bool CUCKOO(_empty)(const CUCKOO_TABLE_NAME *tbl) { return tbl->t.empty(); } // size size_t CUCKOO(_size)(const CUCKOO_TABLE_NAME *tbl) { return tbl->t.size(); } // capacity size_t CUCKOO(_capacity)(const CUCKOO_TABLE_NAME *tbl) { return tbl->t.capacity(); } // load_factor double CUCKOO(_load_factor)(const CUCKOO_TABLE_NAME *tbl) { return tbl->t.load_factor(); } // find_fn bool CUCKOO(_find_fn)(const CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, void (*fn)(const CUCKOO_MAPPED_ALIAS *)) { return tbl->t.find_fn(*key, [&fn](const CUCKOO_MAPPED_ALIAS &v) { fn(&v); }); } // update_fn bool CUCKOO(_update_fn)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, void (*fn)(CUCKOO_MAPPED_ALIAS *)) { return tbl->t.update_fn(*key, [&fn](CUCKOO_MAPPED_ALIAS &v) { fn(&v); }); } // upsert bool CUCKOO(_upsert)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, void (*fn)(CUCKOO_MAPPED_ALIAS *), const CUCKOO_MAPPED_ALIAS *value) { try { return tbl->t.upsert(*key, [&fn](CUCKOO_MAPPED_ALIAS &v) { fn(&v); }, *value); } catch (std::bad_alloc &) { errno = ENOMEM; return false; } } // erase_fn bool CUCKOO(_erase_fn)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, bool (*fn)(CUCKOO_MAPPED_ALIAS *)) { return tbl->t.erase_fn( *key, [&fn](CUCKOO_MAPPED_ALIAS &v) -> bool { return fn(&v); }); } // find bool CUCKOO(_find)(const CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, CUCKOO_MAPPED_ALIAS *val) { return tbl->t.find(*key, *val); } // contains bool CUCKOO(_contains)(const CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key) { return tbl->t.contains(*key); } // update bool CUCKOO(_update)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, const CUCKOO_MAPPED_ALIAS *val) { return tbl->t.update(*key, *val); } // insert bool CUCKOO(_insert)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, const CUCKOO_MAPPED_ALIAS *val) { try { return tbl->t.insert(*key, *val); } catch (std::bad_alloc &) { errno = ENOMEM; return false; } } // insert_or_assign bool CUCKOO(_insert_or_assign)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, const CUCKOO_MAPPED_ALIAS *val) { try { return tbl->t.insert_or_assign(*key, *val); } catch (std::bad_alloc &) { errno = ENOMEM; return false; } } // erase bool CUCKOO(_erase)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key) { return tbl->t.erase(*key); } // rehash bool CUCKOO(_rehash)(CUCKOO_TABLE_NAME *tbl, size_t n) { try { return tbl->t.rehash(n); } catch (std::bad_alloc &) { errno = ENOMEM; return false; } } // reserve bool CUCKOO(_reserve)(CUCKOO_TABLE_NAME *tbl, size_t n) { try { return tbl->t.reserve(n); } catch (std::bad_alloc &) { errno = ENOMEM; return false; } } // clear void CUCKOO(_clear)(CUCKOO_TABLE_NAME *tbl) { tbl->t.clear(); } #define CUCKOO_LOCKED_TABLE CUCKOO(_locked_table) #define CUCKOO_LT(a) PASTE(CUCKOO_LOCKED_TABLE, a) struct CUCKOO_LOCKED_TABLE { tbl_t::locked_table lt; CUCKOO_LOCKED_TABLE(CUCKOO_TABLE_NAME *tbl) : lt(std::move(tbl->t.lock_table())) {} }; typedef struct CUCKOO_LOCKED_TABLE CUCKOO_LOCKED_TABLE; // lock_table -- this is the only way to construct a locked table CUCKOO_LOCKED_TABLE *CUCKOO(_lock_table)(CUCKOO_TABLE_NAME *tbl) { try { return new CUCKOO_LOCKED_TABLE(tbl); } catch (std::bad_alloc &) { errno = ENOMEM; return NULL; } } // free locked_table void CUCKOO_LT(_free)(CUCKOO_LOCKED_TABLE *ltbl) { delete ltbl; } // locked_table::unlock void CUCKOO_LT(_unlock)(CUCKOO_LOCKED_TABLE *ltbl) { ltbl->lt.unlock(); } // locked_table::is_active bool CUCKOO_LT(_is_active)(const CUCKOO_LOCKED_TABLE *ltbl) { return ltbl->lt.is_active(); } // locked_table::hashpower size_t CUCKOO_LT(_hashpower)(const CUCKOO_LOCKED_TABLE *ltbl) { return ltbl->lt.hashpower(); } // locked_table::bucket_count size_t CUCKOO_LT(_bucket_count)(const CUCKOO_LOCKED_TABLE *ltbl) { return ltbl->lt.bucket_count(); } // locked_table::empty bool CUCKOO_LT(_empty)(const CUCKOO_LOCKED_TABLE *ltbl) { return ltbl->lt.empty(); } // locked_table::size size_t CUCKOO_LT(_size)(const CUCKOO_LOCKED_TABLE *ltbl) { return ltbl->lt.size(); } // locked_table::capacity size_t CUCKOO_LT(_capacity)(const CUCKOO_LOCKED_TABLE *ltbl) { return ltbl->lt.capacity(); } // locked_table::load_factor double CUCKOO_LT(_load_factor)(const CUCKOO_LOCKED_TABLE *ltbl) { return ltbl->lt.load_factor(); } #define CUCKOO_ITERATOR CUCKOO(_iterator) #define CUCKOO_IT(a) PASTE(CUCKOO_ITERATOR, a) struct CUCKOO_ITERATOR { tbl_t::locked_table::iterator it; CUCKOO_ITERATOR(tbl_t::locked_table::iterator i) : it(i) {} }; typedef struct CUCKOO_ITERATOR CUCKOO_ITERATOR; #define CUCKOO_CONST_ITERATOR CUCKOO(_const_iterator) #define CUCKOO_CONST_IT(a) PASTE(CUCKOO_CONST_ITERATOR, a) struct CUCKOO_CONST_ITERATOR { tbl_t::locked_table::const_iterator it; CUCKOO_CONST_ITERATOR(tbl_t::locked_table::const_iterator i) : it(i) {} }; typedef struct CUCKOO_CONST_ITERATOR CUCKOO_CONST_ITERATOR; // the following four functions are the only way to construct iterators // locked_table::begin CUCKOO_ITERATOR *CUCKOO_LT(_begin)(CUCKOO_LOCKED_TABLE *ltbl) { try { return new CUCKOO_ITERATOR(ltbl->lt.begin()); } catch (std::bad_alloc &) { errno = ENOMEM; return NULL; } } // locked_table::cbegin CUCKOO_CONST_ITERATOR *CUCKOO_LT(_cbegin)(const CUCKOO_LOCKED_TABLE *ltbl) { try { return new CUCKOO_CONST_ITERATOR(ltbl->lt.cbegin()); } catch (std::bad_alloc &) { errno = ENOMEM; return NULL; } } // locked_table::end CUCKOO_ITERATOR *CUCKOO_LT(_end)(CUCKOO_LOCKED_TABLE *ltbl) { try { return new CUCKOO_ITERATOR(ltbl->lt.end()); } catch (std::bad_alloc &) { errno = ENOMEM; return NULL; } } // locked_table::cend CUCKOO_CONST_ITERATOR *CUCKOO_LT(_cend)(const CUCKOO_LOCKED_TABLE *ltbl) { try { return new CUCKOO_CONST_ITERATOR(ltbl->lt.cend()); } catch (std::bad_alloc &) { errno = ENOMEM; return NULL; } } // free iterator void CUCKOO_IT(_free)(CUCKOO_ITERATOR *it) { delete it; } void CUCKOO_CONST_IT(_free)(CUCKOO_CONST_ITERATOR *it) { delete it; } // locked_table::clear void CUCKOO_LT(_clear)(CUCKOO_LOCKED_TABLE *ltbl) { ltbl->lt.clear(); } // locked_table::insert -- passing NULL for the iterator will not save the // position of the inserted element bool CUCKOO_LT(_insert)(CUCKOO_LOCKED_TABLE *ltbl, const CUCKOO_KEY_ALIAS *key, const CUCKOO_MAPPED_ALIAS *val, CUCKOO_ITERATOR *it) { std::pair ret; try { ret = ltbl->lt.insert(*key, *val); } catch (std::bad_alloc &) { errno = ENOMEM; return false; } if (it != NULL) { it->it = ret.first; } return ret.second; } // locked_table::erase -- passing NULL for the iterator will not save the // position of the following element void CUCKOO_LT(_erase_it)(CUCKOO_LOCKED_TABLE *ltbl, CUCKOO_ITERATOR *it, CUCKOO_ITERATOR *nextit) { auto ret = ltbl->lt.erase(it->it); if (nextit != NULL) { nextit->it = ret; } } // locked_table::erase const iterator void CUCKOO_LT(_erase_const_it)(CUCKOO_LOCKED_TABLE *ltbl, CUCKOO_CONST_ITERATOR *it, CUCKOO_ITERATOR *nextit) { auto ret = ltbl->lt.erase(it->it); if (nextit != NULL) { nextit->it = ret; } } // locked_table::erase size_t CUCKOO_LT(_erase)(CUCKOO_LOCKED_TABLE *ltbl, const CUCKOO_KEY_ALIAS *key) { return ltbl->lt.erase(*key); } // locked_table::find -- the iterator passed in cannot be NULL void CUCKOO_LT(_find)(CUCKOO_LOCKED_TABLE *ltbl, const CUCKOO_KEY_ALIAS *key, CUCKOO_ITERATOR *it) { it->it = ltbl->lt.find(*key); } void CUCKOO_LT(_find_const)(const CUCKOO_LOCKED_TABLE *ltbl, const CUCKOO_KEY_ALIAS *key, CUCKOO_CONST_ITERATOR *it) { it->it = ltbl->lt.find(*key); } // locked_table::rehash void CUCKOO_LT(_rehash)(CUCKOO_LOCKED_TABLE *ltbl, size_t n) { try { ltbl->lt.rehash(n); } catch (std::bad_alloc &) { errno = ENOMEM; } } // locked_table::reserve void CUCKOO_LT(_reserve)(CUCKOO_LOCKED_TABLE *ltbl, size_t n) { try { ltbl->lt.reserve(n); } catch (std::bad_alloc &) { errno = ENOMEM; } } // locked_table::write bool CUCKOO_LT(_write)(const CUCKOO_LOCKED_TABLE *ltbl, FILE *fp) { size_t tbl_size = ltbl->lt.size(); if (!fwrite(&tbl_size, sizeof(size_t), 1, fp)) { return false; } for (const auto &pair : ltbl->lt) { if (!fwrite(std::addressof(pair.first), sizeof(CUCKOO_KEY_ALIAS), 1, fp)) { return false; } if (!fwrite(std::addressof(pair.second), sizeof(CUCKOO_MAPPED_ALIAS), 1, fp)) { return false; } } return true; } // iterator::copy assignment void CUCKOO_IT(_set)(CUCKOO_ITERATOR *dst, CUCKOO_ITERATOR *src) { dst->it = src->it; } void CUCKOO_CONST_IT(_set)(CUCKOO_CONST_ITERATOR *dst, CUCKOO_CONST_ITERATOR *src) { dst->it = src->it; } // iterator::set to beginning void CUCKOO_LT(_set_begin)(CUCKOO_LOCKED_TABLE *ltbl, CUCKOO_ITERATOR *it) { it->it = ltbl->lt.begin(); } void CUCKOO_LT(_set_cbegin)(const CUCKOO_LOCKED_TABLE *ltbl, CUCKOO_CONST_ITERATOR *it) { it->it = ltbl->lt.cbegin(); } // iterator::set to end void CUCKOO_LT(_set_end)(CUCKOO_LOCKED_TABLE *ltbl, CUCKOO_ITERATOR *it) { it->it = ltbl->lt.end(); } void CUCKOO_LT(_set_cend)(const CUCKOO_LOCKED_TABLE *ltbl, CUCKOO_CONST_ITERATOR *it) { it->it = ltbl->lt.cend(); } // iterator::equality bool CUCKOO_IT(_equal)(CUCKOO_ITERATOR *it1, CUCKOO_ITERATOR *it2) { return it1->it == it2->it; } bool CUCKOO_CONST_IT(_equal)(CUCKOO_CONST_ITERATOR *it1, CUCKOO_CONST_ITERATOR *it2) { return it1->it == it2->it; } // iterator::dereference key const CUCKOO_KEY_ALIAS *CUCKOO_IT(_key)(CUCKOO_ITERATOR *it) { return &it->it->first; } const CUCKOO_KEY_ALIAS *CUCKOO_CONST_IT(_key)(CUCKOO_CONST_ITERATOR *it) { return &it->it->first; } // iterator::dereference mapped CUCKOO_MAPPED_ALIAS *CUCKOO_IT(_mapped)(CUCKOO_ITERATOR *it) { return &it->it->second; } const CUCKOO_MAPPED_ALIAS *CUCKOO_CONST_IT(_mapped)(CUCKOO_CONST_ITERATOR *it) { return &it->it->second; } // iterator::increment void CUCKOO_IT(_increment)(CUCKOO_ITERATOR *it) { ++(it->it); } void CUCKOO_CONST_IT(_increment)(CUCKOO_CONST_ITERATOR *it) { ++(it->it); } // iterator::decrement void CUCKOO_IT(_decrement)(CUCKOO_ITERATOR *it) { --(it->it); } void CUCKOO_CONST_IT(_decrement)(CUCKOO_CONST_ITERATOR *it) { --(it->it); } #ifdef __cplusplus } #endif // #undef the helper macros we defined #undef PASTE #undef PASTE2 #undef CUCKOO #undef CUCKOO_KEY_ALIAS #undef CUCKOO_MAPPED_ALIAS #undef CUCKOO_LOCKED_TABLE #undef CUCKOO_LT #undef CUCKOO_ITERATOR #undef CUCKOO_IT #undef CUCKOO_CONST_ITERATOR #undef CUCKOO_CONST_IT libcuckoo-0.3.1/libcuckoo-c/cuckoo_table_template.h000066400000000000000000000235031426042121400223540ustar00rootroot00000000000000// To create an interface instance of the cuckoo table for a certain key-value // type, you must define the following constants in your header file: // // Name of table // #define CUCKOO_TABLE_NAME ___ // // Type of key // #define CUCKOO_KEY_TYPE ___ // // Type of mapped value // #define CUCKOO_MAPPED_TYPE ___ // // Then, include this template file, which will fill in the interface // definition. If you are including multiple different table interfaces in the // same compilation unit, make sure to undefine the three symbols above using // the `#undef` macro. // // EXCEPTION SAFETY NOTE: // Assuming no user defined data types, hash functions, or equality functions // throw exceptions, the only exception that could be thrown by the table is // std::bad_alloc whenever we allocate memory. This could occur when // initializing objects, like the hashtable, a locked_table object, or an // iterator. Or it could occur when we resize the table. In this latter case, // all functions that could trigger a resize (upsert, insert, insert_or_assign, // rehash, reserve, locked_table::insert, locked_table::rehash, // locked_table::reserve) could throw. For all functions that allocate memory, // we catch std::bad_alloc and set errno to ENOMEM. // Helper macros, we take care of undefining these #define PASTE2(a, b) a##b #define PASTE(a, b) PASTE2(a, b) #define CUCKOO(a) PASTE(CUCKOO_TABLE_NAME, a) #include #include #include // Abstract type of the cuckoo table struct CUCKOO_TABLE_NAME; typedef struct CUCKOO_TABLE_NAME CUCKOO_TABLE_NAME; // Typedefs for the key and value types #define CUCKOO_KEY_ALIAS CUCKOO(_key_type) typedef CUCKOO_KEY_TYPE CUCKOO_KEY_ALIAS; #define CUCKOO_MAPPED_ALIAS CUCKOO(_mapped_type) typedef CUCKOO_MAPPED_TYPE CUCKOO_MAPPED_ALIAS; // Constructs a new table allocated to store at least `n` elements. Uses a // default hash function, equality function, and allocator. There is no minimum // load factor or maximum hashpower. CUCKOO_TABLE_NAME *CUCKOO(_init)(size_t n); // Reads in a table serialized in the file `fp` and constructs a new table. // Uses a default hash function, equality function, and allocator. There is no // minimum load factor or maximum hashpower. This will only work if the table // types are POD, which should always be the case in a C program. CUCKOO_TABLE_NAME *CUCKOO(_read)(FILE *fp); // Destroys the given table void CUCKOO(_free)(CUCKOO_TABLE_NAME *tbl); // hashpower size_t CUCKOO(_hashpower)(const CUCKOO_TABLE_NAME *tbl); // bucket_count size_t CUCKOO(_bucket_count)(const CUCKOO_TABLE_NAME *tbl); // empty bool CUCKOO(_empty)(const CUCKOO_TABLE_NAME *tbl); // size size_t CUCKOO(_size)(const CUCKOO_TABLE_NAME *tbl); // capacity size_t CUCKOO(_capacity)(const CUCKOO_TABLE_NAME *tbl); // load_factor double CUCKOO(_load_factor)(const CUCKOO_TABLE_NAME *tbl); // find_fn bool CUCKOO(_find_fn)(const CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, void (*fn)(const CUCKOO_MAPPED_ALIAS *)); // update_fn bool CUCKOO(_update_fn)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, void (*fn)(CUCKOO_MAPPED_ALIAS *)); // upsert bool CUCKOO(_upsert)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, void (*fn)(CUCKOO_MAPPED_ALIAS *), const CUCKOO_MAPPED_ALIAS *val); // erase_fn bool CUCKOO(_erase_fn)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, bool (*fn)(CUCKOO_MAPPED_ALIAS *)); // find bool CUCKOO(_find)(const CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, CUCKOO_MAPPED_ALIAS *val); // contains bool CUCKOO(_contains)(const CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key); // update bool CUCKOO(_update)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, const CUCKOO_MAPPED_ALIAS *val); // insert bool CUCKOO(_insert)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, const CUCKOO_MAPPED_ALIAS *val); // insert_or_assign bool CUCKOO(_insert_or_assign)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key, const CUCKOO_MAPPED_ALIAS *val); // erase bool CUCKOO(_erase)(CUCKOO_TABLE_NAME *tbl, const CUCKOO_KEY_ALIAS *key); // rehash bool CUCKOO(_rehash)(CUCKOO_TABLE_NAME *tbl, size_t n); // reserve bool CUCKOO(_reserve)(CUCKOO_TABLE_NAME *tbl, size_t n); // clear void CUCKOO(_clear)(CUCKOO_TABLE_NAME *tbl); // Abstract type of the locked table #define CUCKOO_LOCKED_TABLE CUCKOO(_locked_table) #define CUCKOO_LT(a) PASTE(CUCKOO_LOCKED_TABLE, a) struct CUCKOO_LOCKED_TABLE; typedef struct CUCKOO_LOCKED_TABLE CUCKOO_LOCKED_TABLE; // lock_table -- this is the only way to construct a locked table CUCKOO_LOCKED_TABLE *CUCKOO(_lock_table)(CUCKOO_TABLE_NAME *tbl); // free locked_table void CUCKOO_LT(_free)(CUCKOO_LOCKED_TABLE *ltbl); // locked_table::unlock void CUCKOO_LT(_unlock)(CUCKOO_LOCKED_TABLE *ltbl); // locked_table::is_active bool CUCKOO_LT(_is_active)(const CUCKOO_LOCKED_TABLE *ltbl); // locked_table::hashpower size_t CUCKOO_LT(_hashpower)(const CUCKOO_LOCKED_TABLE *ltbl); // locked_table::bucket_count size_t CUCKOO_LT(_bucket_count)(const CUCKOO_LOCKED_TABLE *ltbl); // locked_table::empty bool CUCKOO_LT(_empty)(const CUCKOO_LOCKED_TABLE *ltbl); // locked_table::size size_t CUCKOO_LT(_size)(const CUCKOO_LOCKED_TABLE *ltbl); // locked_table::capacity size_t CUCKOO_LT(_capacity)(const CUCKOO_LOCKED_TABLE *ltbl); // locked_table::load_factor double CUCKOO_LT(_load_factor)(const CUCKOO_LOCKED_TABLE *ltbl); // Abstract type of the iterators #define CUCKOO_ITERATOR CUCKOO(_iterator) #define CUCKOO_IT(a) PASTE(CUCKOO_ITERATOR, a) struct CUCKOO_ITERATOR; typedef struct CUCKOO_ITERATOR CUCKOO_ITERATOR; #define CUCKOO_CONST_ITERATOR CUCKOO(_const_iterator) #define CUCKOO_CONST_IT(a) PASTE(CUCKOO_CONST_ITERATOR, a) struct CUCKOO_CONST_ITERATOR; typedef struct CUCKOO_CONST_ITERATOR CUCKOO_CONST_ITERATOR; // begin and end are the only way to construct iterators // locked_table::begin CUCKOO_ITERATOR *CUCKOO_LT(_begin)(CUCKOO_LOCKED_TABLE *ltbl); // locked_table::cbegin CUCKOO_CONST_ITERATOR *CUCKOO_LT(_cbegin)(const CUCKOO_LOCKED_TABLE *ltbl); // locked_table::end CUCKOO_ITERATOR *CUCKOO_LT(_end)(CUCKOO_LOCKED_TABLE *ltbl); // locked_table::cend CUCKOO_CONST_ITERATOR *CUCKOO_LT(_cend)(const CUCKOO_LOCKED_TABLE *ltbl); // free iterator void CUCKOO_IT(_free)(CUCKOO_ITERATOR *it); // free const iterator void CUCKOO_CONST_IT(_free)(CUCKOO_CONST_ITERATOR *it); // locked_table::clear void CUCKOO_LT(_clear)(CUCKOO_LOCKED_TABLE *ltbl); // locked_table::insert -- passing NULL for the iterator will not save the // position of the inserted element bool CUCKOO_LT(_insert)(CUCKOO_LOCKED_TABLE *ltbl, const CUCKOO_KEY_ALIAS *key, const CUCKOO_MAPPED_ALIAS *val, CUCKOO_ITERATOR *it); // locked_table::erase -- passing NULL for the iterator will not save the // position of the following element void CUCKOO_LT(_erase_it)(CUCKOO_LOCKED_TABLE *ltbl, CUCKOO_ITERATOR *it, CUCKOO_ITERATOR *nextit); // locked_table::erase const iterator void CUCKOO_LT(_erase_const_it)(CUCKOO_LOCKED_TABLE *ltbl, CUCKOO_CONST_ITERATOR *it, CUCKOO_ITERATOR *nextit); // locked_table::erase size_t CUCKOO_LT(_erase)(CUCKOO_LOCKED_TABLE *ltbl, const CUCKOO_KEY_ALIAS *key); // locked_table::find -- the iterator passed in cannot be NULL void CUCKOO_LT(_find)(CUCKOO_LOCKED_TABLE *ltbl, const CUCKOO_KEY_ALIAS *key, CUCKOO_ITERATOR *it); void CUCKOO_LT(_find_const)(const CUCKOO_LOCKED_TABLE *ltbl, const CUCKOO_KEY_ALIAS *key, CUCKOO_CONST_ITERATOR *it); // locked_table::rehash void CUCKOO_LT(_rehash)(CUCKOO_LOCKED_TABLE *ltbl, size_t n); // locked_table::reserve void CUCKOO_LT(_reserve)(CUCKOO_LOCKED_TABLE *ltbl, size_t n); // locked_table::write will serialize the table to the file `fp`. This will // only work if the table types are POD, which should always be the case in a C // program. bool CUCKOO_LT(_write)(const CUCKOO_LOCKED_TABLE *ltbl, FILE *fp); // iterator::copy assignment void CUCKOO_IT(_set)(CUCKOO_ITERATOR *dst, CUCKOO_ITERATOR *src); void CUCKOO_CONST_IT(_set)(CUCKOO_CONST_ITERATOR *dst, CUCKOO_CONST_ITERATOR *src); // iterator::set to beginning void CUCKOO_LT(_set_begin)(CUCKOO_LOCKED_TABLE *ltbl, CUCKOO_ITERATOR *it); void CUCKOO_LT(_set_cbegin)(const CUCKOO_LOCKED_TABLE *ltbl, CUCKOO_CONST_ITERATOR *it); // iterator::set to end void CUCKOO_LT(_set_end)(CUCKOO_LOCKED_TABLE *ltbl, CUCKOO_ITERATOR *it); void CUCKOO_LT(_set_cend)(const CUCKOO_LOCKED_TABLE *ltbl, CUCKOO_CONST_ITERATOR *it); // iterator::equality bool CUCKOO_IT(_equal)(CUCKOO_ITERATOR *it1, CUCKOO_ITERATOR *it2); bool CUCKOO_CONST_IT(_equal)(CUCKOO_CONST_ITERATOR *it1, CUCKOO_CONST_ITERATOR *it2); // iterator::dereference key const CUCKOO_KEY_ALIAS *CUCKOO_IT(_key)(CUCKOO_ITERATOR *it); const CUCKOO_KEY_ALIAS *CUCKOO_CONST_IT(_key)(CUCKOO_CONST_ITERATOR *it); // iterator::dereference mapped CUCKOO_MAPPED_ALIAS *CUCKOO_IT(_mapped)(CUCKOO_ITERATOR *it); const CUCKOO_MAPPED_ALIAS *CUCKOO_CONST_IT(_mapped)(CUCKOO_CONST_ITERATOR *it); // iterator::increment void CUCKOO_IT(_increment)(CUCKOO_ITERATOR *it); void CUCKOO_CONST_IT(_increment)(CUCKOO_CONST_ITERATOR *it); // iterator::decrement void CUCKOO_IT(_decrement)(CUCKOO_ITERATOR *it); void CUCKOO_CONST_IT(_decrement)(CUCKOO_CONST_ITERATOR *it); // #undef the helper macros we defined #undef PASTE #undef PASTE2 #undef CUCKOO #undef CUCKOO_KEY_ALIAS #undef CUCKOO_MAPPED_ALIAS #undef CUCKOO_LOCKED_TABLE #undef CUCKOO_LT #undef CUCKOO_ITERATOR #undef CUCKOO_IT #undef CUCKOO_CONST_ITERATOR #undef CUCKOO_CONST_IT libcuckoo-0.3.1/libcuckoo-gdb-printers/000077500000000000000000000000001426042121400200315ustar00rootroot00000000000000libcuckoo-0.3.1/libcuckoo-gdb-printers/README.md000066400000000000000000000006051426042121400213110ustar00rootroot00000000000000# GDB Printers This directory contains a python module that allows pretty-printing a cuckoohash map. ## Usage In order to use the libcuckoo pretty printers, add the following to your `~/.gdbinit`: ``` python import sys sys.path.insert(0, '/path/to/libcuckoo/libcuckoo-gdb-printers') from libcuckoo.printers import register_libcuckoo_printers register_libcuckoo_printers (None) end ``` libcuckoo-0.3.1/libcuckoo-gdb-printers/libcuckoo/000077500000000000000000000000001426042121400220035ustar00rootroot00000000000000libcuckoo-0.3.1/libcuckoo-gdb-printers/libcuckoo/__init__.py000066400000000000000000000000001426042121400241020ustar00rootroot00000000000000libcuckoo-0.3.1/libcuckoo-gdb-printers/libcuckoo/printers.py000066400000000000000000000024671426042121400242340ustar00rootroot00000000000000import gdb import gdb.printing class CuckoohashMapPrinter: """Print a cuckoohash_map object""" def __init__(self, val): self.val = val self.slot_per_bucket = int(self.val.type.template_argument(5)) def _iterator(self): buckets_obj = self.val['buckets_'] hashpower = buckets_obj['hashpower_']['_M_i'] buckets_ptr = buckets_obj['buckets_'] if not buckets_ptr: return num_buckets = int(2**hashpower) for i in range(num_buckets): bucket = (buckets_ptr + i).dereference() storage_value_type = gdb.lookup_type(str(bucket.type) + '::storage_value_type') for j in range(self.slot_per_bucket): if bucket['occupied_']['_M_elems'][j]: value_blob = bucket['values_']['_M_elems'][j] storage_value = value_blob.cast(storage_value_type) yield ('key', storage_value['first']) yield ('value', storage_value['second']) def children(self): return self._iterator() def to_string(self): return 'cuckoohash_map' def display_hint(self): return 'map' def build_pretty_printer(): pp = gdb.printing.RegexpCollectionPrettyPrinter("libcuckoo") pp.add_printer('cuckoohash_map', '^cuckoohash_map<.*>$', CuckoohashMapPrinter) return pp def register_libcuckoo_printers(objfile): gdb.printing.register_pretty_printer(objfile, build_pretty_printer()) libcuckoo-0.3.1/libcuckoo/000077500000000000000000000000001426042121400154335ustar00rootroot00000000000000libcuckoo-0.3.1/libcuckoo/CMakeLists.txt000066400000000000000000000034421426042121400201760ustar00rootroot00000000000000# for write_basic_package_version_file() include(CMakePackageConfigHelpers) # we require the use of threads set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) # generate a version for cmake to use with find_package(libcuckoo) set (libcuckoo_VERSION "${libcuckoo_VERSION_MAJOR}.${libcuckoo_VERSION_MINOR}") set (libcuckoo_VERSION "${libcuckoo_VERSION}.${libcuckoo_VERSION_PATCH}") # libcuckoo is an interface (all headers) library target add_library(libcuckoo INTERFACE) add_library(libcuckoo::libcuckoo ALIAS libcuckoo) # tag libcuckoo target with a c++11 feature so that libcuckoo users # will have c++11 turned on in their compile when they use this target. # XXX: newer cmakes have a "cxx_std_11" feature that could be used target_compile_features (libcuckoo INTERFACE cxx_constexpr) # Include relative to the base directory target_include_directories(libcuckoo INTERFACE $ $ ) # switch on threading for all targets that link with libcuckoo target_link_libraries(libcuckoo INTERFACE Threads::Threads) # cmake packaging set (libcuckoo_pkgloc "share/cmake/libcuckoo") write_basic_package_version_file( "libcuckoo-config-version.cmake" VERSION ${libcuckoo_VERSION} COMPATIBILITY AnyNewerVersion) install(TARGETS libcuckoo EXPORT libcuckoo-targets) install(EXPORT libcuckoo-targets NAMESPACE libcuckoo:: DESTINATION ${libcuckoo_pkgloc} FILE "libcuckoo-targets.cmake") install(FILES libcuckoo-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/libcuckoo-config-version.cmake DESTINATION ${libcuckoo_pkgloc}) install( FILES cuckoohash_config.hh cuckoohash_map.hh cuckoohash_util.hh bucket_container.hh DESTINATION ${CMAKE_INSTALL_PREFIX}/include/libcuckoo ) libcuckoo-0.3.1/libcuckoo/bucket_container.hh000066400000000000000000000352241426042121400213010ustar00rootroot00000000000000#ifndef BUCKET_CONTAINER_H #define BUCKET_CONTAINER_H #include #include #include #include #include #include #include #include #include "cuckoohash_util.hh" namespace libcuckoo { /** * bucket_container manages storage of key-value pairs for the table. * It stores the items inline in uninitialized memory, and keeps track of which * slots have live data and which do not. It also stores a partial hash for * each live key. It is sized by powers of two. * * @tparam Key type of keys in the table * @tparam T type of values in the table * @tparam Allocator type of key-value pair allocator * @tparam Partial type of partial keys * @tparam SLOT_PER_BUCKET number of slots for each bucket in the table */ template class bucket_container { public: using key_type = Key; using mapped_type = T; using value_type = std::pair; private: using traits_ = typename std::allocator_traits< Allocator>::template rebind_traits; public: using allocator_type = typename traits_::allocator_type; using partial_t = Partial; using size_type = typename traits_::size_type; using reference = value_type &; using const_reference = const value_type &; using pointer = typename traits_::pointer; using const_pointer = typename traits_::const_pointer; /* * The bucket type holds SLOT_PER_BUCKET key-value pairs, along with their * partial keys and occupancy info. It uses aligned_storage arrays to store * the keys and values to allow constructing and destroying key-value pairs * in place. The lifetime of bucket data should be managed by the container. * It is the user's responsibility to confirm whether the data they are * accessing is live or not. */ class bucket { public: bucket() noexcept : occupied_() {} const value_type &kvpair(size_type ind) const { return *static_cast( static_cast(&values_[ind])); } value_type &kvpair(size_type ind) { return *static_cast(static_cast(&values_[ind])); } const key_type &key(size_type ind) const { return storage_kvpair(ind).first; } key_type &&movable_key(size_type ind) { return std::move(storage_kvpair(ind).first); } const mapped_type &mapped(size_type ind) const { return storage_kvpair(ind).second; } mapped_type &mapped(size_type ind) { return storage_kvpair(ind).second; } partial_t partial(size_type ind) const { return partials_[ind]; } partial_t &partial(size_type ind) { return partials_[ind]; } bool occupied(size_type ind) const { return occupied_[ind]; } bool &occupied(size_type ind) { return occupied_[ind]; } private: friend class bucket_container; using storage_value_type = std::pair; const storage_value_type &storage_kvpair(size_type ind) const { return *static_cast( static_cast(&values_[ind])); } storage_value_type &storage_kvpair(size_type ind) { return *static_cast( static_cast(&values_[ind])); } std::array::type, SLOT_PER_BUCKET> values_; std::array partials_; std::array occupied_; }; bucket_container(size_type hp, const allocator_type &allocator) : allocator_(allocator), bucket_allocator_(allocator), hashpower_(hp), buckets_(bucket_allocator_.allocate(size())) { // The bucket default constructor is nothrow, so we don't have to // worry about dealing with exceptions when constructing all the // elements. static_assert(std::is_nothrow_constructible::value, "bucket_container requires bucket to be nothrow " "constructible"); for (size_type i = 0; i < size(); ++i) { traits_::construct(allocator_, &buckets_[i]); } } ~bucket_container() noexcept { destroy_buckets(); } bucket_container(const bucket_container &bc) : allocator_( traits_::select_on_container_copy_construction(bc.allocator_)), bucket_allocator_(allocator_), hashpower_(bc.hashpower()), buckets_(transfer(bc.hashpower(), bc, std::false_type())) {} bucket_container(const bucket_container &bc, const allocator_type &a) : allocator_(a), bucket_allocator_(allocator_), hashpower_(bc.hashpower()), buckets_(transfer(bc.hashpower(), bc, std::false_type())) {} bucket_container(bucket_container &&bc) : allocator_(std::move(bc.allocator_)), bucket_allocator_(allocator_), hashpower_(bc.hashpower()), buckets_(std::move(bc.buckets_)) { // De-activate the other buckets container bc.buckets_ = nullptr; } bucket_container(bucket_container &&bc, const allocator_type &a) : allocator_(a), bucket_allocator_(allocator_) { move_assign(bc, std::false_type()); } bucket_container &operator=(const bucket_container &bc) { destroy_buckets(); copy_allocator(allocator_, bc.allocator_, typename traits_::propagate_on_container_copy_assignment()); bucket_allocator_ = allocator_; hashpower(bc.hashpower()); buckets_ = transfer(bc.hashpower(), bc, std::false_type()); return *this; } bucket_container &operator=(bucket_container &&bc) { destroy_buckets(); move_assign(bc, typename traits_::propagate_on_container_move_assignment()); return *this; } void swap(bucket_container &bc) noexcept { swap_allocator(allocator_, bc.allocator_, typename traits_::propagate_on_container_swap()); swap_allocator(bucket_allocator_, bc.bucket_allocator_, typename traits_::propagate_on_container_swap()); // Regardless of whether we actually swapped the allocators or not, it will // always be okay to do the remainder of the swap. This is because if the // allocators were swapped, then the subsequent operations are okay. If the // allocators weren't swapped but compare equal, then we're okay. If they // weren't swapped and compare unequal, then behavior is undefined, so // we're okay. size_t bc_hashpower = bc.hashpower(); bc.hashpower(hashpower()); hashpower(bc_hashpower); std::swap(buckets_, bc.buckets_); } size_type hashpower() const { return hashpower_.load(std::memory_order_acquire); } void hashpower(size_type val) { hashpower_.store(val, std::memory_order_release); } size_type size() const { return size_type(1) << hashpower(); } allocator_type get_allocator() const { return allocator_; } bucket &operator[](size_type i) { return buckets_[i]; } const bucket &operator[](size_type i) const { return buckets_[i]; } // Constructs live data in a bucket template void setKV(size_type ind, size_type slot, partial_t p, K &&k, Args &&... args) { bucket &b = buckets_[ind]; assert(!b.occupied(slot)); b.partial(slot) = p; traits_::construct(allocator_, std::addressof(b.storage_kvpair(slot)), std::piecewise_construct, std::forward_as_tuple(std::forward(k)), std::forward_as_tuple(std::forward(args)...)); // This must occur last, to enforce a strong exception guarantee b.occupied(slot) = true; } // Destroys live data in a bucket void eraseKV(size_type ind, size_type slot) { bucket &b = buckets_[ind]; assert(b.occupied(slot)); b.occupied(slot) = false; traits_::destroy(allocator_, std::addressof(b.storage_kvpair(slot))); } // Destroys all the live data in the buckets. Does not deallocate the bucket // memory. void clear() noexcept { static_assert( std::is_nothrow_destructible::value && std::is_nothrow_destructible::value, "bucket_container requires key and value to be nothrow " "destructible"); for (size_type i = 0; i < size(); ++i) { bucket &b = buckets_[i]; for (size_type j = 0; j < SLOT_PER_BUCKET; ++j) { if (b.occupied(j)) { eraseKV(i, j); } } } } // Destroys and deallocates all data in the buckets. After this operation, // the bucket container will have no allocated data. It is still valid to // swap, move or copy assign to this container. void clear_and_deallocate() noexcept { destroy_buckets(); } // Returns true if the bucket container memory has been deallocated, or false // if it still owns any memory. If true, the member-wise getter/setter // operations cannot be called safely. Object-level members (such as // hashpower and size) will remain valid after deallocation. bool is_deallocated() const noexcept { return buckets_ == nullptr; } private: using bucket_traits_ = typename traits_::template rebind_traits; using bucket_pointer = typename bucket_traits_::pointer; // true here means the allocators from `src` are propagated on libcuckoo_copy template void copy_allocator(A &dst, const A &src, std::true_type) { dst = src; } template void copy_allocator(A &dst, const A &src, std::false_type) {} // true here means the allocators from `src` are propagated on libcuckoo_swap template void swap_allocator(A &dst, A &src, std::true_type) { std::swap(dst, src); } template void swap_allocator(A &, A &, std::false_type) {} // true here means the bucket allocator should be propagated void move_assign(bucket_container &src, std::true_type) { allocator_ = std::move(src.allocator_); bucket_allocator_ = allocator_; hashpower(src.hashpower()); buckets_ = src.buckets_; src.buckets_ = nullptr; } void move_assign(bucket_container &src, std::false_type) { hashpower(src.hashpower()); if (allocator_ == src.allocator_) { buckets_ = src.buckets_; src.buckets_ = nullptr; } else { buckets_ = transfer(src.hashpower(), src, std::true_type()); } } void destroy_buckets() noexcept { if (is_deallocated()) { return; } // The bucket default constructor is nothrow, so we don't have to // worry about dealing with exceptions when constructing all the // elements. static_assert(std::is_nothrow_destructible::value, "bucket_container requires bucket to be nothrow " "destructible"); clear(); for (size_type i = 0; i < size(); ++i) { traits_::destroy(allocator_, &buckets_[i]); } bucket_allocator_.deallocate(buckets_, size()); buckets_ = nullptr; } // `true` here refers to whether or not we should move void move_or_copy(size_type dst_ind, size_type dst_slot, bucket &src, size_type src_slot, std::true_type) { setKV(dst_ind, dst_slot, src.partial(src_slot), src.movable_key(src_slot), std::move(src.mapped(src_slot))); } void move_or_copy(size_type dst_ind, size_type dst_slot, bucket &src, size_type src_slot, std::false_type) { setKV(dst_ind, dst_slot, src.partial(src_slot), src.key(src_slot), src.mapped(src_slot)); } template bucket_pointer transfer( size_type dst_hp, typename std::conditional::type src, std::integral_constant move) { assert(dst_hp >= src.hashpower()); if (src.is_deallocated()) { return nullptr; } bucket_container dst(dst_hp, get_allocator()); // Move/copy all occupied slots of the source buckets for (size_t i = 0; i < src.size(); ++i) { for (size_t j = 0; j < SLOT_PER_BUCKET; ++j) { if (src.buckets_[i].occupied(j)) { dst.move_or_copy(i, j, src.buckets_[i], j, move); } } } // Take away the pointer from `dst` and return it bucket_pointer dst_pointer = dst.buckets_; dst.buckets_ = nullptr; return dst_pointer; } // This allocator matches the value_type, but is not used to construct // storage_value_type pairs, or allocate buckets allocator_type allocator_; // This allocator is used for actually allocating buckets. It is simply // copy-constructed from `allocator_`, and will always be copied whenever // allocator_ is copied. typename traits_::template rebind_alloc bucket_allocator_; // This needs to be atomic, since it can be read and written by multiple // threads not necessarily synchronized by a lock. std::atomic hashpower_; // These buckets are protected by striped locks (external to the // BucketContainer), which must be obtained before accessing a bucket. bucket_pointer buckets_; // If the key and value are Trivial, the bucket be serilizable. Since we // already disallow user-specialized instances of std::pair, we know that the // default implementation of std::pair uses a default copy constructor, so // this should be okay. We could in theory just check if the type is // TriviallyCopyable but this check is not available on some compilers we // want to support. template friend typename std::enable_if::value && std::is_trivial::value, std::ostream &>::type operator<<(std::ostream &os, const bucket_container &bc) { size_type hp = bc.hashpower(); os.write(reinterpret_cast(&hp), sizeof(size_type)); os.write(reinterpret_cast(bc.buckets_), sizeof(bucket) * bc.size()); return os; } template friend typename std::enable_if::value && std::is_trivial::value, std::istream &>::type operator>>(std::istream &is, bucket_container &bc) { size_type hp; is.read(reinterpret_cast(&hp), sizeof(size_type)); bucket_container new_bc(hp, bc.get_allocator()); is.read(reinterpret_cast(new_bc.buckets_), new_bc.size() * sizeof(bucket)); bc.swap(new_bc); return is; } }; } // namespace libcuckoo #endif // BUCKET_CONTAINER_H libcuckoo-0.3.1/libcuckoo/cuckoohash_config.hh000066400000000000000000000021431426042121400214300ustar00rootroot00000000000000/** \file */ #ifndef _CUCKOOHASH_CONFIG_HH #define _CUCKOOHASH_CONFIG_HH #include #include namespace libcuckoo { //! The default maximum number of keys per bucket constexpr size_t DEFAULT_SLOT_PER_BUCKET = 4; //! The default number of elements in an empty hash table constexpr size_t DEFAULT_SIZE = (1U << 16) * DEFAULT_SLOT_PER_BUCKET; //! The default minimum load factor that the table allows for automatic //! expansion. It must be a number between 0.0 and 1.0. The table will throw //! load_factor_too_low if the load factor falls below this value //! during an automatic expansion. constexpr double DEFAULT_MINIMUM_LOAD_FACTOR = 0.05; //! An alias for the value that sets no limit on the maximum hashpower. If this //! value is set as the maximum hashpower limit, there will be no limit. This //! is also the default initial value for the maximum hashpower in a table. constexpr size_t NO_MAXIMUM_HASHPOWER = std::numeric_limits::max(); //! set LIBCUCKOO_DEBUG to 1 to enable debug output #define LIBCUCKOO_DEBUG 0 } // namespace libcuckoo #endif // _CUCKOOHASH_CONFIG_HH libcuckoo-0.3.1/libcuckoo/cuckoohash_map.hh000066400000000000000000003065311426042121400207500ustar00rootroot00000000000000/** \file */ #ifndef _CUCKOOHASH_MAP_HH #define _CUCKOOHASH_MAP_HH #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cuckoohash_config.hh" #include "cuckoohash_util.hh" #include "bucket_container.hh" namespace libcuckoo { /** * A concurrent hash table * * @tparam Key type of keys in the table * @tparam T type of values in the table * @tparam Hash type of hash functor * @tparam KeyEqual type of equality comparison functor * @tparam Allocator type of allocator. We suggest using an aligned allocator, * because the table relies on types that are over-aligned to optimize * concurrent cache usage. * @tparam SLOT_PER_BUCKET number of slots for each bucket in the table */ template , class KeyEqual = std::equal_to, class Allocator = std::allocator>, std::size_t SLOT_PER_BUCKET = DEFAULT_SLOT_PER_BUCKET> class cuckoohash_map { private: // Type of the partial key using partial_t = uint8_t; // The type of the buckets container using buckets_t = bucket_container; public: /** @name Type Declarations */ /**@{*/ using key_type = typename buckets_t::key_type; using mapped_type = typename buckets_t::mapped_type; /** * This type is defined as an @c std::pair. Note that table behavior is * undefined if a user-defined specialization of @c std::pair or @c * std::pair exists. */ using value_type = typename buckets_t::value_type; using size_type = typename buckets_t::size_type; using difference_type = std::ptrdiff_t; using hasher = Hash; using key_equal = KeyEqual; using allocator_type = typename buckets_t::allocator_type; using reference = typename buckets_t::reference; using const_reference = typename buckets_t::const_reference; using pointer = typename buckets_t::pointer; using const_pointer = typename buckets_t::const_pointer; class locked_table; /**@}*/ /** @name Table Parameters */ /**@{*/ /** * The number of slots per hash bucket */ static constexpr uint16_t slot_per_bucket() { return SLOT_PER_BUCKET; } /**@}*/ /** @name Constructors, Destructors, and Assignment */ /**@{*/ /** * Creates a new cuckohash_map instance * * @param n the number of elements to reserve space for initially * @param hf hash function instance to use * @param equal equality function instance to use * @param alloc allocator instance to use */ cuckoohash_map(size_type n = DEFAULT_SIZE, const Hash &hf = Hash(), const KeyEqual &equal = KeyEqual(), const Allocator &alloc = Allocator()) : hash_fn_(hf), eq_fn_(equal), buckets_(reserve_calc(n), alloc), old_buckets_(0, alloc), all_locks_(get_allocator()), num_remaining_lazy_rehash_locks_(0), minimum_load_factor_(DEFAULT_MINIMUM_LOAD_FACTOR), maximum_hashpower_(NO_MAXIMUM_HASHPOWER), max_num_worker_threads_(0) { all_locks_.emplace_back(get_allocator()); all_locks_.back().resize(std::min(bucket_count(), size_type(kMaxNumLocks))); } /** * Constructs the map with the contents of the range @c [first, last]. If * multiple elements in the range have equivalent keys, it is unspecified * which element is inserted. * * @param first the beginning of the range to copy from * @param last the end of the range to copy from * @param n the number of elements to reserve space for initially * @param hf hash function instance to use * @param equal equality function instance to use * @param alloc allocator instance to use */ template cuckoohash_map(InputIt first, InputIt last, size_type n = DEFAULT_SIZE, const Hash &hf = Hash(), const KeyEqual &equal = KeyEqual(), const Allocator &alloc = Allocator()) : cuckoohash_map(n, hf, equal, alloc) { for (; first != last; ++first) { insert(first->first, first->second); } } /** * Copy constructor. If @p other is being modified concurrently, behavior is * unspecified. * * @param other the map being copied */ cuckoohash_map(const cuckoohash_map &other) = default; /** * Copy constructor with separate allocator. If @p other is being modified * concurrently, behavior is unspecified. * * @param other the map being copied * @param alloc the allocator instance to use with the map */ cuckoohash_map(const cuckoohash_map &other, const Allocator &alloc) : hash_fn_(other.hash_fn_), eq_fn_(other.eq_fn_), buckets_(other.buckets_, alloc), old_buckets_(other.old_buckets_, alloc), all_locks_(alloc), num_remaining_lazy_rehash_locks_( other.num_remaining_lazy_rehash_locks_), minimum_load_factor_(other.minimum_load_factor_), maximum_hashpower_(other.maximum_hashpower_), max_num_worker_threads_(other.max_num_worker_threads_) { if (other.get_allocator() == alloc) { all_locks_ = other.all_locks_; } else { add_locks_from_other(other); } } /** * Move constructor. If @p other is being modified concurrently, behavior is * unspecified. * * @param other the map being moved */ cuckoohash_map(cuckoohash_map &&other) = default; /** * Move constructor with separate allocator. If the map being moved is being * modified concurrently, behavior is unspecified. * * @param other the map being moved * @param alloc the allocator instance to use with the map */ cuckoohash_map(cuckoohash_map &&other, const Allocator &alloc) : hash_fn_(std::move(other.hash_fn_)), eq_fn_(std::move(other.eq_fn_)), buckets_(std::move(other.buckets_), alloc), old_buckets_(std::move(other.old_buckets_), alloc), all_locks_(alloc), num_remaining_lazy_rehash_locks_( other.num_remaining_lazy_rehash_locks_), minimum_load_factor_(other.minimum_load_factor_), maximum_hashpower_(other.maximum_hashpower_), max_num_worker_threads_(other.max_num_worker_threads_) { if (other.get_allocator() == alloc) { all_locks_ = std::move(other.all_locks_); } else { add_locks_from_other(other); } } /** * Constructs the map with the contents of initializer list @c init. * * @param init initializer list to initialize the elements of the map with * @param n the number of elements to reserve space for initially * @param hf hash function instance to use * @param equal equality function instance to use * @param alloc allocator instance to use */ cuckoohash_map(std::initializer_list init, size_type n = DEFAULT_SIZE, const Hash &hf = Hash(), const KeyEqual &equal = KeyEqual(), const Allocator &alloc = Allocator()) : cuckoohash_map(init.begin(), init.end(), n, hf, equal, alloc) {} /** * Exchanges the contents of the map with those of @p other * * @param other the map to exchange contents with */ void swap(cuckoohash_map &other) noexcept { std::swap(hash_fn_, other.hash_fn_); std::swap(eq_fn_, other.eq_fn_); buckets_.swap(other.buckets_); all_locks_.swap(other.all_locks_); other.minimum_load_factor_.store( minimum_load_factor_.exchange(other.minimum_load_factor(), std::memory_order_release), std::memory_order_release); other.maximum_hashpower_.store( maximum_hashpower_.exchange(other.maximum_hashpower(), std::memory_order_release), std::memory_order_release); } /** * Copy assignment operator. If @p other is being modified concurrently, * behavior is unspecified. * * @param other the map to assign from * @return @c *this */ cuckoohash_map &operator=(const cuckoohash_map &other) = default; /** * Move assignment operator. If @p other is being modified concurrently, * behavior is unspecified. * * @param other the map to assign from * @return @c *this */ cuckoohash_map &operator=(cuckoohash_map &&other) = default; /** * Initializer list assignment operator * * @param ilist an initializer list to assign from * @return @c *this */ cuckoohash_map &operator=(std::initializer_list ilist) { clear(); for (const auto &item : ilist) { insert(item.first, item.second); } return *this; } /**@}*/ /** @name Table Details * * Methods for getting information about the table. Methods that query * changing properties of the table are not synchronized with concurrent * operations, and may return out-of-date information if the table is being * concurrently modified. They will also continue to work after the container * has been moved. * */ /**@{*/ /** * Returns the function that hashes the keys * * @return the hash function */ hasher hash_function() const { return hash_fn_; } /** * Returns the function that compares keys for equality * * @return the key comparison function */ key_equal key_eq() const { return eq_fn_; } /** * Returns the allocator associated with the map * * @return the associated allocator */ allocator_type get_allocator() const { return buckets_.get_allocator(); } /** * Returns the hashpower of the table, which is log2(@ref * bucket_count()). * * @return the hashpower */ size_type hashpower() const { return buckets_.hashpower(); } /** * Returns the number of buckets in the table. * * @return the bucket count */ size_type bucket_count() const { return buckets_.size(); } /** * Returns whether the table is empty or not. * * @return true if the table is empty, false otherwise */ bool empty() const { return size() == 0; } /** * Returns the number of elements in the table. * * @return number of elements in the table */ size_type size() const { if (all_locks_.size() == 0) { return 0; } counter_type s = 0; for (spinlock &lock : get_current_locks()) { s += lock.elem_counter(); } assert(s >= 0); return static_cast(s); } /** Returns the current capacity of the table, that is, @ref bucket_count() * × @ref slot_per_bucket(). * * @return capacity of table */ size_type capacity() const { return bucket_count() * slot_per_bucket(); } /** * Returns the percentage the table is filled, that is, @ref size() ÷ * @ref capacity(). * * @return load factor of the table */ double load_factor() const { return static_cast(size()) / static_cast(capacity()); } /** * Sets the minimum load factor allowed for automatic expansions. If an * expansion is needed when the load factor of the table is lower than this * threshold, @ref load_factor_too_low is thrown. It will not be * thrown for an explicitly-triggered expansion. * * @param mlf the load factor to set the minimum to * @throw std::invalid_argument if the given load factor is less than 0.0 * or greater than 1.0 */ void minimum_load_factor(const double mlf) { if (mlf < 0.0) { throw std::invalid_argument("load factor " + std::to_string(mlf) + " cannot be " "less than 0"); } else if (mlf > 1.0) { throw std::invalid_argument("load factor " + std::to_string(mlf) + " cannot be " "greater than 1"); } minimum_load_factor_.store(mlf, std::memory_order_release); } /** * Returns the minimum load factor of the table * * @return the minimum load factor */ double minimum_load_factor() const { return minimum_load_factor_.load(std::memory_order_acquire); } /** * Sets the maximum hashpower the table can be. If set to @ref * NO_MAXIMUM_HASHPOWER, there will be no limit on the hashpower. * Otherwise, the table will not be able to expand beyond the given * hashpower, either by an explicit or an automatic expansion. * * @param mhp the hashpower to set the maximum to * @throw std::invalid_argument if the current hashpower exceeds the limit */ void maximum_hashpower(size_type mhp) { if (hashpower() > mhp) { throw std::invalid_argument("maximum hashpower " + std::to_string(mhp) + " is less than current hashpower"); } maximum_hashpower_.store(mhp, std::memory_order_release); } /** * Returns the maximum hashpower of the table * * @return the maximum hashpower */ size_type maximum_hashpower() const { return maximum_hashpower_.load(std::memory_order_acquire); } /** * Set the maximum number of extra worker threads the table can spawn when * doing large batch operations. Currently batch operations occur in the * following scenarios. * - Any resizing operation which invokes cuckoo_expand_simple. This * includes any explicit rehash/resize operation, or any general resize if * the data is not nothrow-move-constructible. * - Creating a locked_table or resizing within a locked_table. * * @param num_threads the number of extra threads */ void max_num_worker_threads(size_type extra_threads) { max_num_worker_threads_.store(extra_threads, std::memory_order_release); } /** * Returns the maximum number of extra worker threads. */ size_type max_num_worker_threads() const { return max_num_worker_threads_.load(std::memory_order_acquire); } /**@}*/ /** @name Table Operations * * These are operations that affect the data in the table. They are safe to * call concurrently with each other. * */ /**@{*/ /** * Searches the table for @p key, and invokes @p fn on the value. @p fn is * not allowed to modify the contents of the value if found. * * @tparam K type of the key. This can be any type comparable with @c key_type * @tparam F type of the functor. It should implement the method * void operator()(const mapped_type&). * @param key the key to search for * @param fn the functor to invoke if the element is found * @return true if the key was found and functor invoked, false otherwise */ template bool find_fn(const K &key, F fn) const { const hash_value hv = hashed_key(key); const auto b = snapshot_and_lock_two(hv); const table_position pos = cuckoo_find(key, hv.partial, b.i1, b.i2); if (pos.status == ok) { fn(buckets_[pos.index].mapped(pos.slot)); return true; } else { return false; } } /** * Searches the table for @p key, and invokes @p fn on the value. @p fn is * allow to modify the contents of the value if found. * * @tparam K type of the key. This can be any type comparable with @c key_type * @tparam F type of the functor. It should implement the method * void operator()(mapped_type&). * @param key the key to search for * @param fn the functor to invoke if the element is found * @return true if the key was found and functor invoked, false otherwise */ template bool update_fn(const K &key, F fn) { const hash_value hv = hashed_key(key); const auto b = snapshot_and_lock_two(hv); const table_position pos = cuckoo_find(key, hv.partial, b.i1, b.i2); if (pos.status == ok) { fn(buckets_[pos.index].mapped(pos.slot)); return true; } else { return false; } } /** * Searches for @p key in the table, and invokes @p fn on the value if the * key is found. The functor can mutate the value, and should return @c true * in order to erase the element, and @c false otherwise. * * @tparam K type of the key * @tparam F type of the functor. It should implement the method * bool operator()(mapped_type&). * @param key the key to possibly erase from the table * @param fn the functor to invoke if the element is found * @return true if @p key was found and @p fn invoked, false otherwise */ template bool erase_fn(const K &key, F fn) { const hash_value hv = hashed_key(key); const auto b = snapshot_and_lock_two(hv); const table_position pos = cuckoo_find(key, hv.partial, b.i1, b.i2); if (pos.status == ok) { if (fn(buckets_[pos.index].mapped(pos.slot))) { del_from_bucket(pos.index, pos.slot); } return true; } else { return false; } } /** * Searches for @p key in the table. If the key is found, then @p fn is * called on the existing value, and nothing happens to the passed-in key and * values. The functor can mutate the value, and should return @c true in * order to erase the element, and @c false otherwise. If the key is not * found and must be inserted, the pair will be constructed by forwarding the * given key and values. If there is no room left in the table, it will be * automatically expanded. Expansion may throw exceptions. * * @tparam K type of the key * @tparam F type of the functor. It should implement the method * bool operator()(mapped_type&). * @tparam Args list of types for the value constructor arguments * @param key the key to insert into the table * @param fn the functor to invoke if the element is found. If your @p fn * needs more data that just the value being modified, consider implementing * it as a lambda with captured arguments. * @param val a list of constructor arguments with which to create the value * @return true if a new key was inserted, false if the key was already in * the table */ template bool uprase_fn(K &&key, F fn, Args &&... val) { hash_value hv = hashed_key(key); auto b = snapshot_and_lock_two(hv); table_position pos = cuckoo_insert_loop(hv, b, key); if (pos.status == ok) { add_to_bucket(pos.index, pos.slot, hv.partial, std::forward(key), std::forward(val)...); } else { if (fn(buckets_[pos.index].mapped(pos.slot))) { del_from_bucket(pos.index, pos.slot); } } return pos.status == ok; } /** * Equivalent to calling @ref uprase_fn with a functor that modifies the * given value and always returns false (meaning the element is not removed). * The passed-in functor must implement the method void * operator()(mapped_type&). */ template bool upsert(K &&key, F fn, Args &&... val) { return uprase_fn(std::forward(key), [&fn](mapped_type &v) { fn(v); return false; }, std::forward(val)...); } /** * Copies the value associated with @p key into @p val. Equivalent to * calling @ref find_fn with a functor that copies the value into @p val. @c * mapped_type must be @c CopyAssignable. */ template bool find(const K &key, mapped_type &val) const { return find_fn(key, [&val](const mapped_type &v) mutable { val = v; }); } /** Searches the table for @p key, and returns the associated value it * finds. @c mapped_type must be @c CopyConstructible. * * @tparam K type of the key * @param key the key to search for * @return the value associated with the given key * @throw std::out_of_range if the key is not found */ template mapped_type find(const K &key) const { const hash_value hv = hashed_key(key); const auto b = snapshot_and_lock_two(hv); const table_position pos = cuckoo_find(key, hv.partial, b.i1, b.i2); if (pos.status == ok) { return buckets_[pos.index].mapped(pos.slot); } else { throw std::out_of_range("key not found in table"); } } /** * Returns whether or not @p key is in the table. Equivalent to @ref * find_fn with a functor that does nothing. */ template bool contains(const K &key) const { return find_fn(key, [](const mapped_type &) {}); } /** * Updates the value associated with @p key to @p val. Equivalent to * calling @ref update_fn with a functor that assigns the existing mapped * value to @p val. @c mapped_type must be @c MoveAssignable or @c * CopyAssignable. */ template bool update(const K &key, V &&val) { return update_fn(key, [&val](mapped_type &v) { v = std::forward(val); }); } /** * Inserts the key-value pair into the table. Equivalent to calling @ref * upsert with a functor that does nothing. */ template bool insert(K &&key, Args &&... val) { return upsert(std::forward(key), [](mapped_type &) {}, std::forward(val)...); } /** * Inserts the key-value pair into the table. If the key is already in the * table, assigns the existing mapped value to @p val. Equivalent to * calling @ref upsert with a functor that assigns the mapped value to @p * val. */ template bool insert_or_assign(K &&key, V &&val) { return upsert(std::forward(key), [&val](mapped_type &m) { m = val; }, std::forward(val)); } /** * Erases the key from the table. Equivalent to calling @ref erase_fn with a * functor that just returns true. */ template bool erase(const K &key) { return erase_fn(key, [](mapped_type &) { return true; }); } /** * Resizes the table to the given hashpower. If this hashpower is not larger * than the current hashpower, then it decreases the hashpower to the * maximum of the specified value and the smallest hashpower that can hold * all the elements currently in the table. * * @param n the hashpower to set for the table * @return true if the table changed size, false otherwise */ bool rehash(size_type n) { return cuckoo_rehash(n); } /** * Reserve enough space in the table for the given number of elements. If * the table can already hold that many elements, the function will shrink * the table to the smallest hashpower that can hold the maximum of the * specified amount and the current table size. * * @param n the number of elements to reserve space for * @return true if the size of the table changed, false otherwise */ bool reserve(size_type n) { return cuckoo_reserve(n); } /** * Removes all elements in the table, calling their destructors. */ void clear() { auto all_locks_manager = lock_all(normal_mode()); cuckoo_clear(); } /** * Construct a @ref locked_table object that owns all the locks in the * table. * * @return a \ref locked_table instance */ locked_table lock_table() { return locked_table(*this); } /**@}*/ private: // Constructor helpers void add_locks_from_other(const cuckoohash_map &other) { locks_t &other_locks = other.get_current_locks(); all_locks_.emplace_back(get_allocator()); all_locks_.back().resize(other_locks.size()); std::copy(other_locks.begin(), other_locks.end(), get_current_locks().begin()); } // Hashing types and functions // true if the key is small and simple, which means using partial keys for // lookup would probably slow us down static constexpr bool is_simple() { return std::is_standard_layout::value && std::is_trivial::value && sizeof(key_type) <= 8; } // Whether or not the data is nothrow-move-constructible. static constexpr bool is_data_nothrow_move_constructible() { return std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value; } // Contains a hash and partial for a given key. The partial key is used for // partial-key cuckoohashing, and for finding the alternate bucket of that a // key hashes to. struct hash_value { size_type hash; partial_t partial; }; template hash_value hashed_key(const K &key) const { const size_type hash = hash_function()(key); return {hash, partial_key(hash)}; } template size_type hashed_key_only_hash(const K &key) const { return hash_function()(key); } // hashsize returns the number of buckets corresponding to a given // hashpower. static inline size_type hashsize(const size_type hp) { return size_type(1) << hp; } // hashmask returns the bitmask for the buckets array corresponding to a // given hashpower. static inline size_type hashmask(const size_type hp) { return hashsize(hp) - 1; } // The partial key must only depend on the hash value. It cannot change with // the hashpower, because, in order for `cuckoo_fast_double` to work // properly, the alt_index must only grow by one bit at the top each time we // expand the table. static partial_t partial_key(const size_type hash) { const uint64_t hash_64bit = hash; const uint32_t hash_32bit = (static_cast(hash_64bit) ^ static_cast(hash_64bit >> 32)); const uint16_t hash_16bit = (static_cast(hash_32bit) ^ static_cast(hash_32bit >> 16)); const uint8_t hash_8bit = (static_cast(hash_16bit) ^ static_cast(hash_16bit >> 8)); return hash_8bit; } // index_hash returns the first possible bucket that the given hashed key // could be. static inline size_type index_hash(const size_type hp, const size_type hv) { return hv & hashmask(hp); } // alt_index returns the other possible bucket that the given hashed key // could be. It takes the first possible bucket as a parameter. Note that // this function will return the first possible bucket if index is the // second possible bucket, so alt_index(ti, partial, alt_index(ti, partial, // index_hash(ti, hv))) == index_hash(ti, hv). static inline size_type alt_index(const size_type hp, const partial_t partial, const size_type index) { // ensure tag is nonzero for the multiply. 0xc6a4a7935bd1e995 is the // hash constant from 64-bit MurmurHash2 const size_type nonzero_tag = static_cast(partial) + 1; return (index ^ (nonzero_tag * 0xc6a4a7935bd1e995)) & hashmask(hp); } // Locking types // Counter type using counter_type = int64_t; // A fast, lightweight spinlock // // Per-spinlock, we also maintain some metadata about the contents of the // table. Storing data per-spinlock avoids false sharing issues when multiple // threads need to update this metadata. We store the following information: // // - elem_counter: A counter indicating how many elements in the table are // under this lock. One can compute the size of the table by summing the // elem_counter over all locks. // // - is_migrated: When resizing with cuckoo_fast_double, we do not // immediately rehash elements from the old buckets array to the new one. // Instead, we'll mark all of the locks as not migrated. So anybody trying to // acquire the lock must also migrate the corresponding buckets if // !is_migrated. LIBCUCKOO_SQUELCH_PADDING_WARNING class LIBCUCKOO_ALIGNAS(64) spinlock { public: spinlock() : elem_counter_(0), is_migrated_(true) { lock_.clear(); } spinlock(const spinlock &other) noexcept : elem_counter_(other.elem_counter()), is_migrated_(other.is_migrated()) { lock_.clear(); } spinlock &operator=(const spinlock &other) noexcept { elem_counter() = other.elem_counter(); is_migrated() = other.is_migrated(); return *this; } void lock() noexcept { while (lock_.test_and_set(std::memory_order_acq_rel)) ; } void unlock() noexcept { lock_.clear(std::memory_order_release); } bool try_lock() noexcept { return !lock_.test_and_set(std::memory_order_acq_rel); } counter_type &elem_counter() noexcept { return elem_counter_; } counter_type elem_counter() const noexcept { return elem_counter_; } bool &is_migrated() noexcept { return is_migrated_; } bool is_migrated() const noexcept { return is_migrated_; } private: std::atomic_flag lock_; counter_type elem_counter_; bool is_migrated_; }; template using rebind_alloc = typename std::allocator_traits::template rebind_alloc; using locks_t = std::vector>; using all_locks_t = std::list>; // Classes for managing locked buckets. By storing and moving around sets of // locked buckets in these classes, we can ensure that they are unlocked // properly. struct LockDeleter { void operator()(spinlock *l) const { l->unlock(); } }; using LockManager = std::unique_ptr; // Each of the locking methods can operate in two modes: locked_table_mode // and normal_mode. When we're in locked_table_mode, we assume the caller has // already taken all locks on the buckets. We also require that all data is // rehashed immediately, so that the caller never has to look through any // locks. In normal_mode, we actually do take locks, and can rehash lazily. using locked_table_mode = std::integral_constant; using normal_mode = std::integral_constant; class TwoBuckets { public: TwoBuckets() {} TwoBuckets(size_type i1_, size_type i2_, locked_table_mode) : i1(i1_), i2(i2_) {} TwoBuckets(locks_t &locks, size_type i1_, size_type i2_, normal_mode) : i1(i1_), i2(i2_), first_manager_(&locks[lock_ind(i1)]), second_manager_((lock_ind(i1) != lock_ind(i2)) ? &locks[lock_ind(i2)] : nullptr) {} void unlock() { first_manager_.reset(); second_manager_.reset(); } size_type i1, i2; private: LockManager first_manager_, second_manager_; }; struct AllUnlocker { void operator()(cuckoohash_map *map) const { for (auto it = first_locked; it != map->all_locks_.end(); ++it) { locks_t &locks = *it; for (spinlock &lock : locks) { lock.unlock(); } } } typename all_locks_t::iterator first_locked; }; using AllLocksManager = std::unique_ptr; // This exception is thrown whenever we try to lock a bucket, but the // hashpower is not what was expected class hashpower_changed {}; // After taking a lock on the table for the given bucket, this function will // check the hashpower to make sure it is the same as what it was before the // lock was taken. If it isn't unlock the bucket and throw a // hashpower_changed exception. inline void check_hashpower(size_type hp, spinlock &lock) const { if (hashpower() != hp) { lock.unlock(); LIBCUCKOO_DBG("%s", "hashpower changed\n"); throw hashpower_changed(); } } // If necessary, rehashes the buckets corresponding to the given lock index, // and sets the is_migrated flag to true. We should only ever do migrations // if the data is nothrow move constructible, so this function is noexcept. // // This only works if our current locks array is at the maximum size, because // otherwise, rehashing could require taking other locks. Assumes the lock at // the given index is taken. // // If IS_LAZY is true, we assume the lock is being rehashed in a lazy // (on-demand) fashion, so we additionally decrement the number of locks we // need to lazy_rehash. This may trigger false sharing with other // lazy-rehashing threads, but the hope is that the fraction of such // operations is low-enough to not significantly impact overall performance. static constexpr bool kIsLazy = true; static constexpr bool kIsNotLazy = false; template void rehash_lock(size_t l) const noexcept { locks_t &locks = get_current_locks(); spinlock &lock = locks[l]; if (lock.is_migrated()) return; assert(is_data_nothrow_move_constructible()); assert(locks.size() == kMaxNumLocks); assert(old_buckets_.hashpower() + 1 == buckets_.hashpower()); assert(old_buckets_.size() >= kMaxNumLocks); // Iterate through all buckets in old_buckets that are controlled by this // lock, and move them into the current buckets array. for (size_type bucket_ind = l; bucket_ind < old_buckets_.size(); bucket_ind += kMaxNumLocks) { move_bucket(old_buckets_, buckets_, bucket_ind); } lock.is_migrated() = true; if (IS_LAZY) { decrement_num_remaining_lazy_rehash_locks(); } } // locks the given bucket index. // // throws hashpower_changed if it changed after taking the lock. LockManager lock_one(size_type, size_type, locked_table_mode) const { return LockManager(); } LockManager lock_one(size_type hp, size_type i, normal_mode) const { locks_t &locks = get_current_locks(); const size_type l = lock_ind(i); spinlock &lock = locks[l]; lock.lock(); check_hashpower(hp, lock); rehash_lock(l); return LockManager(&lock); } // locks the two bucket indexes, always locking the earlier index first to // avoid deadlock. If the two indexes are the same, it just locks one. // // throws hashpower_changed if it changed after taking the lock. TwoBuckets lock_two(size_type, size_type i1, size_type i2, locked_table_mode) const { return TwoBuckets(i1, i2, locked_table_mode()); } TwoBuckets lock_two(size_type hp, size_type i1, size_type i2, normal_mode) const { size_type l1 = lock_ind(i1); size_type l2 = lock_ind(i2); if (l2 < l1) { std::swap(l1, l2); } locks_t &locks = get_current_locks(); locks[l1].lock(); check_hashpower(hp, locks[l1]); if (l2 != l1) { locks[l2].lock(); } rehash_lock(l1); rehash_lock(l2); return TwoBuckets(locks, i1, i2, normal_mode()); } // lock_three locks the three bucket indexes in numerical order, returning // the containers as a two (i1 and i2) and a one (i3). The one will not be // active if i3 shares a lock index with i1 or i2. // // throws hashpower_changed if it changed after taking the lock. std::pair lock_three(size_type, size_type i1, size_type i2, size_type, locked_table_mode) const { return std::make_pair(TwoBuckets(i1, i2, locked_table_mode()), LockManager()); } std::pair lock_three(size_type hp, size_type i1, size_type i2, size_type i3, normal_mode) const { std::array l{{lock_ind(i1), lock_ind(i2), lock_ind(i3)}}; // Lock in order. if (l[2] < l[1]) std::swap(l[2], l[1]); if (l[2] < l[0]) std::swap(l[2], l[0]); if (l[1] < l[0]) std::swap(l[1], l[0]); locks_t &locks = get_current_locks(); locks[l[0]].lock(); check_hashpower(hp, locks[l[0]]); if (l[1] != l[0]) { locks[l[1]].lock(); } if (l[2] != l[1]) { locks[l[2]].lock(); } rehash_lock(l[0]); rehash_lock(l[1]); rehash_lock(l[2]); return std::make_pair(TwoBuckets(locks, i1, i2, normal_mode()), LockManager((lock_ind(i3) == lock_ind(i1) || lock_ind(i3) == lock_ind(i2)) ? nullptr : &locks[lock_ind(i3)])); } // snapshot_and_lock_two loads locks the buckets associated with the given // hash value, making sure the hashpower doesn't change before the locks are // taken. Thus it ensures that the buckets and locks corresponding to the // hash value will stay correct as long as the locks are held. It returns // the bucket indices associated with the hash value and the current // hashpower. template TwoBuckets snapshot_and_lock_two(const hash_value &hv) const { while (true) { // Keep the current hashpower and locks we're using to compute the buckets const size_type hp = hashpower(); const size_type i1 = index_hash(hp, hv.hash); const size_type i2 = alt_index(hp, hv.partial, i1); try { return lock_two(hp, i1, i2, TABLE_MODE()); } catch (hashpower_changed &) { // The hashpower changed while taking the locks. Try again. continue; } } } // lock_all takes all the locks, and returns a deleter object that releases // the locks upon destruction. It does NOT perform any hashpower checks, or // rehash any un-migrated buckets. // // Note that after taking all the locks, it is okay to resize the buckets_ // container, since no other threads should be accessing the buckets. AllLocksManager lock_all(locked_table_mode) { return AllLocksManager(); } AllLocksManager lock_all(normal_mode) { // all_locks_ should never decrease in size, so if it is non-empty now, it // will remain non-empty assert(!all_locks_.empty()); const auto first_locked = std::prev(all_locks_.end()); auto current_locks = first_locked; while (current_locks != all_locks_.end()) { locks_t &locks = *current_locks; for (spinlock &lock : locks) { lock.lock(); } ++current_locks; } // Once we have taken all the locks of the "current" container, nobody // else can do locking operations on the table. return AllLocksManager(this, AllUnlocker{first_locked}); } // lock_ind converts an index into buckets to an index into locks. static inline size_type lock_ind(const size_type bucket_ind) { return bucket_ind & (kMaxNumLocks - 1); } // Data storage types and functions // The type of the bucket using bucket = typename buckets_t::bucket; // Status codes for internal functions enum cuckoo_status { ok, failure, failure_key_not_found, failure_key_duplicated, failure_table_full, failure_under_expansion, }; // A composite type for functions that need to return a table position, and // a status code. struct table_position { size_type index; size_type slot; cuckoo_status status; }; // Searching types and functions // cuckoo_find searches the table for the given key, returning the position // of the element found, or a failure status code if the key wasn't found. // It expects the locks to be taken and released outside the function. template table_position cuckoo_find(const K &key, const partial_t partial, const size_type i1, const size_type i2) const { int slot = try_read_from_bucket(buckets_[i1], partial, key); if (slot != -1) { return table_position{i1, static_cast(slot), ok}; } slot = try_read_from_bucket(buckets_[i2], partial, key); if (slot != -1) { return table_position{i2, static_cast(slot), ok}; } return table_position{0, 0, failure_key_not_found}; } // try_read_from_bucket will search the bucket for the given key and return // the index of the slot if found, or -1 if not found. template int try_read_from_bucket(const bucket &b, const partial_t partial, const K &key) const { // Silence a warning from MSVC about partial being unused if is_simple. (void)partial; for (int i = 0; i < static_cast(slot_per_bucket()); ++i) { if (!b.occupied(i) || (!is_simple() && partial != b.partial(i))) { continue; } else if (key_eq()(b.key(i), key)) { return i; } } return -1; } // Insertion types and function /** * Runs cuckoo_insert in a loop until it succeeds in insert and upsert, so * we pulled out the loop to avoid duplicating logic. * * @param hv the hash value of the key * @param b bucket locks * @param key the key to insert * @return table_position of the location to insert the new element, or the * site of the duplicate element with a status code if there was a duplicate. * In either case, the locks will still be held after the function ends. * @throw load_factor_too_low if expansion is necessary, but the * load factor of the table is below the threshold */ template table_position cuckoo_insert_loop(hash_value hv, TwoBuckets &b, K &key) { table_position pos; while (true) { const size_type hp = hashpower(); pos = cuckoo_insert(hv, b, key); switch (pos.status) { case ok: case failure_key_duplicated: return pos; case failure_table_full: // Expand the table and try again, re-grabbing the locks cuckoo_fast_double(hp); b = snapshot_and_lock_two(hv); break; case failure_under_expansion: // The table was under expansion while we were cuckooing. Re-grab the // locks and try again. b = snapshot_and_lock_two(hv); break; default: assert(false); } } } // cuckoo_insert tries to find an empty slot in either of the buckets to // insert the given key into, performing cuckoo hashing if necessary. It // expects the locks to be taken outside the function. Before inserting, it // checks that the key isn't already in the table. cuckoo hashing presents // multiple concurrency issues, which are explained in the function. The // following return states are possible: // // ok -- Found an empty slot, locks will be held on both buckets after the // function ends, and the position of the empty slot is returned // // failure_key_duplicated -- Found a duplicate key, locks will be held, and // the position of the duplicate key will be returned // // failure_under_expansion -- Failed due to a concurrent expansion // operation. Locks are released. No meaningful position is returned. // // failure_table_full -- Failed to find an empty slot for the table. Locks // are released. No meaningful position is returned. template table_position cuckoo_insert(const hash_value hv, TwoBuckets &b, K &key) { int res1, res2; bucket &b1 = buckets_[b.i1]; if (!try_find_insert_bucket(b1, res1, hv.partial, key)) { return table_position{b.i1, static_cast(res1), failure_key_duplicated}; } bucket &b2 = buckets_[b.i2]; if (!try_find_insert_bucket(b2, res2, hv.partial, key)) { return table_position{b.i2, static_cast(res2), failure_key_duplicated}; } if (res1 != -1) { return table_position{b.i1, static_cast(res1), ok}; } if (res2 != -1) { return table_position{b.i2, static_cast(res2), ok}; } // We are unlucky, so let's perform cuckoo hashing. size_type insert_bucket = 0; size_type insert_slot = 0; cuckoo_status st = run_cuckoo(b, insert_bucket, insert_slot); if (st == failure_under_expansion) { // The run_cuckoo operation operated on an old version of the table, // so we have to try again. We signal to the calling insert method // to try again by returning failure_under_expansion. return table_position{0, 0, failure_under_expansion}; } else if (st == ok) { assert(TABLE_MODE() == locked_table_mode() || !get_current_locks()[lock_ind(b.i1)].try_lock()); assert(TABLE_MODE() == locked_table_mode() || !get_current_locks()[lock_ind(b.i2)].try_lock()); assert(!buckets_[insert_bucket].occupied(insert_slot)); assert(insert_bucket == index_hash(hashpower(), hv.hash) || insert_bucket == alt_index(hashpower(), hv.partial, index_hash(hashpower(), hv.hash))); // Since we unlocked the buckets during run_cuckoo, another insert // could have inserted the same key into either b.i1 or // b.i2, so we check for that before doing the insert. table_position pos = cuckoo_find(key, hv.partial, b.i1, b.i2); if (pos.status == ok) { pos.status = failure_key_duplicated; return pos; } return table_position{insert_bucket, insert_slot, ok}; } assert(st == failure); LIBCUCKOO_DBG("hash table is full (hashpower = %zu, hash_items = %zu," "load factor = %.2f), need to increase hashpower\n", hashpower(), size(), load_factor()); return table_position{0, 0, failure_table_full}; } // add_to_bucket will insert the given key-value pair into the slot. The key // and value will be move-constructed into the table, so they are not valid // for use afterwards. template void add_to_bucket(const size_type bucket_ind, const size_type slot, const partial_t partial, K &&key, Args &&... val) { buckets_.setKV(bucket_ind, slot, partial, std::forward(key), std::forward(val)...); ++get_current_locks()[lock_ind(bucket_ind)].elem_counter(); } // try_find_insert_bucket will search the bucket for the given key, and for // an empty slot. If the key is found, we store the slot of the key in // `slot` and return false. If we find an empty slot, we store its position // in `slot` and return true. If no duplicate key is found and no empty slot // is found, we store -1 in `slot` and return true. template bool try_find_insert_bucket(const bucket &b, int &slot, const partial_t partial, const K &key) const { // Silence a warning from MSVC about partial being unused if is_simple. (void)partial; slot = -1; for (int i = 0; i < static_cast(slot_per_bucket()); ++i) { if (b.occupied(i)) { if (!is_simple() && partial != b.partial(i)) { continue; } if (key_eq()(b.key(i), key)) { slot = i; return false; } } else { slot = i; } } return true; } // CuckooRecord holds one position in a cuckoo path. Since cuckoopath // elements only define a sequence of alternate hashings for different hash // values, we only need to keep track of the hash values being moved, rather // than the keys themselves. typedef struct { size_type bucket; size_type slot; hash_value hv; } CuckooRecord; // The maximum number of items in a cuckoo BFS path. It determines the // maximum number of slots we search when cuckooing. static constexpr uint8_t MAX_BFS_PATH_LEN = 5; // An array of CuckooRecords using CuckooRecords = std::array; // run_cuckoo performs cuckoo hashing on the table in an attempt to free up // a slot on either of the insert buckets, which are assumed to be locked // before the start. On success, the bucket and slot that was freed up is // stored in insert_bucket and insert_slot. In order to perform the search // and the swaps, it has to release the locks, which can lead to certain // concurrency issues, the details of which are explained in the function. // If run_cuckoo returns ok (success), then `b` will be active, otherwise it // will not. template cuckoo_status run_cuckoo(TwoBuckets &b, size_type &insert_bucket, size_type &insert_slot) { // We must unlock the buckets here, so that cuckoopath_search and // cuckoopath_move can lock buckets as desired without deadlock. // cuckoopath_move has to move something out of one of the original // buckets as its last operation, and it will lock both buckets and // leave them locked after finishing. This way, we know that if // cuckoopath_move succeeds, then the buckets needed for insertion are // still locked. If cuckoopath_move fails, the buckets are unlocked and // we try again. This unlocking does present two problems. The first is // that another insert on the same key runs and, finding that the key // isn't in the table, inserts the key into the table. Then we insert // the key into the table, causing a duplication. To check for this, we // search the buckets for the key we are trying to insert before doing // so (this is done in cuckoo_insert, and requires that both buckets are // locked). Another problem is that an expansion runs and changes the // hashpower, meaning the buckets may not be valid anymore. In this // case, the cuckoopath functions will have thrown a hashpower_changed // exception, which we catch and handle here. size_type hp = hashpower(); b.unlock(); CuckooRecords cuckoo_path; bool done = false; try { while (!done) { const int depth = cuckoopath_search(hp, cuckoo_path, b.i1, b.i2); if (depth < 0) { break; } if (cuckoopath_move(hp, cuckoo_path, depth, b)) { insert_bucket = cuckoo_path[0].bucket; insert_slot = cuckoo_path[0].slot; assert(insert_bucket == b.i1 || insert_bucket == b.i2); assert(TABLE_MODE() == locked_table_mode() || !get_current_locks()[lock_ind(b.i1)].try_lock()); assert(TABLE_MODE() == locked_table_mode() || !get_current_locks()[lock_ind(b.i2)].try_lock()); assert(!buckets_[insert_bucket].occupied(insert_slot)); done = true; break; } } } catch (hashpower_changed &) { // The hashpower changed while we were trying to cuckoo, which means // we want to retry. b.i1 and b.i2 should not be locked // in this case. return failure_under_expansion; } return done ? ok : failure; } // cuckoopath_search finds a cuckoo path from one of the starting buckets to // an empty slot in another bucket. It returns the depth of the discovered // cuckoo path on success, and -1 on failure. Since it doesn't take locks on // the buckets it searches, the data can change between this function and // cuckoopath_move. Thus cuckoopath_move checks that the data matches the // cuckoo path before changing it. // // throws hashpower_changed if it changed during the search. template int cuckoopath_search(const size_type hp, CuckooRecords &cuckoo_path, const size_type i1, const size_type i2) { b_slot x = slot_search(hp, i1, i2); if (x.depth == -1) { return -1; } // Fill in the cuckoo path slots from the end to the beginning. for (int i = x.depth; i >= 0; i--) { cuckoo_path[i].slot = x.pathcode % slot_per_bucket(); x.pathcode /= slot_per_bucket(); } // Fill in the cuckoo_path buckets and keys from the beginning to the // end, using the final pathcode to figure out which bucket the path // starts on. Since data could have been modified between slot_search // and the computation of the cuckoo path, this could be an invalid // cuckoo_path. CuckooRecord &first = cuckoo_path[0]; if (x.pathcode == 0) { first.bucket = i1; } else { assert(x.pathcode == 1); first.bucket = i2; } { const auto lock_manager = lock_one(hp, first.bucket, TABLE_MODE()); const bucket &b = buckets_[first.bucket]; if (!b.occupied(first.slot)) { // We can terminate here return 0; } first.hv = hashed_key(b.key(first.slot)); } for (int i = 1; i <= x.depth; ++i) { CuckooRecord &curr = cuckoo_path[i]; const CuckooRecord &prev = cuckoo_path[i - 1]; assert(prev.bucket == index_hash(hp, prev.hv.hash) || prev.bucket == alt_index(hp, prev.hv.partial, index_hash(hp, prev.hv.hash))); // We get the bucket that this slot is on by computing the alternate // index of the previous bucket curr.bucket = alt_index(hp, prev.hv.partial, prev.bucket); const auto lock_manager = lock_one(hp, curr.bucket, TABLE_MODE()); const bucket &b = buckets_[curr.bucket]; if (!b.occupied(curr.slot)) { // We can terminate here return i; } curr.hv = hashed_key(b.key(curr.slot)); } return x.depth; } // cuckoopath_move moves keys along the given cuckoo path in order to make // an empty slot in one of the buckets in cuckoo_insert. Before the start of // this function, the two insert-locked buckets were unlocked in run_cuckoo. // At the end of the function, if the function returns true (success), then // both insert-locked buckets remain locked. If the function is // unsuccessful, then both insert-locked buckets will be unlocked. // // throws hashpower_changed if it changed during the move. template bool cuckoopath_move(const size_type hp, CuckooRecords &cuckoo_path, size_type depth, TwoBuckets &b) { if (depth == 0) { // There is a chance that depth == 0, when try_add_to_bucket sees // both buckets as full and cuckoopath_search finds one empty. In // this case, we lock both buckets. If the slot that // cuckoopath_search found empty isn't empty anymore, we unlock them // and return false. Otherwise, the bucket is empty and insertable, // so we hold the locks and return true. const size_type bucket_i = cuckoo_path[0].bucket; assert(bucket_i == b.i1 || bucket_i == b.i2); b = lock_two(hp, b.i1, b.i2, TABLE_MODE()); if (!buckets_[bucket_i].occupied(cuckoo_path[0].slot)) { return true; } else { b.unlock(); return false; } } while (depth > 0) { CuckooRecord &from = cuckoo_path[depth - 1]; CuckooRecord &to = cuckoo_path[depth]; const size_type fs = from.slot; const size_type ts = to.slot; TwoBuckets twob; LockManager extra_manager; if (depth == 1) { // Even though we are only swapping out of one of the original // buckets, we have to lock both of them along with the slot we // are swapping to, since at the end of this function, they both // must be locked. We store tb inside the extrab container so it // is unlocked at the end of the loop. std::tie(twob, extra_manager) = lock_three(hp, b.i1, b.i2, to.bucket, TABLE_MODE()); } else { twob = lock_two(hp, from.bucket, to.bucket, TABLE_MODE()); } bucket &fb = buckets_[from.bucket]; bucket &tb = buckets_[to.bucket]; // We plan to kick out fs, but let's check if it is still there; // there's a small chance we've gotten scooped by a later cuckoo. If // that happened, just... try again. Also the slot we are filling in // may have already been filled in by another thread, or the slot we // are moving from may be empty, both of which invalidate the swap. // We only need to check that the hash value is the same, because, // even if the keys are different and have the same hash value, then // the cuckoopath is still valid. if (tb.occupied(ts) || !fb.occupied(fs) || hashed_key_only_hash(fb.key(fs)) != from.hv.hash) { return false; } buckets_.setKV(to.bucket, ts, fb.partial(fs), fb.movable_key(fs), std::move(fb.mapped(fs))); buckets_.eraseKV(from.bucket, fs); if (depth == 1) { // Hold onto the locks contained in twob b = std::move(twob); } depth--; } return true; } // A constexpr version of pow that we can use for various compile-time // constants and checks. static constexpr size_type const_pow(size_type a, size_type b) { return (b == 0) ? 1 : a * const_pow(a, b - 1); } // b_slot holds the information for a BFS path through the table. struct b_slot { // The bucket of the last item in the path. size_type bucket; // a compressed representation of the slots for each of the buckets in // the path. pathcode is sort of like a base-slot_per_bucket number, and // we need to hold at most MAX_BFS_PATH_LEN slots. Thus we need the // maximum pathcode to be at least slot_per_bucket()^(MAX_BFS_PATH_LEN). uint16_t pathcode; static_assert(const_pow(slot_per_bucket(), MAX_BFS_PATH_LEN) < std::numeric_limits::max(), "pathcode may not be large enough to encode a cuckoo " "path"); // The 0-indexed position in the cuckoo path this slot occupies. It must // be less than MAX_BFS_PATH_LEN, and also able to hold negative values. int8_t depth; static_assert(MAX_BFS_PATH_LEN - 1 <= std::numeric_limits::max(), "The depth type must able to hold a value of" " MAX_BFS_PATH_LEN - 1"); static_assert(-1 >= std::numeric_limits::min(), "The depth type must be able to hold a value of -1"); b_slot() {} b_slot(const size_type b, const uint16_t p, const decltype(depth) d) : bucket(b), pathcode(p), depth(d) { assert(d < MAX_BFS_PATH_LEN); } }; // b_queue is the queue used to store b_slots for BFS cuckoo hashing. class b_queue { public: b_queue() noexcept : first_(0), last_(0) {} void enqueue(b_slot x) { assert(!full()); slots_[last_++] = x; } b_slot dequeue() { assert(!empty()); assert(first_ < last_); b_slot &x = slots_[first_++]; return x; } bool empty() const { return first_ == last_; } bool full() const { return last_ == MAX_CUCKOO_COUNT; } private: // The size of the BFS queue. It holds just enough elements to fulfill a // MAX_BFS_PATH_LEN search for two starting buckets, with no circular // wrapping-around. For one bucket, this is the geometric sum // sum_{k=0}^{MAX_BFS_PATH_LEN-1} slot_per_bucket()^k // = (1 - slot_per_bucket()^MAX_BFS_PATH_LEN) / (1 - slot_per_bucket()) // // Note that if slot_per_bucket() == 1, then this simply equals // MAX_BFS_PATH_LEN. static_assert(slot_per_bucket() > 0, "SLOT_PER_BUCKET must be greater than 0."); static constexpr size_type MAX_CUCKOO_COUNT = 2 * ((slot_per_bucket() == 1) ? MAX_BFS_PATH_LEN : (const_pow(slot_per_bucket(), MAX_BFS_PATH_LEN) - 1) / (slot_per_bucket() - 1)); // An array of b_slots. Since we allocate just enough space to complete a // full search, we should never exceed the end of the array. b_slot slots_[MAX_CUCKOO_COUNT]; // The index of the head of the queue in the array size_type first_; // One past the index of the last_ item of the queue in the array. size_type last_; }; // slot_search searches for a cuckoo path using breadth-first search. It // starts with the i1 and i2 buckets, and, until it finds a bucket with an // empty slot, adds each slot of the bucket in the b_slot. If the queue runs // out of space, it fails. // // throws hashpower_changed if it changed during the search template b_slot slot_search(const size_type hp, const size_type i1, const size_type i2) { b_queue q; // The initial pathcode informs cuckoopath_search which bucket the path // starts on q.enqueue(b_slot(i1, 0, 0)); q.enqueue(b_slot(i2, 1, 0)); while (!q.empty()) { b_slot x = q.dequeue(); auto lock_manager = lock_one(hp, x.bucket, TABLE_MODE()); bucket &b = buckets_[x.bucket]; // Picks a (sort-of) random slot to start from size_type starting_slot = x.pathcode % slot_per_bucket(); for (size_type i = 0; i < slot_per_bucket(); ++i) { uint16_t slot = (starting_slot + i) % slot_per_bucket(); if (!b.occupied(slot)) { // We can terminate the search here x.pathcode = x.pathcode * slot_per_bucket() + slot; return x; } // If x has less than the maximum number of path components, // create a new b_slot item, that represents the bucket we would // have come from if we kicked out the item at this slot. const partial_t partial = b.partial(slot); if (x.depth < MAX_BFS_PATH_LEN - 1) { assert(!q.full()); b_slot y(alt_index(hp, partial, x.bucket), x.pathcode * slot_per_bucket() + slot, x.depth + 1); q.enqueue(y); } } } // We didn't find a short-enough cuckoo path, so the search terminated. // Return a failure value. return b_slot(0, 0, -1); } // cuckoo_fast_double will double the size of the table by taking advantage // of the properties of index_hash and alt_index. If the key's move // constructor is not noexcept, we use cuckoo_expand_simple, since that // provides a strong exception guarantee. template cuckoo_status cuckoo_fast_double(size_type current_hp) { if (!is_data_nothrow_move_constructible()) { LIBCUCKOO_DBG("%s", "cannot run cuckoo_fast_double because key-value" " pair is not nothrow move constructible"); return cuckoo_expand_simple(current_hp + 1); } const size_type new_hp = current_hp + 1; auto all_locks_manager = lock_all(TABLE_MODE()); cuckoo_status st = check_resize_validity(current_hp, new_hp); if (st != ok) { return st; } // Finish rehashing any un-rehashed buckets, so that we can move out any // remaining data in old_buckets_. We should be running cuckoo_fast_double // only after trying to cuckoo for a while, which should mean we've tried // going through most of the table and thus done a lot of rehashing // already. So this shouldn't be too expensive. // // We restrict ourselves to the current thread because we want to avoid // possibly spawning extra threads in this function, unless the // circumstances are predictable (i.e. data is nothrow move constructible, // we're in locked_table mode and must keep the buckets_ container // up-to-date, etc). // // If we have fewer than kNumLocks buckets, there shouldn't be any buckets // left to rehash, so this should be a no-op. { locks_t ¤t_locks = get_current_locks(); for (size_t i = 0; i < current_locks.size(); ++i) { rehash_lock(i); } num_remaining_lazy_rehash_locks(0); } // Resize the locks array if necessary. This is done before we update the // hashpower so that other threads don't grab the new hashpower and the old // locks. maybe_resize_locks(size_type(1) << new_hp); locks_t ¤t_locks = get_current_locks(); // Move the current buckets into old_buckets_, and create a new empty // buckets container, which will become the new current one. The // old_buckets_ data will be destroyed when move-assigning to buckets_. old_buckets_.swap(buckets_); buckets_ = buckets_t(new_hp, get_allocator()); // If we have less than kMaxNumLocks buckets, we do a full rehash in the // current thread. On-demand rehashing wouldn't be very easy with less than // kMaxNumLocks buckets, because it would require taking extra lower-index // locks to do the rehashing. Because kMaxNumLocks is relatively small, // this should not be very expensive. We have already set all locks to // migrated at the start of the function, so we shouldn't have to touch // them again. // // Otherwise, if we're in locked_table_mode, the expectation is that we can // access the latest data in buckets_ without taking any locks. So we must // rehash the data immediately. This would not be much different from // lazy-rehashing in locked_table_mode anyways, because it would still be // going on in one thread. if (old_buckets_.size() < kMaxNumLocks) { for (size_type i = 0; i < old_buckets_.size(); ++i) { move_bucket(old_buckets_, buckets_, i); } // This will also delete the old_buckets_ data. num_remaining_lazy_rehash_locks(0); } else { // Mark all current locks as un-migrated, so that we rehash the data // on-demand when the locks are taken. for (spinlock &lock : current_locks) { lock.is_migrated() = false; } num_remaining_lazy_rehash_locks(current_locks.size()); if (std::is_same::value) { rehash_with_workers(); } } return ok; } void move_bucket(buckets_t &old_buckets, buckets_t &new_buckets, size_type old_bucket_ind) const noexcept { const size_t old_hp = old_buckets.hashpower(); const size_t new_hp = new_buckets.hashpower(); // By doubling the table size, the index_hash and alt_index of each key got // one bit added to the top, at position old_hp, which means anything we // have to move will either be at the same bucket position, or exactly // hashsize(old_hp) later than the current bucket. bucket &old_bucket = old_buckets_[old_bucket_ind]; const size_type new_bucket_ind = old_bucket_ind + hashsize(old_hp); size_type new_bucket_slot = 0; // For each occupied slot, either move it into its same position in the // new buckets container, or to the first available spot in the new // bucket in the new buckets container. for (size_type old_bucket_slot = 0; old_bucket_slot < slot_per_bucket(); ++old_bucket_slot) { if (!old_bucket.occupied(old_bucket_slot)) { continue; } const hash_value hv = hashed_key(old_bucket.key(old_bucket_slot)); const size_type old_ihash = index_hash(old_hp, hv.hash); const size_type old_ahash = alt_index(old_hp, hv.partial, old_ihash); const size_type new_ihash = index_hash(new_hp, hv.hash); const size_type new_ahash = alt_index(new_hp, hv.partial, new_ihash); size_type dst_bucket_ind, dst_bucket_slot; if ((old_bucket_ind == old_ihash && new_ihash == new_bucket_ind) || (old_bucket_ind == old_ahash && new_ahash == new_bucket_ind)) { // We're moving the key to the new bucket dst_bucket_ind = new_bucket_ind; dst_bucket_slot = new_bucket_slot++; } else { // We're moving the key to the old bucket assert((old_bucket_ind == old_ihash && new_ihash == old_ihash) || (old_bucket_ind == old_ahash && new_ahash == old_ahash)); dst_bucket_ind = old_bucket_ind; dst_bucket_slot = old_bucket_slot; } new_buckets.setKV(dst_bucket_ind, dst_bucket_slot++, old_bucket.partial(old_bucket_slot), old_bucket.movable_key(old_bucket_slot), std::move(old_bucket.mapped(old_bucket_slot))); } } // Checks whether the resize is okay to proceed. Returns a status code, or // throws an exception, depending on the error type. using automatic_resize = std::integral_constant; using manual_resize = std::integral_constant; template cuckoo_status check_resize_validity(const size_type orig_hp, const size_type new_hp) { const size_type mhp = maximum_hashpower(); if (mhp != NO_MAXIMUM_HASHPOWER && new_hp > mhp) { throw maximum_hashpower_exceeded(new_hp); } if (AUTO_RESIZE::value && load_factor() < minimum_load_factor()) { throw load_factor_too_low(minimum_load_factor()); } if (hashpower() != orig_hp) { // Most likely another expansion ran before this one could grab the // locks LIBCUCKOO_DBG("%s", "another expansion is on-going\n"); return failure_under_expansion; } return ok; } // When we expand the contanier, we may need to expand the locks array, if // the current locks array is smaller than the maximum size and also smaller // than the number of buckets in the upcoming buckets container. In this // case, we grow the locks array to the smaller of the maximum lock array // size and the bucket count. This is done by allocating an entirely new lock // container, taking all the locks, copying over the counters, and then // finally adding it to the end of `all_locks_`, thereby designating it the // "current" locks container. It is the responsibility of the caller to // unlock all locks taken, including the new locks, whenever it is done with // them, so that old threads can resume and potentially re-start. void maybe_resize_locks(size_type new_bucket_count) { locks_t ¤t_locks = get_current_locks(); if (!(current_locks.size() < kMaxNumLocks && current_locks.size() < new_bucket_count)) { return; } locks_t new_locks(get_allocator()); new_locks.resize(std::min(size_type(kMaxNumLocks), new_bucket_count)); assert(new_locks.size() > current_locks.size()); std::copy(current_locks.begin(), current_locks.end(), new_locks.begin()); for (spinlock &lock : new_locks) { lock.lock(); } all_locks_.emplace_back(std::move(new_locks)); } // cuckoo_expand_simple will resize the table to at least the given // new_hashpower. When we're shrinking the table, if the current table // contains more elements than can be held by new_hashpower, the resulting // hashpower will be greater than `new_hp`. It needs to take all the bucket // locks, since no other operations can change the table during expansion. // Throws maximum_hashpower_exceeded if we're expanding beyond the // maximum hashpower, and we have an actual limit. template cuckoo_status cuckoo_expand_simple(size_type new_hp) { auto all_locks_manager = lock_all(TABLE_MODE()); const size_type hp = hashpower(); cuckoo_status st = check_resize_validity(hp, new_hp); if (st != ok) { return st; } // Finish rehashing any data into buckets_. rehash_with_workers(); // Creates a new hash table with hashpower new_hp and adds all the elements // from buckets_ and old_buckets_. Allow this map to spawn extra threads if // it needs to resize during the resize. cuckoohash_map new_map(hashsize(new_hp) * slot_per_bucket(), hash_function(), key_eq(), get_allocator()); new_map.max_num_worker_threads(max_num_worker_threads()); parallel_exec( 0, hashsize(hp), [this, &new_map] (size_type i, size_type end, std::exception_ptr &eptr) { try { for (; i < end; ++i) { auto &bucket = buckets_[i]; for (size_type j = 0; j < slot_per_bucket(); ++j) { if (bucket.occupied(j)) { new_map.insert(bucket.movable_key(j), std::move(bucket.mapped(j))); } } } } catch (...) { eptr = std::current_exception(); } }); // Finish rehashing any data in new_map. new_map.rehash_with_workers(); // Swap the buckets_ container with new_map's. This is okay, because we // have all the locks, so nobody else should be reading from the buckets // array. Then the old buckets will be deleted when new_map is deleted. maybe_resize_locks(new_map.bucket_count()); buckets_.swap(new_map.buckets_); return ok; } // Executes the function over the given range, splitting the work between the // current thread and any available worker threads. // // In the noexcept version, the functor must implement operator()(size_type // start, size_type end). // // In the non-noexcept version, the functor will receive an additional // std::exception_ptr& argument. template void parallel_exec_noexcept(size_type start, size_type end, F func) { const size_type num_extra_threads = max_num_worker_threads(); const size_type num_workers = 1 + num_extra_threads; size_type work_per_thread = (end - start) / num_workers; std::vector> threads( get_allocator()); threads.reserve(num_extra_threads); for (size_type i = 0; i < num_extra_threads; ++i) { threads.emplace_back(func, start, start + work_per_thread); start += work_per_thread; } func(start, end); for (std::thread &t : threads) { t.join(); } } template void parallel_exec(size_type start, size_type end, F func) { const size_type num_extra_threads = max_num_worker_threads(); const size_type num_workers = 1 + num_extra_threads; size_type work_per_thread = (end - start) / num_workers; std::vector> threads( get_allocator()); threads.reserve(num_extra_threads); std::vector> eptrs( num_workers, nullptr, get_allocator()); for (size_type i = 0; i < num_extra_threads; ++i) { threads.emplace_back(func, start, start + work_per_thread, std::ref(eptrs[i])); start += work_per_thread; } func(start, end, std::ref(eptrs.back())); for (std::thread &t : threads) { t.join(); } for (std::exception_ptr &eptr : eptrs) { if (eptr) std::rethrow_exception(eptr); } } // Does a batch resize of the remaining data in old_buckets_. Assumes all the // locks have already been taken. void rehash_with_workers() noexcept { locks_t ¤t_locks = get_current_locks(); parallel_exec_noexcept( 0, current_locks.size(), [this](size_type start, size_type end) { for (size_type i = start; i < end; ++i) { rehash_lock(i); } }); num_remaining_lazy_rehash_locks(0); } // Deletion functions // Removes an item from a bucket, decrementing the associated counter as // well. void del_from_bucket(const size_type bucket_ind, const size_type slot) { buckets_.eraseKV(bucket_ind, slot); --get_current_locks()[lock_ind(bucket_ind)].elem_counter(); } // Empties the table, calling the destructors of all the elements it removes // from the table. It assumes the locks are taken as necessary. void cuckoo_clear() { buckets_.clear(); // This will also clear out any data in old_buckets and delete it, if we // haven't already. num_remaining_lazy_rehash_locks(0); for (spinlock &lock : get_current_locks()) { lock.elem_counter() = 0; lock.is_migrated() = true; } } // Rehashing functions template bool cuckoo_rehash(size_type n) { const size_type hp = hashpower(); if (n == hp) { return false; } return cuckoo_expand_simple(n) == ok; } template bool cuckoo_reserve(size_type n) { const size_type hp = hashpower(); const size_type new_hp = reserve_calc(n); if (new_hp == hp) { return false; } return cuckoo_expand_simple(new_hp) == ok; } // Miscellaneous functions // reserve_calc takes in a parameter specifying a certain number of slots // for a table and returns the smallest hashpower that will hold n elements. static size_type reserve_calc(const size_type n) { const size_type buckets = (n + slot_per_bucket() - 1) / slot_per_bucket(); size_type blog2; for (blog2 = 0; (size_type(1) << blog2) < buckets; ++blog2) ; assert(n <= buckets * slot_per_bucket() && buckets <= hashsize(blog2)); return blog2; } // This class is a friend for unit testing friend class UnitTestInternalAccess; static constexpr size_type kMaxNumLocks = 1UL << 16; locks_t &get_current_locks() const { return all_locks_.back(); } // Get/set/decrement num remaining lazy rehash locks. If we reach 0 remaining // lazy locks, we can deallocate the memory in old_buckets_. size_type num_remaining_lazy_rehash_locks() const { return num_remaining_lazy_rehash_locks_.load( std::memory_order_acquire); } void num_remaining_lazy_rehash_locks(size_type n) const { num_remaining_lazy_rehash_locks_.store( n, std::memory_order_release); if (n == 0) { old_buckets_.clear_and_deallocate(); } } void decrement_num_remaining_lazy_rehash_locks() const { size_type old_num_remaining = num_remaining_lazy_rehash_locks_.fetch_sub( 1, std::memory_order_acq_rel); assert(old_num_remaining >= 1); if (old_num_remaining == 1) { old_buckets_.clear_and_deallocate(); } } // Member variables // The hash function hasher hash_fn_; // The equality function key_equal eq_fn_; // container of buckets. The size or memory location of the buckets cannot be // changed unless all the locks are taken on the table. Thus, it is only safe // to access the buckets_ container when you have at least one lock held. // // Marked mutable so that const methods can rehash into this container when // necessary. mutable buckets_t buckets_; // An old container of buckets, containing data that may not have been // rehashed into the current one. If valid, this will always have a hashpower // exactly one less than the one in buckets_. // // Marked mutable so that const methods can rehash into this container when // necessary. mutable buckets_t old_buckets_; // A linked list of all lock containers. We never discard lock containers, // since there is currently no mechanism for detecting when all threads are // done looking at the memory. The back lock container in this list is // designated the "current" one, and is used by all operations taking locks. // This container can be modified if either it is empty (which should only // occur during construction), or if the modifying thread has taken all the // locks on the existing "current" container. In the latter case, a // modification must take place before a modification to the hashpower, so // that other threads can detect the change and adjust appropriately. Marked // mutable so that const methods can access and take locks. mutable all_locks_t all_locks_; // A small wrapper around std::atomic to make it copyable for constructors. template class CopyableAtomic : public std::atomic { public: using std::atomic::atomic; CopyableAtomic(const CopyableAtomic& other) noexcept : CopyableAtomic(other.load(std::memory_order_acquire)) {} CopyableAtomic& operator=(const CopyableAtomic& other) noexcept { this->store(other.load(std::memory_order_acquire), std::memory_order_release); return *this; } }; // We keep track of the number of remaining locks in the latest locks array, // that remain to be rehashed. Once this reaches 0, we can free the memory of // the old buckets. It should only be accessed or modified when // lazy-rehashing a lock, so not in the common case. // // Marked mutable so that we can modify this during rehashing. mutable CopyableAtomic num_remaining_lazy_rehash_locks_; // Stores the minimum load factor allowed for automatic expansions. Whenever // an automatic expansion is triggered (during an insertion where cuckoo // hashing fails, for example), we check the load factor against this // double, and throw an exception if it's lower than this value. It can be // used to signal when the hash function is bad or the input adversarial. CopyableAtomic minimum_load_factor_; // stores the maximum hashpower allowed for any expansions. If set to // NO_MAXIMUM_HASHPOWER, this limit will be disregarded. CopyableAtomic maximum_hashpower_; // Maximum number of extra threads to spawn when doing any large batch // operations. CopyableAtomic max_num_worker_threads_; public: /** * An ownership wrapper around a @ref cuckoohash_map table instance. When * given a table instance, it takes all the locks on the table, blocking all * outside operations on the table. Because the locked_table has unique * ownership of the table, it can provide a set of operations on the table * that aren't possible in a concurrent context. * * The locked_table interface is very similar to the STL unordered_map * interface, and for functions whose signatures correspond to unordered_map * methods, the behavior should be mostly the same. */ class locked_table { public: /** @name Type Declarations */ /**@{*/ using key_type = typename cuckoohash_map::key_type; using mapped_type = typename cuckoohash_map::mapped_type; using value_type = typename cuckoohash_map::value_type; using size_type = typename cuckoohash_map::size_type; using difference_type = typename cuckoohash_map::difference_type; using hasher = typename cuckoohash_map::hasher; using key_equal = typename cuckoohash_map::key_equal; using allocator_type = typename cuckoohash_map::allocator_type; using reference = typename cuckoohash_map::reference; using const_reference = typename cuckoohash_map::const_reference; using pointer = typename cuckoohash_map::pointer; using const_pointer = typename cuckoohash_map::const_pointer; /** * A constant iterator over a @ref locked_table, which allows read-only * access to the elements of the table. It fulfills the * BidirectionalIterator concept. */ class const_iterator { public: using difference_type = typename locked_table::difference_type; using value_type = typename locked_table::value_type; using pointer = typename locked_table::const_pointer; using reference = typename locked_table::const_reference; using iterator_category = std::bidirectional_iterator_tag; const_iterator() {} // Return true if the iterators are from the same locked table and // location, false otherwise. bool operator==(const const_iterator &it) const { return buckets_ == it.buckets_ && index_ == it.index_ && slot_ == it.slot_; } bool operator!=(const const_iterator &it) const { return !(operator==(it)); } reference operator*() const { return (*buckets_)[index_].kvpair(slot_); } pointer operator->() const { return std::addressof(operator*()); } // Advance the iterator to the next item in the table, or to the end // of the table. Returns the iterator at its new position. const_iterator &operator++() { // Move forward until we get to a slot that is occupied, or we // get to the end ++slot_; for (; index_ < buckets_->size(); ++index_) { for (; slot_ < slot_per_bucket(); ++slot_) { if ((*buckets_)[index_].occupied(slot_)) { return *this; } } slot_ = 0; } assert(std::make_pair(index_, slot_) == end_pos(*buckets_)); return *this; } // Advance the iterator to the next item in the table, or to the end // of the table. Returns the iterator at its old position. const_iterator operator++(int) { const_iterator old(*this); ++(*this); return old; } // Move the iterator back to the previous item in the table. Returns // the iterator at its new position. const_iterator &operator--() { // Move backward until we get to the beginning. Behavior is // undefined if we are iterating at the first element, so we can // assume we'll reach an element. This means we'll never reach // index_ == 0 and slot_ == 0. if (slot_ == 0) { --index_; slot_ = slot_per_bucket() - 1; } else { --slot_; } while (!(*buckets_)[index_].occupied(slot_)) { if (slot_ == 0) { --index_; slot_ = slot_per_bucket() - 1; } else { --slot_; } } return *this; } //! Move the iterator back to the previous item in the table. //! Returns the iterator at its old position. Behavior is undefined //! if the iterator is at the beginning. const_iterator operator--(int) { const_iterator old(*this); --(*this); return old; } protected: // The buckets owned by the locked table being iterated over. Even // though const_iterator cannot modify the buckets, we don't mark // them const so that the mutable iterator can derive from this // class. Also, since iterators should be default constructible, // copyable, and movable, we have to make this a raw pointer type. buckets_t *buckets_; // The bucket index of the item being pointed to. For implementation // convenience, we let it take on negative values. size_type index_; // The slot in the bucket of the item being pointed to. For // implementation convenience, we let it take on negative values. size_type slot_; // Returns the position signifying the end of the table static std::pair end_pos(const buckets_t &buckets) { return std::make_pair(buckets.size(), 0); } // The private constructor is used by locked_table to create // iterators from scratch. If the given index_-slot_ pair is at the // end of the table, or the given spot is occupied, stay. Otherwise, // step forward to the next data item, or to the end of the table. const_iterator(buckets_t &buckets, size_type index, size_type slot) noexcept : buckets_(std::addressof(buckets)), index_(index), slot_(slot) { if (std::make_pair(index_, slot_) != end_pos(*buckets_) && !(*buckets_)[index_].occupied(slot_)) { operator++(); } } friend class locked_table; }; /** * An iterator over a @ref locked_table, which allows read-write access * to elements of the table. It fulfills the BidirectionalIterator * concept. */ class iterator : public const_iterator { public: using pointer = typename cuckoohash_map::pointer; using reference = typename cuckoohash_map::reference; iterator() {} bool operator==(const iterator &it) const { return const_iterator::operator==(it); } bool operator!=(const iterator &it) const { return const_iterator::operator!=(it); } reference operator*() { return (*const_iterator::buckets_)[const_iterator::index_].kvpair( const_iterator::slot_); } pointer operator->() { return std::addressof(operator*()); } iterator &operator++() { const_iterator::operator++(); return *this; } iterator operator++(int) { iterator old(*this); const_iterator::operator++(); return old; } iterator &operator--() { const_iterator::operator--(); return *this; } iterator operator--(int) { iterator old(*this); const_iterator::operator--(); return old; } private: iterator(buckets_t &buckets, size_type index, size_type slot) noexcept : const_iterator(buckets, index, slot) {} friend class locked_table; }; /**@}*/ /** @name Table Parameters */ /**@{*/ static constexpr size_type slot_per_bucket() { return cuckoohash_map::slot_per_bucket(); } /**@}*/ /** @name Constructors, Destructors, and Assignment */ /**@{*/ locked_table() = delete; locked_table(const locked_table &) = delete; locked_table &operator=(const locked_table &) = delete; locked_table(locked_table &<) noexcept : map_(std::move(lt.map_)), all_locks_manager_(std::move(lt.all_locks_manager_)) {} locked_table &operator=(locked_table &<) noexcept { unlock(); map_ = std::move(lt.map_); all_locks_manager_ = std::move(lt.all_locks_manager_); return *this; } /** * Unlocks the table, thereby freeing the locks on the table, but also * invalidating all iterators and table operations with this object. It * is idempotent. */ void unlock() { all_locks_manager_.reset(); } /**@}*/ /** @name Table Details * * Methods for getting information about the table. Many are identical * to their @ref cuckoohash_map counterparts. Only new functions or * those with different behavior are documented. * */ /**@{*/ /** * Returns whether the locked table has ownership of the table * * @return true if it still has ownership, false otherwise */ bool is_active() const { return static_cast(all_locks_manager_); } hasher hash_function() const { return map_.get().hash_function(); } key_equal key_eq() const { return map_.get().key_eq(); } allocator_type get_allocator() const { return map_.get().get_allocator(); } size_type hashpower() const { return map_.get().hashpower(); } size_type bucket_count() const { return map_.get().bucket_count(); } bool empty() const { return map_.get().empty(); } size_type size() const { return map_.get().size(); } size_type capacity() const { return map_.get().capacity(); } double load_factor() const { return map_.get().load_factor(); } void minimum_load_factor(const double mlf) { map_.get().minimum_load_factor(mlf); } double minimum_load_factor() const { return map_.get().minimum_load_factor(); } void maximum_hashpower(size_type mhp) { map_.get().maximum_hashpower(mhp); } size_type maximum_hashpower() const { return map_.get().maximum_hashpower(); } void max_num_worker_threads(size_type extra_threads) { map_.get().max_num_worker_threads(extra_threads); } size_type max_num_worker_threads() const { return map_.get().max_num_worker_threads(); } /**@}*/ /** @name Iterators */ /**@{*/ /** * Returns an iterator to the beginning of the table. If the table is * empty, it will point past the end of the table. * * @return an iterator to the beginning of the table */ iterator begin() { return iterator(map_.get().buckets_, 0, 0); } const_iterator begin() const { return const_iterator(map_.get().buckets_, 0, 0); } const_iterator cbegin() const { return begin(); } /** * Returns an iterator past the end of the table. * * @return an iterator past the end of the table */ iterator end() { const auto end_pos = const_iterator::end_pos(map_.get().buckets_); return iterator(map_.get().buckets_, static_cast(end_pos.first), static_cast(end_pos.second)); } const_iterator end() const { const auto end_pos = const_iterator::end_pos(map_.get().buckets_); return const_iterator(map_.get().buckets_, static_cast(end_pos.first), static_cast(end_pos.second)); } const_iterator cend() const { return end(); } /**@}*/ /** @name Modifiers */ /**@{*/ void clear() { map_.get().cuckoo_clear(); } /** * This behaves like the @c unordered_map::try_emplace method. It will * always invalidate all iterators, due to the possibilities of cuckoo * hashing and expansion. */ template std::pair insert(K &&key, Args &&... val) { hash_value hv = map_.get().hashed_key(key); auto b = map_.get().template snapshot_and_lock_two(hv); table_position pos = map_.get().template cuckoo_insert_loop(hv, b, key); if (pos.status == ok) { map_.get().add_to_bucket(pos.index, pos.slot, hv.partial, std::forward(key), std::forward(val)...); } else { assert(pos.status == failure_key_duplicated); } return std::make_pair(iterator(map_.get().buckets_, pos.index, pos.slot), pos.status == ok); } iterator erase(const_iterator pos) { map_.get().del_from_bucket(pos.index_, pos.slot_); return iterator(map_.get().buckets_, pos.index_, pos.slot_); } iterator erase(iterator pos) { map_.get().del_from_bucket(pos.index_, pos.slot_); return iterator(map_.get().buckets_, pos.index_, pos.slot_); } template size_type erase(const K &key) { const hash_value hv = map_.get().hashed_key(key); const auto b = map_.get().template snapshot_and_lock_two(hv); const table_position pos = map_.get().cuckoo_find(key, hv.partial, b.i1, b.i2); if (pos.status == ok) { map_.get().del_from_bucket(pos.index, pos.slot); return 1; } else { return 0; } } /**@}*/ /** @name Lookup */ /**@{*/ template iterator find(const K &key) { const hash_value hv = map_.get().hashed_key(key); const auto b = map_.get().template snapshot_and_lock_two(hv); const table_position pos = map_.get().cuckoo_find(key, hv.partial, b.i1, b.i2); if (pos.status == ok) { return iterator(map_.get().buckets_, pos.index, pos.slot); } else { return end(); } } template const_iterator find(const K &key) const { const hash_value hv = map_.get().hashed_key(key); const auto b = map_.get().template snapshot_and_lock_two(hv); const table_position pos = map_.get().cuckoo_find(key, hv.partial, b.i1, b.i2); if (pos.status == ok) { return const_iterator(map_.get().buckets_, pos.index, pos.slot); } else { return end(); } } template mapped_type &at(const K &key) { auto it = find(key); if (it == end()) { throw std::out_of_range("key not found in table"); } else { return it->second; } } template const mapped_type &at(const K &key) const { auto it = find(key); if (it == end()) { throw std::out_of_range("key not found in table"); } else { return it->second; } } /** * This function has the same lifetime properties as @ref * cuckoohash_map::insert, except that the value is default-constructed, * with no parameters, if it is not already in the table. */ template T &operator[](K &&key) { auto result = insert(std::forward(key)); return result.first->second; } template size_type count(const K &key) const { const hash_value hv = map_.get().hashed_key(key); const auto b = map_.get().template snapshot_and_lock_two(hv); return map_.get().cuckoo_find(key, hv.partial, b.i1, b.i2).status == ok ? 1 : 0; } template std::pair equal_range(const K &key) { auto it = find(key); if (it == end()) { return std::make_pair(it, it); } else { auto start_it = it++; return std::make_pair(start_it, it); } } template std::pair equal_range(const K &key) const { auto it = find(key); if (it == end()) { return std::make_pair(it, it); } else { auto start_it = it++; return std::make_pair(start_it, it); } } /**@}*/ /** @name Re-sizing */ /**@{*/ /** * This has the same behavior as @ref cuckoohash_map::rehash, except * that we don't return anything. */ void rehash(size_type n) { map_.get().template cuckoo_rehash(n); } /** * This has the same behavior as @ref cuckoohash_map::reserve, except * that we don't return anything. */ void reserve(size_type n) { map_.get().template cuckoo_reserve(n); } /**@}*/ /** @name Comparison */ /**@{*/ bool operator==(const locked_table <) const { if (size() != lt.size()) { return false; } for (const auto &elem : lt) { auto it = find(elem.first); if (it == end() || it->second != elem.second) { return false; } } return true; } bool operator!=(const locked_table <) const { if (size() != lt.size()) { return true; } for (const auto &elem : lt) { auto it = find(elem.first); if (it == end() || it->second != elem.second) { return true; } } return false; } /**@}*/ private: // The constructor locks the entire table. We keep this constructor private // (but expose it to the cuckoohash_map class), since we don't want users // calling it. We also complete any remaining rehashing in the table, so // that everything is in map.buckets_. locked_table(cuckoohash_map &map) noexcept : map_(map), all_locks_manager_(map.lock_all(normal_mode())) { map.rehash_with_workers(); } // Dispatchers for methods on cuckoohash_map buckets_t &buckets() { return map_.get().buckets_; } const buckets_t &buckets() const { return map_.get().buckets_; } void maybe_resize_locks(size_type new_bucket_count) { map_.get().maybe_resize_locks(new_bucket_count); } locks_t &get_current_locks() { return map_.get().get_current_locks(); } // A reference to the map owned by the table std::reference_wrapper map_; // A manager for all the locks we took on the table. AllLocksManager all_locks_manager_; friend class cuckoohash_map; friend std::ostream &operator<<(std::ostream &os, const locked_table <) { os << lt.buckets(); size_type size = lt.size(); os.write(reinterpret_cast(&size), sizeof(size_type)); double mlf = lt.minimum_load_factor(); size_type mhp = lt.maximum_hashpower(); os.write(reinterpret_cast(&mlf), sizeof(double)); os.write(reinterpret_cast(&mhp), sizeof(size_type)); return os; } friend std::istream &operator>>(std::istream &is, locked_table <) { is >> lt.buckets(); // Re-size the locks, and set the size to the stored size lt.maybe_resize_locks(lt.bucket_count()); for (auto &lock : lt.get_current_locks()) { lock.elem_counter() = 0; } size_type size; is.read(reinterpret_cast(&size), sizeof(size_type)); if (size > 0) { lt.get_current_locks()[0].elem_counter() = size; } double mlf; size_type mhp; is.read(reinterpret_cast(&mlf), sizeof(double)); is.read(reinterpret_cast(&mhp), sizeof(size_type)); lt.minimum_load_factor(mlf); lt.maximum_hashpower(mhp); return is; } }; }; /** * Specializes the @c std::swap algorithm for @c cuckoohash_map. Calls @c * lhs.swap(rhs). * * @param lhs the map on the left side to swap * @param lhs the map on the right side to swap */ template void swap( cuckoohash_map &lhs, cuckoohash_map &rhs) noexcept { lhs.swap(rhs); } } // namespace libcuckoo #endif // _CUCKOOHASH_MAP_HH libcuckoo-0.3.1/libcuckoo/cuckoohash_util.hh000066400000000000000000000100661426042121400211430ustar00rootroot00000000000000/** \file */ #ifndef _CUCKOOHASH_UTIL_HH #define _CUCKOOHASH_UTIL_HH #include "cuckoohash_config.hh" // for LIBCUCKOO_DEBUG #include #include #include #include namespace libcuckoo { #if LIBCUCKOO_DEBUG //! When \ref LIBCUCKOO_DEBUG is 0, LIBCUCKOO_DBG will printing out status //! messages in various situations #define LIBCUCKOO_DBG(fmt, ...) \ fprintf(stderr, "\x1b[32m" \ "[libcuckoo:%s:%d:%lu] " fmt "" \ "\x1b[0m", \ __FILE__, __LINE__, \ std::hash()(std::this_thread::get_id()), \ __VA_ARGS__) #else //! When \ref LIBCUCKOO_DEBUG is 0, LIBCUCKOO_DBG does nothing #define LIBCUCKOO_DBG(fmt, ...) \ do { \ } while (0) #endif /** * alignas() requires GCC >= 4.9, so we stick with the alignment attribute for * GCC. */ #ifdef __GNUC__ #define LIBCUCKOO_ALIGNAS(x) __attribute__((aligned(x))) #else #define LIBCUCKOO_ALIGNAS(x) alignas(x) #endif /** * At higher warning levels, MSVC produces an annoying warning that alignment * may cause wasted space: "structure was padded due to __declspec(align())". */ #ifdef _MSC_VER #define LIBCUCKOO_SQUELCH_PADDING_WARNING __pragma(warning(suppress : 4324)) #else #define LIBCUCKOO_SQUELCH_PADDING_WARNING #endif /** * At higher warning levels, MSVC may issue a deadcode warning which depends on * the template arguments given. For certain other template arguments, the code * is not really "dead". */ #ifdef _MSC_VER #define LIBCUCKOO_SQUELCH_DEADCODE_WARNING_BEGIN \ do { \ __pragma(warning(push)); \ __pragma(warning(disable : 4702)) \ } while (0) #define LIBCUCKOO_SQUELCH_DEADCODE_WARNING_END __pragma(warning(pop)) #else #define LIBCUCKOO_SQUELCH_DEADCODE_WARNING_BEGIN #define LIBCUCKOO_SQUELCH_DEADCODE_WARNING_END #endif /** * Thrown when an automatic expansion is triggered, but the load factor of the * table is below a minimum threshold, which can be set by the \ref * cuckoohash_map::minimum_load_factor method. This can happen if the hash * function does not properly distribute keys, or for certain adversarial * workloads. */ class load_factor_too_low : public std::exception { public: /** * Constructor * * @param lf the load factor of the table when the exception was thrown */ load_factor_too_low(const double lf) noexcept : load_factor_(lf) {} /** * @return a descriptive error message */ virtual const char *what() const noexcept override { return "Automatic expansion triggered when load factor was below " "minimum threshold"; } /** * @return the load factor of the table when the exception was thrown */ double load_factor() const noexcept { return load_factor_; } private: const double load_factor_; }; /** * Thrown when an expansion is triggered, but the hashpower specified is greater * than the maximum, which can be set with the \ref * cuckoohash_map::maximum_hashpower method. */ class maximum_hashpower_exceeded : public std::exception { public: /** * Constructor * * @param hp the hash power we were trying to expand to */ maximum_hashpower_exceeded(const size_t hp) noexcept : hashpower_(hp) {} /** * @return a descriptive error message */ virtual const char *what() const noexcept override { return "Expansion beyond maximum hashpower"; } /** * @return the hashpower we were trying to expand to */ size_t hashpower() const noexcept { return hashpower_; } private: const size_t hashpower_; }; } // namespace libcuckoo #endif // _CUCKOOHASH_UTIL_HH libcuckoo-0.3.1/libcuckoo/libcuckoo-config.cmake000066400000000000000000000002761426042121400216570ustar00rootroot00000000000000# # libcuckoo-config.cmake # include (CMakeFindDependencyMacro) set(THREADS_PREFER_PTHREAD_FLAG ON) find_dependency(Threads) include ("${CMAKE_CURRENT_LIST_DIR}/libcuckoo-targets.cmake") libcuckoo-0.3.1/libcuckoo/mainpage.dox000066400000000000000000000012451426042121400177320ustar00rootroot00000000000000/*! \mainpage libcuckoo Documentation * * libcuckoo is a high-performance, memory efficient hash table that * supports concurrent reads and writes. * * \ref cuckoohash_map is the class of the hash table. Its interface * resembles that of STL's unordered_map but does contain some * important differences. * * Internally, the hash table is partitioned into an array of * buckets, each of which contains \c SLOT_PER_BUCKET slots to * store items. * * Each bucket has a lock to ensure multiple threads don't modify the * same elements. Most operations will lock no more than two buckets * at a time, thereby allowing for concurrent reads and writes. */ libcuckoo-0.3.1/tests/000077500000000000000000000000001426042121400146235ustar00rootroot00000000000000libcuckoo-0.3.1/tests/CMakeLists.txt000066400000000000000000000017011426042121400173620ustar00rootroot00000000000000add_library(test_util INTERFACE) target_include_directories(test_util INTERFACE $ $ ) # put these in the cache so they show up in ccmake option (BUILD_TESTS "build all libcuckoo tests") option (BUILD_STRESS_TESTS "build the stress tests") option (BUILD_UNIT_TESTS "build the unit tests") option (BUILD_UNIVERSAL_BENCHMARK "build the universal benchmark and associated tests") # Add pcg if we're doing stress tests or universal benchmark if (BUILD_TESTS OR BUILD_STRESS_TESTS OR BUILD_UNIVERSAL_BENCHMARK) add_subdirectory(pcg) endif() # Add catch for unit tests if (BUILD_TESTS OR BUILD_UNIT_TESTS) add_subdirectory(Catch) add_subdirectory(unit-tests) endif() if (BUILD_TESTS OR BUILD_STRESS_TESTS) add_subdirectory(stress-tests) endif() if (BUILD_TESTS OR BUILD_UNIVERSAL_BENCHMARK) add_subdirectory(universal-benchmark) endif() libcuckoo-0.3.1/tests/Catch/000077500000000000000000000000001426042121400156455ustar00rootroot00000000000000libcuckoo-0.3.1/tests/Catch/CMakeLists.txt000066400000000000000000000002131426042121400204010ustar00rootroot00000000000000add_library(catch INTERFACE) target_include_directories(catch INTERFACE $ )libcuckoo-0.3.1/tests/Catch/LICENSE_1_0.txt000066400000000000000000000024721426042121400201340ustar00rootroot00000000000000Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. libcuckoo-0.3.1/tests/Catch/README.md000066400000000000000000000021621426042121400171250ustar00rootroot00000000000000![catch logo](catch-logo-small.png) *v1.5.9* Build status (on Travis CI) [![Build Status](https://travis-ci.org/philsquared/Catch.png)](https://travis-ci.org/philsquared/Catch) The latest, single header, version can be downloaded directly using this link ## What's the Catch? Catch stands for C++ Automated Test Cases in Headers and is a multi-paradigm automated test framework for C++ and Objective-C (and, maybe, C). It is implemented entirely in a set of header files, but is packaged up as a single header for extra convenience. ## How to use it This documentation comprises these three parts: * [Why do we need yet another C++ Test Framework?](docs/why-catch.md) * [Tutorial](docs/tutorial.md) - getting started * [Reference section](docs/Readme.md) - all the details ## More * Issues and bugs can be raised on the [Issue tracker on GitHub](https://github.com/philsquared/Catch/issues) * For discussion or questions please use [the dedicated Google Groups forum](https://groups.google.com/forum/?fromgroups#!forum/catch-forum) libcuckoo-0.3.1/tests/Catch/single_include/000077500000000000000000000000001426042121400206315ustar00rootroot00000000000000libcuckoo-0.3.1/tests/Catch/single_include/catch.hpp000066400000000000000000013453121426042121400224350ustar00rootroot00000000000000/* * Catch v1.5.9 * Generated: 2016-11-29 12:14:38.049276 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_CATCH_HPP_INCLUDED #ifdef __clang__ # pragma clang system_header #elif defined __GNUC__ # pragma GCC system_header #endif // #included from: internal/catch_suppress_warnings.h #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC # pragma clang diagnostic ignored "-Wglobal-constructors" # pragma clang diagnostic ignored "-Wvariadic-macros" # pragma clang diagnostic ignored "-Wc99-extensions" # pragma clang diagnostic ignored "-Wunused-variable" # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" # pragma clang diagnostic ignored "-Wc++98-compat" # pragma clang diagnostic ignored "-Wc++98-compat-pedantic" # pragma clang diagnostic ignored "-Wswitch-enum" # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ # pragma GCC diagnostic ignored "-Wvariadic-macros" # pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wpadded" #endif #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL #endif #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED # define CLARA_CONFIG_MAIN # endif #endif // #included from: internal/catch_notimplemented_exception.h #define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED // #included from: catch_common.h #define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) #ifdef CATCH_CONFIG_COUNTER # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) #else # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) #endif #define INTERNAL_CATCH_STRINGIFY2( expr ) #expr #define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) #include #include #include // #included from: catch_compiler_capabilities.h #define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED // Detect a number of compiler features - mostly C++11/14 conformance - by compiler // The following features are defined: // // CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? // CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? // CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods // CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? // CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported // CATCH_CONFIG_CPP11_LONG_LONG : is long long supported? // CATCH_CONFIG_CPP11_OVERRIDE : is override supported? // CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) // CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? // CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? // CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too // **************** // In general each macro has a _NO_ form // (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. // All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 #ifdef __cplusplus # if __cplusplus >= 201103L # define CATCH_CPP11_OR_GREATER # endif # if __cplusplus >= 201402L # define CATCH_CPP14_OR_GREATER # endif #endif #ifdef __clang__ # if __has_feature(cxx_nullptr) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # if __has_feature(cxx_noexcept) # define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # endif # if defined(CATCH_CPP11_OR_GREATER) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) # endif #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // Borland #ifdef __BORLANDC__ #endif // __BORLANDC__ //////////////////////////////////////////////////////////////////////////////// // EDG #ifdef __EDG_VERSION__ #endif // __EDG_VERSION__ //////////////////////////////////////////////////////////////////////////////// // Digital Mars #ifdef __DMC__ #endif // __DMC__ //////////////////////////////////////////////////////////////////////////////// // GCC #ifdef __GNUC__ # if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) && defined(CATCH_CPP11_OR_GREATER) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" ) # endif // - otherwise more recent versions define __cplusplus >= 201103L // and will get picked up below #endif // __GNUC__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #ifdef _MSC_VER #if (_MSC_VER >= 1600) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) #define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT #define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #endif #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // Use variadic macros if the compiler supports them #if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ ( defined __GNUC__ && __GNUC__ >= 3 ) || \ ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) #define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS #endif // Use __COUNTER__ if the compiler supports it #if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \ ( defined __GNUC__ && __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 ) || \ ( defined __clang__ && __clang_major__ >= 3 ) #define CATCH_INTERNAL_CONFIG_COUNTER #endif //////////////////////////////////////////////////////////////////////////////// // C++ language feature support // catch all support for C++11 #if defined(CATCH_CPP11_OR_GREATER) # if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS # define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM # define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE # define CATCH_INTERNAL_CONFIG_CPP11_TUPLE # endif # ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS # define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) # define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) # define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) # define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR # endif #endif // __cplusplus >= 201103L // Now set the actual defines based on the above + anything the user has configured #if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_NULLPTR #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_NOEXCEPT #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_GENERATED_METHODS #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_IS_ENUM #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_TUPLE #endif #if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) # define CATCH_CONFIG_VARIADIC_MACROS #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_LONG_LONG #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_OVERRIDE #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_UNIQUE_PTR #endif #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) # define CATCH_CONFIG_COUNTER #endif #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS #endif // noexcept support: #if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) # define CATCH_NOEXCEPT noexcept # define CATCH_NOEXCEPT_IS(x) noexcept(x) #else # define CATCH_NOEXCEPT throw() # define CATCH_NOEXCEPT_IS(x) #endif // nullptr support #ifdef CATCH_CONFIG_CPP11_NULLPTR # define CATCH_NULL nullptr #else # define CATCH_NULL NULL #endif // override support #ifdef CATCH_CONFIG_CPP11_OVERRIDE # define CATCH_OVERRIDE override #else # define CATCH_OVERRIDE #endif // unique_ptr support #ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR # define CATCH_AUTO_PTR( T ) std::unique_ptr #else # define CATCH_AUTO_PTR( T ) std::auto_ptr #endif namespace Catch { struct IConfig; struct CaseSensitive { enum Choice { Yes, No }; }; class NonCopyable { #ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable& operator = ( NonCopyable const& ) = delete; NonCopyable& operator = ( NonCopyable && ) = delete; #else NonCopyable( NonCopyable const& info ); NonCopyable& operator = ( NonCopyable const& ); #endif protected: NonCopyable() {} virtual ~NonCopyable(); }; class SafeBool { public: typedef void (SafeBool::*type)() const; static type makeSafe( bool value ) { return value ? &SafeBool::trueValue : 0; } private: void trueValue() const {} }; template inline void deleteAll( ContainerT& container ) { typename ContainerT::const_iterator it = container.begin(); typename ContainerT::const_iterator itEnd = container.end(); for(; it != itEnd; ++it ) delete *it; } template inline void deleteAllValues( AssociativeContainerT& container ) { typename AssociativeContainerT::const_iterator it = container.begin(); typename AssociativeContainerT::const_iterator itEnd = container.end(); for(; it != itEnd; ++it ) delete it->second; } bool startsWith( std::string const& s, std::string const& prefix ); bool endsWith( std::string const& s, std::string const& suffix ); bool contains( std::string const& s, std::string const& infix ); void toLowerInPlace( std::string& s ); std::string toLower( std::string const& s ); std::string trim( std::string const& str ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); struct pluralise { pluralise( std::size_t count, std::string const& label ); friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); std::size_t m_count; std::string m_label; }; struct SourceLineInfo { SourceLineInfo(); SourceLineInfo( char const* _file, std::size_t _line ); SourceLineInfo( SourceLineInfo const& other ); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS SourceLineInfo( SourceLineInfo && ) = default; SourceLineInfo& operator = ( SourceLineInfo const& ) = default; SourceLineInfo& operator = ( SourceLineInfo && ) = default; # endif bool empty() const; bool operator == ( SourceLineInfo const& other ) const; bool operator < ( SourceLineInfo const& other ) const; std::string file; std::size_t line; }; std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); // This is just here to avoid compiler warnings with macro constants and boolean literals inline bool isTrue( bool value ){ return value; } inline bool alwaysTrue() { return true; } inline bool alwaysFalse() { return false; } void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); void seedRng( IConfig const& config ); unsigned int rngSeed(); // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as // >> stuff +StreamEndStop struct StreamEndStop { std::string operator+() { return std::string(); } }; template T const& operator + ( T const& value, StreamEndStop ) { return value; } } #define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) #define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); #include namespace Catch { class NotImplementedException : public std::exception { public: NotImplementedException( SourceLineInfo const& lineInfo ); NotImplementedException( NotImplementedException const& ) {} virtual ~NotImplementedException() CATCH_NOEXCEPT {} virtual const char* what() const CATCH_NOEXCEPT; private: std::string m_what; SourceLineInfo m_lineInfo; }; } // end namespace Catch /////////////////////////////////////////////////////////////////////////////// #define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) // #included from: internal/catch_context.h #define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED // #included from: catch_interfaces_generators.h #define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED #include namespace Catch { struct IGeneratorInfo { virtual ~IGeneratorInfo(); virtual bool moveNext() = 0; virtual std::size_t getCurrentIndex() const = 0; }; struct IGeneratorsForTest { virtual ~IGeneratorsForTest(); virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; virtual bool moveNext() = 0; }; IGeneratorsForTest* createGeneratorsForTest(); } // end namespace Catch // #included from: catch_ptr.hpp #define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif namespace Catch { // An intrusive reference counting smart pointer. // T must implement addRef() and release() methods // typically implementing the IShared interface template class Ptr { public: Ptr() : m_p( CATCH_NULL ){} Ptr( T* p ) : m_p( p ){ if( m_p ) m_p->addRef(); } Ptr( Ptr const& other ) : m_p( other.m_p ){ if( m_p ) m_p->addRef(); } ~Ptr(){ if( m_p ) m_p->release(); } void reset() { if( m_p ) m_p->release(); m_p = CATCH_NULL; } Ptr& operator = ( T* p ){ Ptr temp( p ); swap( temp ); return *this; } Ptr& operator = ( Ptr const& other ){ Ptr temp( other ); swap( temp ); return *this; } void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } T* get() const{ return m_p; } T& operator*() const { return *m_p; } T* operator->() const { return m_p; } bool operator !() const { return m_p == CATCH_NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); } private: T* m_p; }; struct IShared : NonCopyable { virtual ~IShared(); virtual void addRef() const = 0; virtual void release() const = 0; }; template struct SharedImpl : T { SharedImpl() : m_rc( 0 ){} virtual void addRef() const { ++m_rc; } virtual void release() const { if( --m_rc == 0 ) delete this; } mutable unsigned int m_rc; }; } // end namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif #include #include #include namespace Catch { class TestCase; class Stream; struct IResultCapture; struct IRunner; struct IGeneratorsForTest; struct IConfig; struct IContext { virtual ~IContext(); virtual IResultCapture* getResultCapture() = 0; virtual IRunner* getRunner() = 0; virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; virtual bool advanceGeneratorsForCurrentTest() = 0; virtual Ptr getConfig() const = 0; }; struct IMutableContext : IContext { virtual ~IMutableContext(); virtual void setResultCapture( IResultCapture* resultCapture ) = 0; virtual void setRunner( IRunner* runner ) = 0; virtual void setConfig( Ptr const& config ) = 0; }; IContext& getCurrentContext(); IMutableContext& getCurrentMutableContext(); void cleanUpContext(); Stream createStream( std::string const& streamName ); } // #included from: internal/catch_test_registry.hpp #define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED // #included from: catch_interfaces_testcase.h #define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED #include namespace Catch { class TestSpec; struct ITestCase : IShared { virtual void invoke () const = 0; protected: virtual ~ITestCase(); }; class TestCase; struct IConfig; struct ITestCaseRegistry { virtual ~ITestCaseRegistry(); virtual std::vector const& getAllTests() const = 0; virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); std::vector const& getAllTestCasesSorted( IConfig const& config ); } namespace Catch { template class MethodTestCase : public SharedImpl { public: MethodTestCase( void (C::*method)() ) : m_method( method ) {} virtual void invoke() const { C obj; (obj.*m_method)(); } private: virtual ~MethodTestCase() {} void (C::*m_method)(); }; typedef void(*TestFunction)(); struct NameAndDesc { NameAndDesc( const char* _name = "", const char* _description= "" ) : name( _name ), description( _description ) {} const char* name; const char* description; }; void registerTestCase ( ITestCase* testCase, char const* className, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ); struct AutoReg { AutoReg ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ); template AutoReg ( void (C::*method)(), char const* className, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ) { registerTestCase ( new MethodTestCase( method ), className, nameAndDesc, lineInfo ); } ~AutoReg(); private: AutoReg( AutoReg const& ); void operator= ( AutoReg const& ); }; void registerTestCaseFunction ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ); } // end namespace Catch #ifdef CATCH_CONFIG_VARIADIC_MACROS /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ static void TestName(); \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ namespace{ \ struct TestName : ClassName{ \ void test(); \ }; \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ } \ void TestName::test() #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); #else /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \ static void TestName(); \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ static void TestName() #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\ namespace{ \ struct TestCaseName : ClassName{ \ void test(); \ }; \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ } \ void TestCaseName::test() #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); #endif // #included from: internal/catch_capture.hpp #define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED // #included from: catch_result_builder.h #define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED // #included from: catch_result_type.h #define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED namespace Catch { // ResultWas::OfType enum struct ResultWas { enum OfType { Unknown = -1, Ok = 0, Info = 1, Warning = 2, FailureBit = 0x10, ExpressionFailed = FailureBit | 1, ExplicitFailure = FailureBit | 2, Exception = 0x100 | FailureBit, ThrewException = Exception | 1, DidntThrowException = Exception | 2, FatalErrorCondition = 0x200 | FailureBit }; }; inline bool isOk( ResultWas::OfType resultType ) { return ( resultType & ResultWas::FailureBit ) == 0; } inline bool isJustInfo( int flags ) { return flags == ResultWas::Info; } // ResultDisposition::Flags enum struct ResultDisposition { enum Flags { Normal = 0x01, ContinueOnFailure = 0x02, // Failures fail test, but execution continues FalseTest = 0x04, // Prefix expression with ! SuppressFail = 0x08 // Failures are reported but do not fail the test }; }; inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { return static_cast( static_cast( lhs ) | static_cast( rhs ) ); } inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } } // end namespace Catch // #included from: catch_assertionresult.h #define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED #include namespace Catch { struct AssertionInfo { AssertionInfo() {} AssertionInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, std::string const& _capturedExpression, ResultDisposition::Flags _resultDisposition ); std::string macroName; SourceLineInfo lineInfo; std::string capturedExpression; ResultDisposition::Flags resultDisposition; }; struct AssertionResultData { AssertionResultData() : resultType( ResultWas::Unknown ) {} std::string reconstructedExpression; std::string message; ResultWas::OfType resultType; }; class AssertionResult { public: AssertionResult(); AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); ~AssertionResult(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS AssertionResult( AssertionResult const& ) = default; AssertionResult( AssertionResult && ) = default; AssertionResult& operator = ( AssertionResult const& ) = default; AssertionResult& operator = ( AssertionResult && ) = default; # endif bool isOk() const; bool succeeded() const; ResultWas::OfType getResultType() const; bool hasExpression() const; bool hasMessage() const; std::string getExpression() const; std::string getExpressionInMacro() const; bool hasExpandedExpression() const; std::string getExpandedExpression() const; std::string getMessage() const; SourceLineInfo getSourceInfo() const; std::string getTestMacroName() const; protected: AssertionInfo m_info; AssertionResultData m_resultData; }; } // end namespace Catch // #included from: catch_matchers.hpp #define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED namespace Catch { namespace Matchers { namespace Impl { namespace Generic { template class AllOf; template class AnyOf; template class Not; } template struct Matcher : SharedImpl { typedef ExpressionT ExpressionType; virtual ~Matcher() {} virtual Ptr clone() const = 0; virtual bool match( ExpressionT const& expr ) const = 0; virtual std::string toString() const = 0; Generic::AllOf operator && ( Matcher const& other ) const; Generic::AnyOf operator || ( Matcher const& other ) const; Generic::Not operator ! () const; }; template struct MatcherImpl : Matcher { virtual Ptr > clone() const { return Ptr >( new DerivedT( static_cast( *this ) ) ); } }; namespace Generic { template class Not : public MatcherImpl, ExpressionT> { public: explicit Not( Matcher const& matcher ) : m_matcher(matcher.clone()) {} Not( Not const& other ) : m_matcher( other.m_matcher ) {} virtual bool match( ExpressionT const& expr ) const CATCH_OVERRIDE { return !m_matcher->match( expr ); } virtual std::string toString() const CATCH_OVERRIDE { return "not " + m_matcher->toString(); } private: Ptr< Matcher > m_matcher; }; template class AllOf : public MatcherImpl, ExpressionT> { public: AllOf() {} AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} AllOf& add( Matcher const& matcher ) { m_matchers.push_back( matcher.clone() ); return *this; } virtual bool match( ExpressionT const& expr ) const { for( std::size_t i = 0; i < m_matchers.size(); ++i ) if( !m_matchers[i]->match( expr ) ) return false; return true; } virtual std::string toString() const { std::ostringstream oss; oss << "( "; for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if( i != 0 ) oss << " and "; oss << m_matchers[i]->toString(); } oss << " )"; return oss.str(); } AllOf operator && ( Matcher const& other ) const { AllOf allOfExpr( *this ); allOfExpr.add( other ); return allOfExpr; } private: std::vector > > m_matchers; }; template class AnyOf : public MatcherImpl, ExpressionT> { public: AnyOf() {} AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} AnyOf& add( Matcher const& matcher ) { m_matchers.push_back( matcher.clone() ); return *this; } virtual bool match( ExpressionT const& expr ) const { for( std::size_t i = 0; i < m_matchers.size(); ++i ) if( m_matchers[i]->match( expr ) ) return true; return false; } virtual std::string toString() const { std::ostringstream oss; oss << "( "; for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if( i != 0 ) oss << " or "; oss << m_matchers[i]->toString(); } oss << " )"; return oss.str(); } AnyOf operator || ( Matcher const& other ) const { AnyOf anyOfExpr( *this ); anyOfExpr.add( other ); return anyOfExpr; } private: std::vector > > m_matchers; }; } // namespace Generic template Generic::AllOf Matcher::operator && ( Matcher const& other ) const { Generic::AllOf allOfExpr; allOfExpr.add( *this ); allOfExpr.add( other ); return allOfExpr; } template Generic::AnyOf Matcher::operator || ( Matcher const& other ) const { Generic::AnyOf anyOfExpr; anyOfExpr.add( *this ); anyOfExpr.add( other ); return anyOfExpr; } template Generic::Not Matcher::operator ! () const { return Generic::Not( *this ); } namespace StdString { inline std::string makeString( std::string const& str ) { return str; } inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } struct CasedString { CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) : m_caseSensitivity( caseSensitivity ), m_str( adjustString( str ) ) {} std::string adjustString( std::string const& str ) const { return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; } std::string toStringSuffix() const { return m_caseSensitivity == CaseSensitive::No ? " (case insensitive)" : ""; } CaseSensitive::Choice m_caseSensitivity; std::string m_str; }; struct Equals : MatcherImpl { Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) : m_data( str, caseSensitivity ) {} Equals( Equals const& other ) : m_data( other.m_data ){} virtual ~Equals(); virtual bool match( std::string const& expr ) const { return m_data.m_str == m_data.adjustString( expr );; } virtual std::string toString() const { return "equals: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); } CasedString m_data; }; struct Contains : MatcherImpl { Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) : m_data( substr, caseSensitivity ){} Contains( Contains const& other ) : m_data( other.m_data ){} virtual ~Contains(); virtual bool match( std::string const& expr ) const { return m_data.adjustString( expr ).find( m_data.m_str ) != std::string::npos; } virtual std::string toString() const { return "contains: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); } CasedString m_data; }; struct StartsWith : MatcherImpl { StartsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) : m_data( substr, caseSensitivity ){} StartsWith( StartsWith const& other ) : m_data( other.m_data ){} virtual ~StartsWith(); virtual bool match( std::string const& expr ) const { return startsWith( m_data.adjustString( expr ), m_data.m_str ); } virtual std::string toString() const { return "starts with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); } CasedString m_data; }; struct EndsWith : MatcherImpl { EndsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) : m_data( substr, caseSensitivity ){} EndsWith( EndsWith const& other ) : m_data( other.m_data ){} virtual ~EndsWith(); virtual bool match( std::string const& expr ) const { return endsWith( m_data.adjustString( expr ), m_data.m_str ); } virtual std::string toString() const { return "ends with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); } CasedString m_data; }; } // namespace StdString } // namespace Impl // The following functions create the actual matcher objects. // This allows the types to be inferred template inline Impl::Generic::Not Not( Impl::Matcher const& m ) { return Impl::Generic::Not( m ); } template inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, Impl::Matcher const& m2 ) { return Impl::Generic::AllOf().add( m1 ).add( m2 ); } template inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, Impl::Matcher const& m2, Impl::Matcher const& m3 ) { return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); } template inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, Impl::Matcher const& m2 ) { return Impl::Generic::AnyOf().add( m1 ).add( m2 ); } template inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, Impl::Matcher const& m2, Impl::Matcher const& m3 ) { return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); } inline Impl::StdString::Equals Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { return Impl::StdString::Equals( str, caseSensitivity ); } inline Impl::StdString::Equals Equals( const char* str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { return Impl::StdString::Equals( Impl::StdString::makeString( str ), caseSensitivity ); } inline Impl::StdString::Contains Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { return Impl::StdString::Contains( substr, caseSensitivity ); } inline Impl::StdString::Contains Contains( const char* substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { return Impl::StdString::Contains( Impl::StdString::makeString( substr ), caseSensitivity ); } inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { return Impl::StdString::StartsWith( substr ); } inline Impl::StdString::StartsWith StartsWith( const char* substr ) { return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); } inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { return Impl::StdString::EndsWith( substr ); } inline Impl::StdString::EndsWith EndsWith( const char* substr ) { return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); } } // namespace Matchers using namespace Matchers; } // namespace Catch namespace Catch { struct TestFailureException{}; template class ExpressionLhs; struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; struct CopyableStream { CopyableStream() {} CopyableStream( CopyableStream const& other ) { oss << other.oss.str(); } CopyableStream& operator=( CopyableStream const& other ) { oss.str(""); oss << other.oss.str(); return *this; } std::ostringstream oss; }; class ResultBuilder { public: ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, ResultDisposition::Flags resultDisposition, char const* secondArg = "" ); template ExpressionLhs operator <= ( T const& operand ); ExpressionLhs operator <= ( bool value ); template ResultBuilder& operator << ( T const& value ) { m_stream.oss << value; return *this; } template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); ResultBuilder& setResultType( ResultWas::OfType result ); ResultBuilder& setResultType( bool result ); ResultBuilder& setLhs( std::string const& lhs ); ResultBuilder& setRhs( std::string const& rhs ); ResultBuilder& setOp( std::string const& op ); void endExpression(); std::string reconstructExpression() const; AssertionResult build() const; void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); void captureResult( ResultWas::OfType resultType ); void captureExpression(); void captureExpectedException( std::string const& expectedMessage ); void captureExpectedException( Matchers::Impl::Matcher const& matcher ); void handleResult( AssertionResult const& result ); void react(); bool shouldDebugBreak() const; bool allowThrows() const; private: AssertionInfo m_assertionInfo; AssertionResultData m_data; struct ExprComponents { ExprComponents() : testFalse( false ) {} bool testFalse; std::string lhs, rhs, op; } m_exprComponents; CopyableStream m_stream; bool m_shouldDebugBreak; bool m_shouldThrow; }; } // namespace Catch // Include after due to circular dependency: // #included from: catch_expression_lhs.hpp #define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED // #included from: catch_evaluate.hpp #define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4389) // '==' : signed/unsigned mismatch #endif #include namespace Catch { namespace Internal { enum Operator { IsEqualTo, IsNotEqualTo, IsLessThan, IsGreaterThan, IsLessThanOrEqualTo, IsGreaterThanOrEqualTo }; template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; template inline T& opCast(T const& t) { return const_cast(t); } // nullptr_t support based on pull request #154 from Konstantin Baumann #ifdef CATCH_CONFIG_CPP11_NULLPTR inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } #endif // CATCH_CONFIG_CPP11_NULLPTR // So the compare overloads can be operator agnostic we convey the operator as a template // enum, which is used to specialise an Evaluator for doing the comparison. template class Evaluator{}; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs) { return bool( opCast( lhs ) == opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) != opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) < opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) > opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) >= opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool( opCast( lhs ) <= opCast( rhs ) ); } }; template bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { return Evaluator::evaluate( lhs, rhs ); } // This level of indirection allows us to specialise for integer types // to avoid signed/ unsigned warnings // "base" overload template bool compare( T1 const& lhs, T2 const& rhs ) { return Evaluator::evaluate( lhs, rhs ); } // unsigned X to int template bool compare( unsigned int lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned long lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned char lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } // unsigned X to long template bool compare( unsigned int lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned long lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned char lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } // int to unsigned X template bool compare( int lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( int lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( int lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // long to unsigned X template bool compare( long lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // pointer to long (when comparing against NULL) template bool compare( long lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, long rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } // pointer to int (when comparing against NULL) template bool compare( int lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, int rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } #ifdef CATCH_CONFIG_CPP11_LONG_LONG // long long to unsigned X template bool compare( long long lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long long lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long long lhs, unsigned long long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long long lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // unsigned long long to X template bool compare( unsigned long long lhs, int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( unsigned long long lhs, long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( unsigned long long lhs, long long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( unsigned long long lhs, char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // pointer to long long (when comparing against NULL) template bool compare( long long lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, long long rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } #endif // CATCH_CONFIG_CPP11_LONG_LONG #ifdef CATCH_CONFIG_CPP11_NULLPTR // pointer to nullptr_t (when comparing against nullptr) template bool compare( std::nullptr_t, T* rhs ) { return Evaluator::evaluate( nullptr, rhs ); } template bool compare( T* lhs, std::nullptr_t ) { return Evaluator::evaluate( lhs, nullptr ); } #endif // CATCH_CONFIG_CPP11_NULLPTR } // end of namespace Internal } // end of namespace Catch #ifdef _MSC_VER #pragma warning(pop) #endif // #included from: catch_tostring.h #define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED #include #include #include #include #include #ifdef __OBJC__ // #included from: catch_objc_arc.hpp #define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED #import #ifdef __has_feature #define CATCH_ARC_ENABLED __has_feature(objc_arc) #else #define CATCH_ARC_ENABLED 0 #endif void arcSafeRelease( NSObject* obj ); id performOptionalSelector( id obj, SEL sel ); #if !CATCH_ARC_ENABLED inline void arcSafeRelease( NSObject* obj ) { [obj release]; } inline id performOptionalSelector( id obj, SEL sel ) { if( [obj respondsToSelector: sel] ) return [obj performSelector: sel]; return nil; } #define CATCH_UNSAFE_UNRETAINED #define CATCH_ARC_STRONG #else inline void arcSafeRelease( NSObject* ){} inline id performOptionalSelector( id obj, SEL sel ) { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" #endif if( [obj respondsToSelector: sel] ) return [obj performSelector: sel]; #ifdef __clang__ #pragma clang diagnostic pop #endif return nil; } #define CATCH_UNSAFE_UNRETAINED __unsafe_unretained #define CATCH_ARC_STRONG __strong #endif #endif #ifdef CATCH_CONFIG_CPP11_TUPLE #include #endif #ifdef CATCH_CONFIG_CPP11_IS_ENUM #include #endif namespace Catch { // Why we're here. template std::string toString( T const& value ); // Built in overloads std::string toString( std::string const& value ); std::string toString( std::wstring const& value ); std::string toString( const char* const value ); std::string toString( char* const value ); std::string toString( const wchar_t* const value ); std::string toString( wchar_t* const value ); std::string toString( int value ); std::string toString( unsigned long value ); std::string toString( unsigned int value ); std::string toString( const double value ); std::string toString( const float value ); std::string toString( bool value ); std::string toString( char value ); std::string toString( signed char value ); std::string toString( unsigned char value ); #ifdef CATCH_CONFIG_CPP11_LONG_LONG std::string toString( long long value ); std::string toString( unsigned long long value ); #endif #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ); #endif #ifdef __OBJC__ std::string toString( NSString const * const& nsstring ); std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); std::string toString( NSObject* const& nsObject ); #endif namespace Detail { extern const std::string unprintableString; struct BorgType { template BorgType( T const& ); }; struct TrueType { char sizer[1]; }; struct FalseType { char sizer[2]; }; TrueType& testStreamable( std::ostream& ); FalseType testStreamable( FalseType ); FalseType operator<<( std::ostream const&, BorgType const& ); template struct IsStreamInsertable { static std::ostream &s; static T const&t; enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; }; #if defined(CATCH_CONFIG_CPP11_IS_ENUM) template::value > struct EnumStringMaker { static std::string convert( T const& ) { return unprintableString; } }; template struct EnumStringMaker { static std::string convert( T const& v ) { return ::Catch::toString( static_cast::type>(v) ); } }; #endif template struct StringMakerBase { #if defined(CATCH_CONFIG_CPP11_IS_ENUM) template static std::string convert( T const& v ) { return EnumStringMaker::convert( v ); } #else template static std::string convert( T const& ) { return unprintableString; } #endif }; template<> struct StringMakerBase { template static std::string convert( T const& _value ) { std::ostringstream oss; oss << _value; return oss.str(); } }; std::string rawMemoryToString( const void *object, std::size_t size ); template inline std::string rawMemoryToString( const T& object ) { return rawMemoryToString( &object, sizeof(object) ); } } // end namespace Detail template struct StringMaker : Detail::StringMakerBase::value> {}; template struct StringMaker { template static std::string convert( U* p ) { if( !p ) return "NULL"; else return Detail::rawMemoryToString( p ); } }; template struct StringMaker { static std::string convert( R C::* p ) { if( !p ) return "NULL"; else return Detail::rawMemoryToString( p ); } }; namespace Detail { template std::string rangeToString( InputIterator first, InputIterator last ); } //template //struct StringMaker > { // static std::string convert( std::vector const& v ) { // return Detail::rangeToString( v.begin(), v.end() ); // } //}; template std::string toString( std::vector const& v ) { return Detail::rangeToString( v.begin(), v.end() ); } #ifdef CATCH_CONFIG_CPP11_TUPLE // toString for tuples namespace TupleDetail { template< typename Tuple, std::size_t N = 0, bool = (N < std::tuple_size::value) > struct ElementPrinter { static void print( const Tuple& tuple, std::ostream& os ) { os << ( N ? ", " : " " ) << Catch::toString(std::get(tuple)); ElementPrinter::print(tuple,os); } }; template< typename Tuple, std::size_t N > struct ElementPrinter { static void print( const Tuple&, std::ostream& ) {} }; } template struct StringMaker> { static std::string convert( const std::tuple& tuple ) { std::ostringstream os; os << '{'; TupleDetail::ElementPrinter>::print( tuple, os ); os << " }"; return os.str(); } }; #endif // CATCH_CONFIG_CPP11_TUPLE namespace Detail { template std::string makeString( T const& value ) { return StringMaker::convert( value ); } } // end namespace Detail /// \brief converts any type to a string /// /// The default template forwards on to ostringstream - except when an /// ostringstream overload does not exist - in which case it attempts to detect /// that and writes {?}. /// Overload (not specialise) this template for custom typs that you don't want /// to provide an ostream overload for. template std::string toString( T const& value ) { return StringMaker::convert( value ); } namespace Detail { template std::string rangeToString( InputIterator first, InputIterator last ) { std::ostringstream oss; oss << "{ "; if( first != last ) { oss << Catch::toString( *first ); for( ++first ; first != last ; ++first ) oss << ", " << Catch::toString( *first ); } oss << " }"; return oss.str(); } } } // end namespace Catch namespace Catch { // Wraps the LHS of an expression and captures the operator and RHS (if any) - // wrapping them all in a ResultBuilder object template class ExpressionLhs { ExpressionLhs& operator = ( ExpressionLhs const& ); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS ExpressionLhs& operator = ( ExpressionLhs && ) = delete; # endif public: ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS ExpressionLhs( ExpressionLhs const& ) = default; ExpressionLhs( ExpressionLhs && ) = default; # endif template ResultBuilder& operator == ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator != ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator < ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator > ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator <= ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator >= ( RhsT const& rhs ) { return captureExpression( rhs ); } ResultBuilder& operator == ( bool rhs ) { return captureExpression( rhs ); } ResultBuilder& operator != ( bool rhs ) { return captureExpression( rhs ); } void endExpression() { bool value = m_lhs ? true : false; m_rb .setLhs( Catch::toString( value ) ) .setResultType( value ) .endExpression(); } // Only simple binary expressions are allowed on the LHS. // If more complex compositions are required then place the sub expression in parentheses template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); private: template ResultBuilder& captureExpression( RhsT const& rhs ) { return m_rb .setResultType( Internal::compare( m_lhs, rhs ) ) .setLhs( Catch::toString( m_lhs ) ) .setRhs( Catch::toString( rhs ) ) .setOp( Internal::OperatorTraits::getName() ); } private: ResultBuilder& m_rb; T m_lhs; }; } // end namespace Catch namespace Catch { template inline ExpressionLhs ResultBuilder::operator <= ( T const& operand ) { return ExpressionLhs( *this, operand ); } inline ExpressionLhs ResultBuilder::operator <= ( bool value ) { return ExpressionLhs( *this, value ); } } // namespace Catch // #included from: catch_message.h #define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED #include namespace Catch { struct MessageInfo { MessageInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ); std::string macroName; SourceLineInfo lineInfo; ResultWas::OfType type; std::string message; unsigned int sequence; bool operator == ( MessageInfo const& other ) const { return sequence == other.sequence; } bool operator < ( MessageInfo const& other ) const { return sequence < other.sequence; } private: static unsigned int globalCount; }; struct MessageBuilder { MessageBuilder( std::string const& macroName, SourceLineInfo const& lineInfo, ResultWas::OfType type ) : m_info( macroName, lineInfo, type ) {} template MessageBuilder& operator << ( T const& value ) { m_stream << value; return *this; } MessageInfo m_info; std::ostringstream m_stream; }; class ScopedMessage { public: ScopedMessage( MessageBuilder const& builder ); ScopedMessage( ScopedMessage const& other ); ~ScopedMessage(); MessageInfo m_info; }; } // end namespace Catch // #included from: catch_interfaces_capture.h #define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED #include namespace Catch { class TestCase; class AssertionResult; struct AssertionInfo; struct SectionInfo; struct SectionEndInfo; struct MessageInfo; class ScopedMessageBuilder; struct Counts; struct IResultCapture { virtual ~IResultCapture(); virtual void assertionEnded( AssertionResult const& result ) = 0; virtual bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) = 0; virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; virtual void pushScopedMessage( MessageInfo const& message ) = 0; virtual void popScopedMessage( MessageInfo const& message ) = 0; virtual std::string getCurrentTestName() const = 0; virtual const AssertionResult* getLastResult() const = 0; virtual void handleFatalErrorCondition( std::string const& message ) = 0; }; IResultCapture& getResultCapture(); } // #included from: catch_debugger.h #define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED // #included from: catch_platform.h #define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) #define CATCH_PLATFORM_MAC #elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #define CATCH_PLATFORM_IPHONE #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) #define CATCH_PLATFORM_WINDOWS #endif #include namespace Catch{ bool isDebuggerActive(); void writeToDebugConsole( std::string const& text ); } #ifdef CATCH_PLATFORM_MAC // The following code snippet based on: // http://cocoawithlove.com/2008/03/break-into-debugger.html #ifdef DEBUG #if defined(__ppc64__) || defined(__ppc__) #define CATCH_BREAK_INTO_DEBUGGER() \ if( Catch::isDebuggerActive() ) { \ __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ : : : "memory","r0","r3","r4" ); \ } #else #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );} #endif #endif #elif defined(_MSC_VER) #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); } #elif defined(__MINGW32__) extern "C" __declspec(dllimport) void __stdcall DebugBreak(); #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); } #endif #ifndef CATCH_BREAK_INTO_DEBUGGER #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); #endif // #included from: catch_interfaces_runner.h #define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED namespace Catch { class TestCase; struct IRunner { virtual ~IRunner(); virtual bool aborting() const = 0; }; } /////////////////////////////////////////////////////////////////////////////// // In the event of a failure works out if the debugger needs to be invoked // and/or an exception thrown and takes appropriate action. // This needs to be done as a macro so the debugger will stop in the user // source code rather than in Catch library code #define INTERNAL_CATCH_REACT( resultBuilder ) \ if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ resultBuilder.react(); /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ ( __catchResult <= expr ).endExpression(); \ } \ catch( ... ) { \ __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::isTrue( false && !!(expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ if( Catch::getResultCapture().getLastResult()->succeeded() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ if( !Catch::getResultCapture().getLastResult()->succeeded() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ __catchResult.useActiveException( resultDisposition ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS( expr, resultDisposition, matcher, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \ if( __catchResult.allowThrows() ) \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( ... ) { \ __catchResult.captureExpectedException( matcher ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ if( __catchResult.allowThrows() ) \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( exceptionType ) { \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ __catchResult.useActiveException( resultDisposition ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #ifdef CATCH_CONFIG_VARIADIC_MACROS #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ __catchResult.captureResult( messageType ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) #else #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ __catchResult << log + ::Catch::StreamEndStop(); \ __catchResult.captureResult( messageType ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) #endif /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_INFO( log, macroName ) \ Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \ try { \ std::string matcherAsString = (matcher).toString(); \ __catchResult \ .setLhs( Catch::toString( arg ) ) \ .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ .setOp( "matches" ) \ .setResultType( (matcher).match( arg ) ); \ __catchResult.captureExpression(); \ } catch( ... ) { \ __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) // #included from: internal/catch_section.h #define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED // #included from: catch_section_info.h #define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED // #included from: catch_totals.hpp #define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED #include namespace Catch { struct Counts { Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} Counts operator - ( Counts const& other ) const { Counts diff; diff.passed = passed - other.passed; diff.failed = failed - other.failed; diff.failedButOk = failedButOk - other.failedButOk; return diff; } Counts& operator += ( Counts const& other ) { passed += other.passed; failed += other.failed; failedButOk += other.failedButOk; return *this; } std::size_t total() const { return passed + failed + failedButOk; } bool allPassed() const { return failed == 0 && failedButOk == 0; } bool allOk() const { return failed == 0; } std::size_t passed; std::size_t failed; std::size_t failedButOk; }; struct Totals { Totals operator - ( Totals const& other ) const { Totals diff; diff.assertions = assertions - other.assertions; diff.testCases = testCases - other.testCases; return diff; } Totals delta( Totals const& prevTotals ) const { Totals diff = *this - prevTotals; if( diff.assertions.failed > 0 ) ++diff.testCases.failed; else if( diff.assertions.failedButOk > 0 ) ++diff.testCases.failedButOk; else ++diff.testCases.passed; return diff; } Totals& operator += ( Totals const& other ) { assertions += other.assertions; testCases += other.testCases; return *this; } Counts assertions; Counts testCases; }; } namespace Catch { struct SectionInfo { SectionInfo ( SourceLineInfo const& _lineInfo, std::string const& _name, std::string const& _description = std::string() ); std::string name; std::string description; SourceLineInfo lineInfo; }; struct SectionEndInfo { SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) {} SectionInfo sectionInfo; Counts prevAssertions; double durationInSeconds; }; } // end namespace Catch // #included from: catch_timer.h #define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED #ifdef CATCH_PLATFORM_WINDOWS typedef unsigned long long uint64_t; #else #include #endif namespace Catch { class Timer { public: Timer() : m_ticks( 0 ) {} void start(); unsigned int getElapsedMicroseconds() const; unsigned int getElapsedMilliseconds() const; double getElapsedSeconds() const; private: uint64_t m_ticks; }; } // namespace Catch #include namespace Catch { class Section : NonCopyable { public: Section( SectionInfo const& info ); ~Section(); // This indicates whether the section should be executed or not operator bool() const; private: SectionInfo m_info; std::string m_name; Counts m_assertions; bool m_sectionIncluded; Timer m_timer; }; } // end namespace Catch #ifdef CATCH_CONFIG_VARIADIC_MACROS #define INTERNAL_CATCH_SECTION( ... ) \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) #else #define INTERNAL_CATCH_SECTION( name, desc ) \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) #endif // #included from: internal/catch_generators.hpp #define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED #include #include #include #include namespace Catch { template struct IGenerator { virtual ~IGenerator() {} virtual T getValue( std::size_t index ) const = 0; virtual std::size_t size () const = 0; }; template class BetweenGenerator : public IGenerator { public: BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} virtual T getValue( std::size_t index ) const { return m_from+static_cast( index ); } virtual std::size_t size() const { return static_cast( 1+m_to-m_from ); } private: T m_from; T m_to; }; template class ValuesGenerator : public IGenerator { public: ValuesGenerator(){} void add( T value ) { m_values.push_back( value ); } virtual T getValue( std::size_t index ) const { return m_values[index]; } virtual std::size_t size() const { return m_values.size(); } private: std::vector m_values; }; template class CompositeGenerator { public: CompositeGenerator() : m_totalSize( 0 ) {} // *** Move semantics, similar to auto_ptr *** CompositeGenerator( CompositeGenerator& other ) : m_fileInfo( other.m_fileInfo ), m_totalSize( 0 ) { move( other ); } CompositeGenerator& setFileInfo( const char* fileInfo ) { m_fileInfo = fileInfo; return *this; } ~CompositeGenerator() { deleteAll( m_composed ); } operator T () const { size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); typename std::vector*>::const_iterator it = m_composed.begin(); typename std::vector*>::const_iterator itEnd = m_composed.end(); for( size_t index = 0; it != itEnd; ++it ) { const IGenerator* generator = *it; if( overallIndex >= index && overallIndex < index + generator->size() ) { return generator->getValue( overallIndex-index ); } index += generator->size(); } CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so } void add( const IGenerator* generator ) { m_totalSize += generator->size(); m_composed.push_back( generator ); } CompositeGenerator& then( CompositeGenerator& other ) { move( other ); return *this; } CompositeGenerator& then( T value ) { ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( value ); add( valuesGen ); return *this; } private: void move( CompositeGenerator& other ) { std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); m_totalSize += other.m_totalSize; other.m_composed.clear(); } std::vector*> m_composed; std::string m_fileInfo; size_t m_totalSize; }; namespace Generators { template CompositeGenerator between( T from, T to ) { CompositeGenerator generators; generators.add( new BetweenGenerator( from, to ) ); return generators; } template CompositeGenerator values( T val1, T val2 ) { CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); generators.add( valuesGen ); return generators; } template CompositeGenerator values( T val1, T val2, T val3 ){ CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); valuesGen->add( val3 ); generators.add( valuesGen ); return generators; } template CompositeGenerator values( T val1, T val2, T val3, T val4 ) { CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); valuesGen->add( val3 ); valuesGen->add( val4 ); generators.add( valuesGen ); return generators; } } // end namespace Generators using namespace Generators; } // end namespace Catch #define INTERNAL_CATCH_LINESTR2( line ) #line #define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) #define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) // #included from: internal/catch_interfaces_exception.h #define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED #include #include // #included from: catch_interfaces_registry_hub.h #define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED #include namespace Catch { class TestCase; struct ITestCaseRegistry; struct IExceptionTranslatorRegistry; struct IExceptionTranslator; struct IReporterRegistry; struct IReporterFactory; struct IRegistryHub { virtual ~IRegistryHub(); virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; }; struct IMutableRegistryHub { virtual ~IMutableRegistryHub(); virtual void registerReporter( std::string const& name, Ptr const& factory ) = 0; virtual void registerListener( Ptr const& factory ) = 0; virtual void registerTest( TestCase const& testInfo ) = 0; virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; }; IRegistryHub& getRegistryHub(); IMutableRegistryHub& getMutableRegistryHub(); void cleanUp(); std::string translateActiveException(); } namespace Catch { typedef std::string(*exceptionTranslateFunction)(); struct IExceptionTranslator; typedef std::vector ExceptionTranslators; struct IExceptionTranslator { virtual ~IExceptionTranslator(); virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; }; struct IExceptionTranslatorRegistry { virtual ~IExceptionTranslatorRegistry(); virtual std::string translateActiveException() const = 0; }; class ExceptionTranslatorRegistrar { template class ExceptionTranslator : public IExceptionTranslator { public: ExceptionTranslator( std::string(*translateFunction)( T& ) ) : m_translateFunction( translateFunction ) {} virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE { try { if( it == itEnd ) throw; else return (*it)->translate( it+1, itEnd ); } catch( T& ex ) { return m_translateFunction( ex ); } } protected: std::string(*m_translateFunction)( T& ); }; public: template ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { getMutableRegistryHub().registerTranslator ( new ExceptionTranslator( translateFunction ) ); } }; } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ static std::string translatorName( signature ); \ namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ static std::string translatorName( signature ) #define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) // #included from: internal/catch_approx.hpp #define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED #include #include namespace Catch { namespace Detail { class Approx { public: explicit Approx ( double value ) : m_epsilon( std::numeric_limits::epsilon()*100 ), m_scale( 1.0 ), m_value( value ) {} Approx( Approx const& other ) : m_epsilon( other.m_epsilon ), m_scale( other.m_scale ), m_value( other.m_value ) {} static Approx custom() { return Approx( 0 ); } Approx operator()( double value ) { Approx approx( value ); approx.epsilon( m_epsilon ); approx.scale( m_scale ); return approx; } friend bool operator == ( double lhs, Approx const& rhs ) { // Thanks to Richard Harris for his help refining this formula return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); } friend bool operator == ( Approx const& lhs, double rhs ) { return operator==( rhs, lhs ); } friend bool operator != ( double lhs, Approx const& rhs ) { return !operator==( lhs, rhs ); } friend bool operator != ( Approx const& lhs, double rhs ) { return !operator==( rhs, lhs ); } Approx& epsilon( double newEpsilon ) { m_epsilon = newEpsilon; return *this; } Approx& scale( double newScale ) { m_scale = newScale; return *this; } std::string toString() const { std::ostringstream oss; oss << "Approx( " << Catch::toString( m_value ) << " )"; return oss.str(); } private: double m_epsilon; double m_scale; double m_value; }; } template<> inline std::string toString( Detail::Approx const& value ) { return value.toString(); } } // end namespace Catch // #included from: internal/catch_interfaces_tag_alias_registry.h #define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED // #included from: catch_tag_alias.h #define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED #include namespace Catch { struct TagAlias { TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} std::string tag; SourceLineInfo lineInfo; }; struct RegistrarForTagAliases { RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); }; } // end namespace Catch #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } // #included from: catch_option.hpp #define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED namespace Catch { // An optional type template class Option { public: Option() : nullableValue( CATCH_NULL ) {} Option( T const& _value ) : nullableValue( new( storage ) T( _value ) ) {} Option( Option const& _other ) : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL ) {} ~Option() { reset(); } Option& operator= ( Option const& _other ) { if( &_other != this ) { reset(); if( _other ) nullableValue = new( storage ) T( *_other ); } return *this; } Option& operator = ( T const& _value ) { reset(); nullableValue = new( storage ) T( _value ); return *this; } void reset() { if( nullableValue ) nullableValue->~T(); nullableValue = CATCH_NULL; } T& operator*() { return *nullableValue; } T const& operator*() const { return *nullableValue; } T* operator->() { return nullableValue; } const T* operator->() const { return nullableValue; } T valueOr( T const& defaultValue ) const { return nullableValue ? *nullableValue : defaultValue; } bool some() const { return nullableValue != CATCH_NULL; } bool none() const { return nullableValue == CATCH_NULL; } bool operator !() const { return nullableValue == CATCH_NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( some() ); } private: T* nullableValue; char storage[sizeof(T)]; }; } // end namespace Catch namespace Catch { struct ITagAliasRegistry { virtual ~ITagAliasRegistry(); virtual Option find( std::string const& alias ) const = 0; virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; static ITagAliasRegistry const& get(); }; } // end namespace Catch // These files are included here so the single_include script doesn't put them // in the conditionally compiled sections // #included from: internal/catch_test_case_info.h #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED #include #include #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif namespace Catch { struct ITestCase; struct TestCaseInfo { enum SpecialProperties{ None = 0, IsHidden = 1 << 1, ShouldFail = 1 << 2, MayFail = 1 << 3, Throws = 1 << 4 }; TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, std::set const& _tags, SourceLineInfo const& _lineInfo ); TestCaseInfo( TestCaseInfo const& other ); friend void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ); bool isHidden() const; bool throws() const; bool okToFail() const; bool expectedToFail() const; std::string name; std::string className; std::string description; std::set tags; std::set lcaseTags; std::string tagsAsString; SourceLineInfo lineInfo; SpecialProperties properties; }; class TestCase : public TestCaseInfo { public: TestCase( ITestCase* testCase, TestCaseInfo const& info ); TestCase( TestCase const& other ); TestCase withName( std::string const& _newName ) const; void invoke() const; TestCaseInfo const& getTestCaseInfo() const; void swap( TestCase& other ); bool operator == ( TestCase const& other ) const; bool operator < ( TestCase const& other ) const; TestCase& operator = ( TestCase const& other ); private: Ptr test; }; TestCase makeTestCase( ITestCase* testCase, std::string const& className, std::string const& name, std::string const& description, SourceLineInfo const& lineInfo ); } #ifdef __clang__ #pragma clang diagnostic pop #endif #ifdef __OBJC__ // #included from: internal/catch_objc.hpp #define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED #import #include // NB. Any general catch headers included here must be included // in catch.hpp first to make sure they are included by the single // header for non obj-usage /////////////////////////////////////////////////////////////////////////////// // This protocol is really only here for (self) documenting purposes, since // all its methods are optional. @protocol OcFixture @optional -(void) setUp; -(void) tearDown; @end namespace Catch { class OcMethod : public SharedImpl { public: OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} virtual void invoke() const { id obj = [[m_cls alloc] init]; performOptionalSelector( obj, @selector(setUp) ); performOptionalSelector( obj, m_sel ); performOptionalSelector( obj, @selector(tearDown) ); arcSafeRelease( obj ); } private: virtual ~OcMethod() {} Class m_cls; SEL m_sel; }; namespace Detail{ inline std::string getAnnotation( Class cls, std::string const& annotationName, std::string const& testCaseName ) { NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; SEL sel = NSSelectorFromString( selStr ); arcSafeRelease( selStr ); id value = performOptionalSelector( cls, sel ); if( value ) return [(NSString*)value UTF8String]; return ""; } } inline size_t registerTestMethods() { size_t noTestMethods = 0; int noClasses = objc_getClassList( CATCH_NULL, 0 ); Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); objc_getClassList( classes, noClasses ); for( int c = 0; c < noClasses; c++ ) { Class cls = classes[c]; { u_int count; Method* methods = class_copyMethodList( cls, &count ); for( u_int m = 0; m < count ; m++ ) { SEL selector = method_getName(methods[m]); std::string methodName = sel_getName(selector); if( startsWith( methodName, "Catch_TestCase_" ) ) { std::string testCaseName = methodName.substr( 15 ); std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); const char* className = class_getName( cls ); getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); noTestMethods++; } } free(methods); } } return noTestMethods; } namespace Matchers { namespace Impl { namespace NSStringMatchers { template struct StringHolder : MatcherImpl{ StringHolder( NSString* substr ) : m_substr( [substr copy] ){} StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} StringHolder() { arcSafeRelease( m_substr ); } NSString* m_substr; }; struct Equals : StringHolder { Equals( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str isEqualToString:m_substr]; } virtual std::string toString() const { return "equals string: " + Catch::toString( m_substr ); } }; struct Contains : StringHolder { Contains( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location != NSNotFound; } virtual std::string toString() const { return "contains string: " + Catch::toString( m_substr ); } }; struct StartsWith : StringHolder { StartsWith( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == 0; } virtual std::string toString() const { return "starts with: " + Catch::toString( m_substr ); } }; struct EndsWith : StringHolder { EndsWith( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == [str length] - [m_substr length]; } virtual std::string toString() const { return "ends with: " + Catch::toString( m_substr ); } }; } // namespace NSStringMatchers } // namespace Impl inline Impl::NSStringMatchers::Equals Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } inline Impl::NSStringMatchers::Contains Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } inline Impl::NSStringMatchers::StartsWith StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } inline Impl::NSStringMatchers::EndsWith EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } } // namespace Matchers using namespace Matchers; } // namespace Catch /////////////////////////////////////////////////////////////////////////////// #define OC_TEST_CASE( name, desc )\ +(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ {\ return @ name; \ }\ +(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ { \ return @ desc; \ } \ -(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) #endif #ifdef CATCH_IMPL // #included from: internal/catch_impl.hpp #define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED // Collect all the implementation files together here // These are the equivalent of what would usually be cpp files #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wweak-vtables" #endif // #included from: ../catch_session.hpp #define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED // #included from: internal/catch_commandline.hpp #define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED // #included from: catch_config.hpp #define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED // #included from: catch_test_spec_parser.hpp #define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif // #included from: catch_test_spec.hpp #define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif // #included from: catch_wildcard_pattern.hpp #define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED namespace Catch { class WildcardPattern { enum WildcardPosition { NoWildcard = 0, WildcardAtStart = 1, WildcardAtEnd = 2, WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd }; public: WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) : m_caseSensitivity( caseSensitivity ), m_wildcard( NoWildcard ), m_pattern( adjustCase( pattern ) ) { if( startsWith( m_pattern, "*" ) ) { m_pattern = m_pattern.substr( 1 ); m_wildcard = WildcardAtStart; } if( endsWith( m_pattern, "*" ) ) { m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); } } virtual ~WildcardPattern(); virtual bool matches( std::string const& str ) const { switch( m_wildcard ) { case NoWildcard: return m_pattern == adjustCase( str ); case WildcardAtStart: return endsWith( adjustCase( str ), m_pattern ); case WildcardAtEnd: return startsWith( adjustCase( str ), m_pattern ); case WildcardAtBothEnds: return contains( adjustCase( str ), m_pattern ); } #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code" #endif throw std::logic_error( "Unknown enum" ); #ifdef __clang__ #pragma clang diagnostic pop #endif } private: std::string adjustCase( std::string const& str ) const { return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; } CaseSensitive::Choice m_caseSensitivity; WildcardPosition m_wildcard; std::string m_pattern; }; } #include #include namespace Catch { class TestSpec { struct Pattern : SharedImpl<> { virtual ~Pattern(); virtual bool matches( TestCaseInfo const& testCase ) const = 0; }; class NamePattern : public Pattern { public: NamePattern( std::string const& name ) : m_wildcardPattern( toLower( name ), CaseSensitive::No ) {} virtual ~NamePattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return m_wildcardPattern.matches( toLower( testCase.name ) ); } private: WildcardPattern m_wildcardPattern; }; class TagPattern : public Pattern { public: TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} virtual ~TagPattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); } private: std::string m_tag; }; class ExcludedPattern : public Pattern { public: ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} virtual ~ExcludedPattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } private: Ptr m_underlyingPattern; }; struct Filter { std::vector > m_patterns; bool matches( TestCaseInfo const& testCase ) const { // All patterns in a filter must match for the filter to be a match for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) { if( !(*it)->matches( testCase ) ) return false; } return true; } }; public: bool hasFilters() const { return !m_filters.empty(); } bool matches( TestCaseInfo const& testCase ) const { // A TestSpec matches if any filter matches for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) if( it->matches( testCase ) ) return true; return false; } private: std::vector m_filters; friend class TestSpecParser; }; } #ifdef __clang__ #pragma clang diagnostic pop #endif namespace Catch { class TestSpecParser { enum Mode{ None, Name, QuotedName, Tag }; Mode m_mode; bool m_exclusion; std::size_t m_start, m_pos; std::string m_arg; TestSpec::Filter m_currentFilter; TestSpec m_testSpec; ITagAliasRegistry const* m_tagAliases; public: TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} TestSpecParser& parse( std::string const& arg ) { m_mode = None; m_exclusion = false; m_start = std::string::npos; m_arg = m_tagAliases->expandAliases( arg ); for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) visitChar( m_arg[m_pos] ); if( m_mode == Name ) addPattern(); return *this; } TestSpec testSpec() { addFilter(); return m_testSpec; } private: void visitChar( char c ) { if( m_mode == None ) { switch( c ) { case ' ': return; case '~': m_exclusion = true; return; case '[': return startNewMode( Tag, ++m_pos ); case '"': return startNewMode( QuotedName, ++m_pos ); default: startNewMode( Name, m_pos ); break; } } if( m_mode == Name ) { if( c == ',' ) { addPattern(); addFilter(); } else if( c == '[' ) { if( subString() == "exclude:" ) m_exclusion = true; else addPattern(); startNewMode( Tag, ++m_pos ); } } else if( m_mode == QuotedName && c == '"' ) addPattern(); else if( m_mode == Tag && c == ']' ) addPattern(); } void startNewMode( Mode mode, std::size_t start ) { m_mode = mode; m_start = start; } std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } template void addPattern() { std::string token = subString(); if( startsWith( token, "exclude:" ) ) { m_exclusion = true; token = token.substr( 8 ); } if( !token.empty() ) { Ptr pattern = new T( token ); if( m_exclusion ) pattern = new TestSpec::ExcludedPattern( pattern ); m_currentFilter.m_patterns.push_back( pattern ); } m_exclusion = false; m_mode = None; } void addFilter() { if( !m_currentFilter.m_patterns.empty() ) { m_testSpec.m_filters.push_back( m_currentFilter ); m_currentFilter = TestSpec::Filter(); } } }; inline TestSpec parseTestSpec( std::string const& arg ) { return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); } } // namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif // #included from: catch_interfaces_config.h #define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED #include #include #include namespace Catch { struct Verbosity { enum Level { NoOutput = 0, Quiet, Normal }; }; struct WarnAbout { enum What { Nothing = 0x00, NoAssertions = 0x01 }; }; struct ShowDurations { enum OrNot { DefaultForReporter, Always, Never }; }; struct RunTests { enum InWhatOrder { InDeclarationOrder, InLexicographicalOrder, InRandomOrder }; }; struct UseColour { enum YesOrNo { Auto, Yes, No }; }; class TestSpec; struct IConfig : IShared { virtual ~IConfig(); virtual bool allowThrows() const = 0; virtual std::ostream& stream() const = 0; virtual std::string name() const = 0; virtual bool includeSuccessfulResults() const = 0; virtual bool shouldDebugBreak() const = 0; virtual bool warnAboutMissingAssertions() const = 0; virtual int abortAfter() const = 0; virtual bool showInvisibles() const = 0; virtual ShowDurations::OrNot showDurations() const = 0; virtual TestSpec const& testSpec() const = 0; virtual RunTests::InWhatOrder runOrder() const = 0; virtual unsigned int rngSeed() const = 0; virtual UseColour::YesOrNo useColour() const = 0; }; } // #included from: catch_stream.h #define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED // #included from: catch_streambuf.h #define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED #include namespace Catch { class StreamBufBase : public std::streambuf { public: virtual ~StreamBufBase() CATCH_NOEXCEPT; }; } #include #include #include #include namespace Catch { std::ostream& cout(); std::ostream& cerr(); struct IStream { virtual ~IStream() CATCH_NOEXCEPT; virtual std::ostream& stream() const = 0; }; class FileStream : public IStream { mutable std::ofstream m_ofs; public: FileStream( std::string const& filename ); virtual ~FileStream() CATCH_NOEXCEPT; public: // IStream virtual std::ostream& stream() const CATCH_OVERRIDE; }; class CoutStream : public IStream { mutable std::ostream m_os; public: CoutStream(); virtual ~CoutStream() CATCH_NOEXCEPT; public: // IStream virtual std::ostream& stream() const CATCH_OVERRIDE; }; class DebugOutStream : public IStream { CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf; mutable std::ostream m_os; public: DebugOutStream(); virtual ~DebugOutStream() CATCH_NOEXCEPT; public: // IStream virtual std::ostream& stream() const CATCH_OVERRIDE; }; } #include #include #include #include #include #ifndef CATCH_CONFIG_CONSOLE_WIDTH #define CATCH_CONFIG_CONSOLE_WIDTH 80 #endif namespace Catch { struct ConfigData { ConfigData() : listTests( false ), listTags( false ), listReporters( false ), listTestNamesOnly( false ), showSuccessfulTests( false ), shouldDebugBreak( false ), noThrow( false ), showHelp( false ), showInvisibles( false ), filenamesAsTags( false ), abortAfter( -1 ), rngSeed( 0 ), verbosity( Verbosity::Normal ), warnings( WarnAbout::Nothing ), showDurations( ShowDurations::DefaultForReporter ), runOrder( RunTests::InDeclarationOrder ), useColour( UseColour::Auto ) {} bool listTests; bool listTags; bool listReporters; bool listTestNamesOnly; bool showSuccessfulTests; bool shouldDebugBreak; bool noThrow; bool showHelp; bool showInvisibles; bool filenamesAsTags; int abortAfter; unsigned int rngSeed; Verbosity::Level verbosity; WarnAbout::What warnings; ShowDurations::OrNot showDurations; RunTests::InWhatOrder runOrder; UseColour::YesOrNo useColour; std::string outputFilename; std::string name; std::string processName; std::vector reporterNames; std::vector testsOrTags; }; class Config : public SharedImpl { private: Config( Config const& other ); Config& operator = ( Config const& other ); virtual void dummy(); public: Config() {} Config( ConfigData const& data ) : m_data( data ), m_stream( openStream() ) { if( !data.testsOrTags.empty() ) { TestSpecParser parser( ITagAliasRegistry::get() ); for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) parser.parse( data.testsOrTags[i] ); m_testSpec = parser.testSpec(); } } virtual ~Config() { } std::string const& getFilename() const { return m_data.outputFilename ; } bool listTests() const { return m_data.listTests; } bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } bool listTags() const { return m_data.listTags; } bool listReporters() const { return m_data.listReporters; } std::string getProcessName() const { return m_data.processName; } bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } std::vector getReporterNames() const { return m_data.reporterNames; } int abortAfter() const { return m_data.abortAfter; } TestSpec const& testSpec() const { return m_testSpec; } bool showHelp() const { return m_data.showHelp; } bool showInvisibles() const { return m_data.showInvisibles; } // IConfig interface virtual bool allowThrows() const { return !m_data.noThrow; } virtual std::ostream& stream() const { return m_stream->stream(); } virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } virtual unsigned int rngSeed() const { return m_data.rngSeed; } virtual UseColour::YesOrNo useColour() const { return m_data.useColour; } private: IStream const* openStream() { if( m_data.outputFilename.empty() ) return new CoutStream(); else if( m_data.outputFilename[0] == '%' ) { if( m_data.outputFilename == "%debug" ) return new DebugOutStream(); else throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename ); } else return new FileStream( m_data.outputFilename ); } ConfigData m_data; CATCH_AUTO_PTR( IStream const ) m_stream; TestSpec m_testSpec; }; } // end namespace Catch // #included from: catch_clara.h #define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED // Use Catch's value for console width (store Clara's off to the side, if present) #ifdef CLARA_CONFIG_CONSOLE_WIDTH #define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH #undef CLARA_CONFIG_CONSOLE_WIDTH #endif #define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH // Declare Clara inside the Catch namespace #define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { // #included from: ../external/clara.h // Version 0.0.2.4 // Only use header guard if we are not using an outer namespace #if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) #ifndef STITCH_CLARA_OPEN_NAMESPACE #define TWOBLUECUBES_CLARA_H_INCLUDED #define STITCH_CLARA_OPEN_NAMESPACE #define STITCH_CLARA_CLOSE_NAMESPACE #else #define STITCH_CLARA_CLOSE_NAMESPACE } #endif #define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE // ----------- #included from tbc_text_format.h ----------- // Only use header guard if we are not using an outer namespace #if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) #ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE #define TBC_TEXT_FORMAT_H_INCLUDED #endif #include #include #include #include // Use optional outer namespace #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { #endif namespace Tbc { #ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif struct TextAttributes { TextAttributes() : initialIndent( std::string::npos ), indent( 0 ), width( consoleWidth-1 ), tabChar( '\t' ) {} TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } std::size_t initialIndent; // indent of first line, or npos std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos std::size_t width; // maximum width of text, including indent. Longer text will wrap char tabChar; // If this char is seen the indent is changed to current pos }; class Text { public: Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) : attr( _attr ) { std::string wrappableChars = " [({.,/|\\-"; std::size_t indent = _attr.initialIndent != std::string::npos ? _attr.initialIndent : _attr.indent; std::string remainder = _str; while( !remainder.empty() ) { if( lines.size() >= 1000 ) { lines.push_back( "... message truncated due to excessive size" ); return; } std::size_t tabPos = std::string::npos; std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); std::size_t pos = remainder.find_first_of( '\n' ); if( pos <= width ) { width = pos; } pos = remainder.find_last_of( _attr.tabChar, width ); if( pos != std::string::npos ) { tabPos = pos; if( remainder[width] == '\n' ) width--; remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); } if( width == remainder.size() ) { spliceLine( indent, remainder, width ); } else if( remainder[width] == '\n' ) { spliceLine( indent, remainder, width ); if( width <= 1 || remainder.size() != 1 ) remainder = remainder.substr( 1 ); indent = _attr.indent; } else { pos = remainder.find_last_of( wrappableChars, width ); if( pos != std::string::npos && pos > 0 ) { spliceLine( indent, remainder, pos ); if( remainder[0] == ' ' ) remainder = remainder.substr( 1 ); } else { spliceLine( indent, remainder, width-1 ); lines.back() += "-"; } if( lines.size() == 1 ) indent = _attr.indent; if( tabPos != std::string::npos ) indent += tabPos; } } } void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); _remainder = _remainder.substr( _pos ); } typedef std::vector::const_iterator const_iterator; const_iterator begin() const { return lines.begin(); } const_iterator end() const { return lines.end(); } std::string const& last() const { return lines.back(); } std::size_t size() const { return lines.size(); } std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } std::string toString() const { std::ostringstream oss; oss << *this; return oss.str(); } inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); it != itEnd; ++it ) { if( it != _text.begin() ) _stream << "\n"; _stream << *it; } return _stream; } private: std::string str; TextAttributes attr; std::vector lines; }; } // end namespace Tbc #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE } // end outer namespace #endif #endif // TBC_TEXT_FORMAT_H_INCLUDED // ----------- end of #include from tbc_text_format.h ----------- // ........... back in clara.h #undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE // ----------- #included from clara_compilers.h ----------- #ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED #define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED // Detect a number of compiler features - mostly C++11/14 conformance - by compiler // The following features are defined: // // CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported? // CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported? // CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods // CLARA_CONFIG_CPP11_OVERRIDE : is override supported? // CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) // CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported? // CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported? // In general each macro has a _NO_ form // (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. // All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11 #ifdef __clang__ #if __has_feature(cxx_nullptr) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #endif #if __has_feature(cxx_noexcept) #define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #endif #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // GCC #ifdef __GNUC__ #if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #endif // - otherwise more recent versions define __cplusplus >= 201103L // and will get picked up below #endif // __GNUC__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #ifdef _MSC_VER #if (_MSC_VER >= 1600) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) #define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #endif #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // C++ language feature support // catch all support for C++11 #if defined(__cplusplus) && __cplusplus >= 201103L #define CLARA_CPP11_OR_GREATER #if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #endif #ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #endif #ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #endif #if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) #define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE #endif #if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) #define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #endif // __cplusplus >= 201103L // Now set the actual defines based on the above + anything the user has configured #if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_NULLPTR #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_NOEXCEPT #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_GENERATED_METHODS #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_OVERRIDE #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_UNIQUE_PTR #endif // noexcept support: #if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT) #define CLARA_NOEXCEPT noexcept # define CLARA_NOEXCEPT_IS(x) noexcept(x) #else #define CLARA_NOEXCEPT throw() # define CLARA_NOEXCEPT_IS(x) #endif // nullptr support #ifdef CLARA_CONFIG_CPP11_NULLPTR #define CLARA_NULL nullptr #else #define CLARA_NULL NULL #endif // override support #ifdef CLARA_CONFIG_CPP11_OVERRIDE #define CLARA_OVERRIDE override #else #define CLARA_OVERRIDE #endif // unique_ptr support #ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR # define CLARA_AUTO_PTR( T ) std::unique_ptr #else # define CLARA_AUTO_PTR( T ) std::auto_ptr #endif #endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED // ----------- end of #include from clara_compilers.h ----------- // ........... back in clara.h #include #include #include #if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) #define CLARA_PLATFORM_WINDOWS #endif // Use optional outer namespace #ifdef STITCH_CLARA_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE #endif namespace Clara { struct UnpositionalTag {}; extern UnpositionalTag _; #ifdef CLARA_CONFIG_MAIN UnpositionalTag _; #endif namespace Detail { #ifdef CLARA_CONSOLE_WIDTH const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif using namespace Tbc; inline bool startsWith( std::string const& str, std::string const& prefix ) { return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; } template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct IsBool { static const bool value = false; }; template<> struct IsBool { static const bool value = true; }; template void convertInto( std::string const& _source, T& _dest ) { std::stringstream ss; ss << _source; ss >> _dest; if( ss.fail() ) throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); } inline void convertInto( std::string const& _source, std::string& _dest ) { _dest = _source; } char toLowerCh(char c) { return static_cast( ::tolower( c ) ); } inline void convertInto( std::string const& _source, bool& _dest ) { std::string sourceLC = _source; std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh ); if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) _dest = true; else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) _dest = false; else throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); } template struct IArgFunction { virtual ~IArgFunction() {} #ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS IArgFunction() = default; IArgFunction( IArgFunction const& ) = default; #endif virtual void set( ConfigT& config, std::string const& value ) const = 0; virtual bool takesArg() const = 0; virtual IArgFunction* clone() const = 0; }; template class BoundArgFunction { public: BoundArgFunction() : functionObj( CLARA_NULL ) {} BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {} BoundArgFunction& operator = ( BoundArgFunction const& other ) { IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL; delete functionObj; functionObj = newFunctionObj; return *this; } ~BoundArgFunction() { delete functionObj; } void set( ConfigT& config, std::string const& value ) const { functionObj->set( config, value ); } bool takesArg() const { return functionObj->takesArg(); } bool isSet() const { return functionObj != CLARA_NULL; } private: IArgFunction* functionObj; }; template struct NullBinder : IArgFunction{ virtual void set( C&, std::string const& ) const {} virtual bool takesArg() const { return true; } virtual IArgFunction* clone() const { return new NullBinder( *this ); } }; template struct BoundDataMember : IArgFunction{ BoundDataMember( M C::* _member ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { convertInto( stringValue, p.*member ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } M C::* member; }; template struct BoundUnaryMethod : IArgFunction{ BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { typename RemoveConstRef::type value; convertInto( stringValue, value ); (p.*member)( value ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } void (C::*member)( M ); }; template struct BoundNullaryMethod : IArgFunction{ BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { bool value; convertInto( stringValue, value ); if( value ) (p.*member)(); } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } void (C::*member)(); }; template struct BoundUnaryFunction : IArgFunction{ BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} virtual void set( C& obj, std::string const& stringValue ) const { bool value; convertInto( stringValue, value ); if( value ) function( obj ); } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } void (*function)( C& ); }; template struct BoundBinaryFunction : IArgFunction{ BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} virtual void set( C& obj, std::string const& stringValue ) const { typename RemoveConstRef::type value; convertInto( stringValue, value ); function( obj, value ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } void (*function)( C&, T ); }; } // namespace Detail inline std::vector argsToVector( int argc, char const* const* const argv ) { std::vector args( static_cast( argc ) ); for( std::size_t i = 0; i < static_cast( argc ); ++i ) args[i] = argv[i]; return args; } class Parser { enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional }; Mode mode; std::size_t from; bool inQuotes; public: struct Token { enum Type { Positional, ShortOpt, LongOpt }; Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} Type type; std::string data; }; Parser() : mode( None ), from( 0 ), inQuotes( false ){} void parseIntoTokens( std::vector const& args, std::vector& tokens ) { const std::string doubleDash = "--"; for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i ) parseIntoTokens( args[i], tokens); } void parseIntoTokens( std::string const& arg, std::vector& tokens ) { for( std::size_t i = 0; i <= arg.size(); ++i ) { char c = arg[i]; if( c == '"' ) inQuotes = !inQuotes; mode = handleMode( i, c, arg, tokens ); } } Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { switch( mode ) { case None: return handleNone( i, c ); case MaybeShortOpt: return handleMaybeShortOpt( i, c ); case ShortOpt: case LongOpt: case SlashOpt: return handleOpt( i, c, arg, tokens ); case Positional: return handlePositional( i, c, arg, tokens ); default: throw std::logic_error( "Unknown mode" ); } } Mode handleNone( std::size_t i, char c ) { if( inQuotes ) { from = i; return Positional; } switch( c ) { case '-': return MaybeShortOpt; #ifdef CLARA_PLATFORM_WINDOWS case '/': from = i+1; return SlashOpt; #endif default: from = i; return Positional; } } Mode handleMaybeShortOpt( std::size_t i, char c ) { switch( c ) { case '-': from = i+1; return LongOpt; default: from = i; return ShortOpt; } } Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { if( std::string( ":=\0", 3 ).find( c ) == std::string::npos ) return mode; std::string optName = arg.substr( from, i-from ); if( mode == ShortOpt ) for( std::size_t j = 0; j < optName.size(); ++j ) tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) ); else if( mode == SlashOpt && optName.size() == 1 ) tokens.push_back( Token( Token::ShortOpt, optName ) ); else tokens.push_back( Token( Token::LongOpt, optName ) ); return None; } Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos ) return mode; std::string data = arg.substr( from, i-from ); tokens.push_back( Token( Token::Positional, data ) ); return None; } }; template struct CommonArgProperties { CommonArgProperties() {} CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} Detail::BoundArgFunction boundField; std::string description; std::string detail; std::string placeholder; // Only value if boundField takes an arg bool takesArg() const { return !placeholder.empty(); } void validate() const { if( !boundField.isSet() ) throw std::logic_error( "option not bound" ); } }; struct OptionArgProperties { std::vector shortNames; std::string longName; bool hasShortName( std::string const& shortName ) const { return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); } bool hasLongName( std::string const& _longName ) const { return _longName == longName; } }; struct PositionalArgProperties { PositionalArgProperties() : position( -1 ) {} int position; // -1 means non-positional (floating) bool isFixedPositional() const { return position != -1; } }; template class CommandLine { struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { Arg() {} Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} using CommonArgProperties::placeholder; // !TBD std::string dbgName() const { if( !longName.empty() ) return "--" + longName; if( !shortNames.empty() ) return "-" + shortNames[0]; return "positional args"; } std::string commands() const { std::ostringstream oss; bool first = true; std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); for(; it != itEnd; ++it ) { if( first ) first = false; else oss << ", "; oss << "-" << *it; } if( !longName.empty() ) { if( !first ) oss << ", "; oss << "--" << longName; } if( !placeholder.empty() ) oss << " <" << placeholder << ">"; return oss.str(); } }; typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr; friend void addOptName( Arg& arg, std::string const& optName ) { if( optName.empty() ) return; if( Detail::startsWith( optName, "--" ) ) { if( !arg.longName.empty() ) throw std::logic_error( "Only one long opt may be specified. '" + arg.longName + "' already specified, now attempting to add '" + optName + "'" ); arg.longName = optName.substr( 2 ); } else if( Detail::startsWith( optName, "-" ) ) arg.shortNames.push_back( optName.substr( 1 ) ); else throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); } friend void setPositionalArg( Arg& arg, int position ) { arg.position = position; } class ArgBuilder { public: ArgBuilder( Arg* arg ) : m_arg( arg ) {} // Bind a non-boolean data member (requires placeholder string) template void bind( M C::* field, std::string const& placeholder ) { m_arg->boundField = new Detail::BoundDataMember( field ); m_arg->placeholder = placeholder; } // Bind a boolean data member (no placeholder required) template void bind( bool C::* field ) { m_arg->boundField = new Detail::BoundDataMember( field ); } // Bind a method taking a single, non-boolean argument (requires a placeholder string) template void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); m_arg->placeholder = placeholder; } // Bind a method taking a single, boolean argument (no placeholder string required) template void bind( void (C::* unaryMethod)( bool ) ) { m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); } // Bind a method that takes no arguments (will be called if opt is present) template void bind( void (C::* nullaryMethod)() ) { m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); } // Bind a free function taking a single argument - the object to operate on (no placeholder string required) template void bind( void (* unaryFunction)( C& ) ) { m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); } // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) template void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); m_arg->placeholder = placeholder; } ArgBuilder& describe( std::string const& description ) { m_arg->description = description; return *this; } ArgBuilder& detail( std::string const& detail ) { m_arg->detail = detail; return *this; } protected: Arg* m_arg; }; class OptBuilder : public ArgBuilder { public: OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} OptBuilder& operator[]( std::string const& optName ) { addOptName( *ArgBuilder::m_arg, optName ); return *this; } }; public: CommandLine() : m_boundProcessName( new Detail::NullBinder() ), m_highestSpecifiedArgPosition( 0 ), m_throwOnUnrecognisedTokens( false ) {} CommandLine( CommandLine const& other ) : m_boundProcessName( other.m_boundProcessName ), m_options ( other.m_options ), m_positionalArgs( other.m_positionalArgs ), m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) { if( other.m_floatingArg.get() ) m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); } CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { m_throwOnUnrecognisedTokens = shouldThrow; return *this; } OptBuilder operator[]( std::string const& optName ) { m_options.push_back( Arg() ); addOptName( m_options.back(), optName ); OptBuilder builder( &m_options.back() ); return builder; } ArgBuilder operator[]( int position ) { m_positionalArgs.insert( std::make_pair( position, Arg() ) ); if( position > m_highestSpecifiedArgPosition ) m_highestSpecifiedArgPosition = position; setPositionalArg( m_positionalArgs[position], position ); ArgBuilder builder( &m_positionalArgs[position] ); return builder; } // Invoke this with the _ instance ArgBuilder operator[]( UnpositionalTag ) { if( m_floatingArg.get() ) throw std::logic_error( "Only one unpositional argument can be added" ); m_floatingArg.reset( new Arg() ); ArgBuilder builder( m_floatingArg.get() ); return builder; } template void bindProcessName( M C::* field ) { m_boundProcessName = new Detail::BoundDataMember( field ); } template void bindProcessName( void (C::*_unaryMethod)( M ) ) { m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); } void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; std::size_t maxWidth = 0; for( it = itBegin; it != itEnd; ++it ) maxWidth = (std::max)( maxWidth, it->commands().size() ); for( it = itBegin; it != itEnd; ++it ) { Detail::Text usage( it->commands(), Detail::TextAttributes() .setWidth( maxWidth+indent ) .setIndent( indent ) ); Detail::Text desc( it->description, Detail::TextAttributes() .setWidth( width - maxWidth - 3 ) ); for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { std::string usageCol = i < usage.size() ? usage[i] : ""; os << usageCol; if( i < desc.size() && !desc[i].empty() ) os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) << desc[i]; os << "\n"; } } } std::string optUsage() const { std::ostringstream oss; optUsage( oss ); return oss.str(); } void argSynopsis( std::ostream& os ) const { for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { if( i > 1 ) os << " "; typename std::map::const_iterator it = m_positionalArgs.find( i ); if( it != m_positionalArgs.end() ) os << "<" << it->second.placeholder << ">"; else if( m_floatingArg.get() ) os << "<" << m_floatingArg->placeholder << ">"; else throw std::logic_error( "non consecutive positional arguments with no floating args" ); } // !TBD No indication of mandatory args if( m_floatingArg.get() ) { if( m_highestSpecifiedArgPosition > 1 ) os << " "; os << "[<" << m_floatingArg->placeholder << "> ...]"; } } std::string argSynopsis() const { std::ostringstream oss; argSynopsis( oss ); return oss.str(); } void usage( std::ostream& os, std::string const& procName ) const { validate(); os << "usage:\n " << procName << " "; argSynopsis( os ); if( !m_options.empty() ) { os << " [options]\n\nwhere options are: \n"; optUsage( os, 2 ); } os << "\n"; } std::string usage( std::string const& procName ) const { std::ostringstream oss; usage( oss, procName ); return oss.str(); } ConfigT parse( std::vector const& args ) const { ConfigT config; parseInto( args, config ); return config; } std::vector parseInto( std::vector const& args, ConfigT& config ) const { std::string processName = args[0]; std::size_t lastSlash = processName.find_last_of( "/\\" ); if( lastSlash != std::string::npos ) processName = processName.substr( lastSlash+1 ); m_boundProcessName.set( config, processName ); std::vector tokens; Parser parser; parser.parseIntoTokens( args, tokens ); return populate( tokens, config ); } std::vector populate( std::vector const& tokens, ConfigT& config ) const { validate(); std::vector unusedTokens = populateOptions( tokens, config ); unusedTokens = populateFixedArgs( unusedTokens, config ); unusedTokens = populateFloatingArgs( unusedTokens, config ); return unusedTokens; } std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; std::vector errors; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); for(; it != itEnd; ++it ) { Arg const& arg = *it; try { if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { if( arg.takesArg() ) { if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) errors.push_back( "Expected argument to option: " + token.data ); else arg.boundField.set( config, tokens[++i].data ); } else { arg.boundField.set( config, "true" ); } break; } } catch( std::exception& ex ) { errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); } } if( it == itEnd ) { if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) unusedTokens.push_back( token ); else if( errors.empty() && m_throwOnUnrecognisedTokens ) errors.push_back( "unrecognised option: " + token.data ); } } if( !errors.empty() ) { std::ostringstream oss; for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); it != itEnd; ++it ) { if( it != errors.begin() ) oss << "\n"; oss << *it; } throw std::runtime_error( oss.str() ); } return unusedTokens; } std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; int position = 1; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; typename std::map::const_iterator it = m_positionalArgs.find( position ); if( it != m_positionalArgs.end() ) it->second.boundField.set( config, token.data ); else unusedTokens.push_back( token ); if( token.type == Parser::Token::Positional ) position++; } return unusedTokens; } std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { if( !m_floatingArg.get() ) return tokens; std::vector unusedTokens; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; if( token.type == Parser::Token::Positional ) m_floatingArg->boundField.set( config, token.data ); else unusedTokens.push_back( token ); } return unusedTokens; } void validate() const { if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) throw std::logic_error( "No options or arguments specified" ); for( typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); it != itEnd; ++it ) it->validate(); } private: Detail::BoundArgFunction m_boundProcessName; std::vector m_options; std::map m_positionalArgs; ArgAutoPtr m_floatingArg; int m_highestSpecifiedArgPosition; bool m_throwOnUnrecognisedTokens; }; } // end namespace Clara STITCH_CLARA_CLOSE_NAMESPACE #undef STITCH_CLARA_OPEN_NAMESPACE #undef STITCH_CLARA_CLOSE_NAMESPACE #endif // TWOBLUECUBES_CLARA_H_INCLUDED #undef STITCH_CLARA_OPEN_NAMESPACE // Restore Clara's value for console width, if present #ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #endif #include namespace Catch { inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } inline void abortAfterX( ConfigData& config, int x ) { if( x < 1 ) throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); config.abortAfter = x; } inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); } inline void addWarning( ConfigData& config, std::string const& _warning ) { if( _warning == "NoAssertions" ) config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); else throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); } inline void setOrder( ConfigData& config, std::string const& order ) { if( startsWith( "declared", order ) ) config.runOrder = RunTests::InDeclarationOrder; else if( startsWith( "lexical", order ) ) config.runOrder = RunTests::InLexicographicalOrder; else if( startsWith( "random", order ) ) config.runOrder = RunTests::InRandomOrder; else throw std::runtime_error( "Unrecognised ordering: '" + order + "'" ); } inline void setRngSeed( ConfigData& config, std::string const& seed ) { if( seed == "time" ) { config.rngSeed = static_cast( std::time(0) ); } else { std::stringstream ss; ss << seed; ss >> config.rngSeed; if( ss.fail() ) throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" ); } } inline void setVerbosity( ConfigData& config, int level ) { // !TBD: accept strings? config.verbosity = static_cast( level ); } inline void setShowDurations( ConfigData& config, bool _showDurations ) { config.showDurations = _showDurations ? ShowDurations::Always : ShowDurations::Never; } inline void setUseColour( ConfigData& config, std::string const& value ) { std::string mode = toLower( value ); if( mode == "yes" ) config.useColour = UseColour::Yes; else if( mode == "no" ) config.useColour = UseColour::No; else if( mode == "auto" ) config.useColour = UseColour::Auto; else throw std::runtime_error( "colour mode must be one of: auto, yes or no" ); } inline void forceColour( ConfigData& config ) { config.useColour = UseColour::Yes; } inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { std::ifstream f( _filename.c_str() ); if( !f.is_open() ) throw std::domain_error( "Unable to load input file: " + _filename ); std::string line; while( std::getline( f, line ) ) { line = trim(line); if( !line.empty() && !startsWith( line, "#" ) ) { if( !startsWith( line, "\"" ) ) line = "\"" + line + "\""; addTestOrTags( config, line + "," ); } } } inline Clara::CommandLine makeCommandLineParser() { using namespace Clara; CommandLine cli; cli.bindProcessName( &ConfigData::processName ); cli["-?"]["-h"]["--help"] .describe( "display usage information" ) .bind( &ConfigData::showHelp ); cli["-l"]["--list-tests"] .describe( "list all/matching test cases" ) .bind( &ConfigData::listTests ); cli["-t"]["--list-tags"] .describe( "list all/matching tags" ) .bind( &ConfigData::listTags ); cli["-s"]["--success"] .describe( "include successful tests in output" ) .bind( &ConfigData::showSuccessfulTests ); cli["-b"]["--break"] .describe( "break into debugger on failure" ) .bind( &ConfigData::shouldDebugBreak ); cli["-e"]["--nothrow"] .describe( "skip exception tests" ) .bind( &ConfigData::noThrow ); cli["-i"]["--invisibles"] .describe( "show invisibles (tabs, newlines)" ) .bind( &ConfigData::showInvisibles ); cli["-o"]["--out"] .describe( "output filename" ) .bind( &ConfigData::outputFilename, "filename" ); cli["-r"]["--reporter"] // .placeholder( "name[:filename]" ) .describe( "reporter to use (defaults to console)" ) .bind( &addReporterName, "name" ); cli["-n"]["--name"] .describe( "suite name" ) .bind( &ConfigData::name, "name" ); cli["-a"]["--abort"] .describe( "abort at first failure" ) .bind( &abortAfterFirst ); cli["-x"]["--abortx"] .describe( "abort after x failures" ) .bind( &abortAfterX, "no. failures" ); cli["-w"]["--warn"] .describe( "enable warnings" ) .bind( &addWarning, "warning name" ); // - needs updating if reinstated // cli.into( &setVerbosity ) // .describe( "level of verbosity (0=no output)" ) // .shortOpt( "v") // .longOpt( "verbosity" ) // .placeholder( "level" ); cli[_] .describe( "which test or tests to use" ) .bind( &addTestOrTags, "test name, pattern or tags" ); cli["-d"]["--durations"] .describe( "show test durations" ) .bind( &setShowDurations, "yes|no" ); cli["-f"]["--input-file"] .describe( "load test names to run from a file" ) .bind( &loadTestNamesFromFile, "filename" ); cli["-#"]["--filenames-as-tags"] .describe( "adds a tag for the filename" ) .bind( &ConfigData::filenamesAsTags ); // Less common commands which don't have a short form cli["--list-test-names-only"] .describe( "list all/matching test cases names only" ) .bind( &ConfigData::listTestNamesOnly ); cli["--list-reporters"] .describe( "list all reporters" ) .bind( &ConfigData::listReporters ); cli["--order"] .describe( "test case order (defaults to decl)" ) .bind( &setOrder, "decl|lex|rand" ); cli["--rng-seed"] .describe( "set a specific seed for random numbers" ) .bind( &setRngSeed, "'time'|number" ); cli["--force-colour"] .describe( "force colourised output (deprecated)" ) .bind( &forceColour ); cli["--use-colour"] .describe( "should output be colourised" ) .bind( &setUseColour, "yes|no" ); return cli; } } // end namespace Catch // #included from: internal/catch_list.hpp #define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED // #included from: catch_text.h #define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED #define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH #define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch // #included from: ../external/tbc_text_format.h // Only use header guard if we are not using an outer namespace #ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE # ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED # ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED # define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED # endif # else # define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED # endif #endif #ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED #include #include #include // Use optional outer namespace #ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { #endif namespace Tbc { #ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif struct TextAttributes { TextAttributes() : initialIndent( std::string::npos ), indent( 0 ), width( consoleWidth-1 ), tabChar( '\t' ) {} TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } std::size_t initialIndent; // indent of first line, or npos std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos std::size_t width; // maximum width of text, including indent. Longer text will wrap char tabChar; // If this char is seen the indent is changed to current pos }; class Text { public: Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) : attr( _attr ) { std::string wrappableChars = " [({.,/|\\-"; std::size_t indent = _attr.initialIndent != std::string::npos ? _attr.initialIndent : _attr.indent; std::string remainder = _str; while( !remainder.empty() ) { if( lines.size() >= 1000 ) { lines.push_back( "... message truncated due to excessive size" ); return; } std::size_t tabPos = std::string::npos; std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); std::size_t pos = remainder.find_first_of( '\n' ); if( pos <= width ) { width = pos; } pos = remainder.find_last_of( _attr.tabChar, width ); if( pos != std::string::npos ) { tabPos = pos; if( remainder[width] == '\n' ) width--; remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); } if( width == remainder.size() ) { spliceLine( indent, remainder, width ); } else if( remainder[width] == '\n' ) { spliceLine( indent, remainder, width ); if( width <= 1 || remainder.size() != 1 ) remainder = remainder.substr( 1 ); indent = _attr.indent; } else { pos = remainder.find_last_of( wrappableChars, width ); if( pos != std::string::npos && pos > 0 ) { spliceLine( indent, remainder, pos ); if( remainder[0] == ' ' ) remainder = remainder.substr( 1 ); } else { spliceLine( indent, remainder, width-1 ); lines.back() += "-"; } if( lines.size() == 1 ) indent = _attr.indent; if( tabPos != std::string::npos ) indent += tabPos; } } } void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); _remainder = _remainder.substr( _pos ); } typedef std::vector::const_iterator const_iterator; const_iterator begin() const { return lines.begin(); } const_iterator end() const { return lines.end(); } std::string const& last() const { return lines.back(); } std::size_t size() const { return lines.size(); } std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } std::string toString() const { std::ostringstream oss; oss << *this; return oss.str(); } inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); it != itEnd; ++it ) { if( it != _text.begin() ) _stream << "\n"; _stream << *it; } return _stream; } private: std::string str; TextAttributes attr; std::vector lines; }; } // end namespace Tbc #ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE } // end outer namespace #endif #endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED #undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace Catch { using Tbc::Text; using Tbc::TextAttributes; } // #included from: catch_console_colour.hpp #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED namespace Catch { struct Colour { enum Code { None = 0, White, Red, Green, Blue, Cyan, Yellow, Grey, Bright = 0x10, BrightRed = Bright | Red, BrightGreen = Bright | Green, LightGrey = Bright | Grey, BrightWhite = Bright | White, // By intention FileName = LightGrey, Warning = Yellow, ResultError = BrightRed, ResultSuccess = BrightGreen, ResultExpectedFailure = Warning, Error = BrightRed, Success = Green, OriginalExpression = Cyan, ReconstructedExpression = Yellow, SecondaryText = LightGrey, Headers = White }; // Use constructed object for RAII guard Colour( Code _colourCode ); Colour( Colour const& other ); ~Colour(); // Use static method for one-shot changes static void use( Code _colourCode ); private: bool m_moved; }; inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } } // end namespace Catch // #included from: catch_interfaces_reporter.h #define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED #include #include #include #include namespace Catch { struct ReporterConfig { explicit ReporterConfig( Ptr const& _fullConfig ) : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} std::ostream& stream() const { return *m_stream; } Ptr fullConfig() const { return m_fullConfig; } private: std::ostream* m_stream; Ptr m_fullConfig; }; struct ReporterPreferences { ReporterPreferences() : shouldRedirectStdOut( false ) {} bool shouldRedirectStdOut; }; template struct LazyStat : Option { LazyStat() : used( false ) {} LazyStat& operator=( T const& _value ) { Option::operator=( _value ); used = false; return *this; } void reset() { Option::reset(); used = false; } bool used; }; struct TestRunInfo { TestRunInfo( std::string const& _name ) : name( _name ) {} std::string name; }; struct GroupInfo { GroupInfo( std::string const& _name, std::size_t _groupIndex, std::size_t _groupsCount ) : name( _name ), groupIndex( _groupIndex ), groupsCounts( _groupsCount ) {} std::string name; std::size_t groupIndex; std::size_t groupsCounts; }; struct AssertionStats { AssertionStats( AssertionResult const& _assertionResult, std::vector const& _infoMessages, Totals const& _totals ) : assertionResult( _assertionResult ), infoMessages( _infoMessages ), totals( _totals ) { if( assertionResult.hasMessage() ) { // Copy message into messages list. // !TBD This should have been done earlier, somewhere MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); builder << assertionResult.getMessage(); builder.m_info.message = builder.m_stream.str(); infoMessages.push_back( builder.m_info ); } } virtual ~AssertionStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS AssertionStats( AssertionStats const& ) = default; AssertionStats( AssertionStats && ) = default; AssertionStats& operator = ( AssertionStats const& ) = default; AssertionStats& operator = ( AssertionStats && ) = default; # endif AssertionResult assertionResult; std::vector infoMessages; Totals totals; }; struct SectionStats { SectionStats( SectionInfo const& _sectionInfo, Counts const& _assertions, double _durationInSeconds, bool _missingAssertions ) : sectionInfo( _sectionInfo ), assertions( _assertions ), durationInSeconds( _durationInSeconds ), missingAssertions( _missingAssertions ) {} virtual ~SectionStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS SectionStats( SectionStats const& ) = default; SectionStats( SectionStats && ) = default; SectionStats& operator = ( SectionStats const& ) = default; SectionStats& operator = ( SectionStats && ) = default; # endif SectionInfo sectionInfo; Counts assertions; double durationInSeconds; bool missingAssertions; }; struct TestCaseStats { TestCaseStats( TestCaseInfo const& _testInfo, Totals const& _totals, std::string const& _stdOut, std::string const& _stdErr, bool _aborting ) : testInfo( _testInfo ), totals( _totals ), stdOut( _stdOut ), stdErr( _stdErr ), aborting( _aborting ) {} virtual ~TestCaseStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS TestCaseStats( TestCaseStats const& ) = default; TestCaseStats( TestCaseStats && ) = default; TestCaseStats& operator = ( TestCaseStats const& ) = default; TestCaseStats& operator = ( TestCaseStats && ) = default; # endif TestCaseInfo testInfo; Totals totals; std::string stdOut; std::string stdErr; bool aborting; }; struct TestGroupStats { TestGroupStats( GroupInfo const& _groupInfo, Totals const& _totals, bool _aborting ) : groupInfo( _groupInfo ), totals( _totals ), aborting( _aborting ) {} TestGroupStats( GroupInfo const& _groupInfo ) : groupInfo( _groupInfo ), aborting( false ) {} virtual ~TestGroupStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS TestGroupStats( TestGroupStats const& ) = default; TestGroupStats( TestGroupStats && ) = default; TestGroupStats& operator = ( TestGroupStats const& ) = default; TestGroupStats& operator = ( TestGroupStats && ) = default; # endif GroupInfo groupInfo; Totals totals; bool aborting; }; struct TestRunStats { TestRunStats( TestRunInfo const& _runInfo, Totals const& _totals, bool _aborting ) : runInfo( _runInfo ), totals( _totals ), aborting( _aborting ) {} virtual ~TestRunStats(); # ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS TestRunStats( TestRunStats const& _other ) : runInfo( _other.runInfo ), totals( _other.totals ), aborting( _other.aborting ) {} # else TestRunStats( TestRunStats const& ) = default; TestRunStats( TestRunStats && ) = default; TestRunStats& operator = ( TestRunStats const& ) = default; TestRunStats& operator = ( TestRunStats && ) = default; # endif TestRunInfo runInfo; Totals totals; bool aborting; }; class MultipleReporters; struct IStreamingReporter : IShared { virtual ~IStreamingReporter(); // Implementing class must also provide the following static method: // static std::string getDescription(); virtual ReporterPreferences getPreferences() const = 0; virtual void noMatchingTestCases( std::string const& spec ) = 0; virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; virtual void sectionEnded( SectionStats const& sectionStats ) = 0; virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; virtual void skipTest( TestCaseInfo const& testInfo ) = 0; virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; } }; struct IReporterFactory : IShared { virtual ~IReporterFactory(); virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; virtual std::string getDescription() const = 0; }; struct IReporterRegistry { typedef std::map > FactoryMap; typedef std::vector > Listeners; virtual ~IReporterRegistry(); virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; virtual FactoryMap const& getFactories() const = 0; virtual Listeners const& getListeners() const = 0; }; Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ); } #include #include namespace Catch { inline std::size_t listTests( Config const& config ) { TestSpec testSpec = config.testSpec(); if( config.testSpec().hasFilters() ) Catch::cout() << "Matching test cases:\n"; else { Catch::cout() << "All available test cases:\n"; testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::size_t matchedTests = 0; TextAttributes nameAttr, tagsAttr; nameAttr.setInitialIndent( 2 ).setIndent( 4 ); tagsAttr.setIndent( 6 ); std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { matchedTests++; TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); Colour::Code colour = testCaseInfo.isHidden() ? Colour::SecondaryText : Colour::None; Colour colourGuard( colour ); Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; if( !testCaseInfo.tags.empty() ) Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; } if( !config.testSpec().hasFilters() ) Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl; else Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; return matchedTests; } inline std::size_t listTestsNamesOnly( Config const& config ) { TestSpec testSpec = config.testSpec(); if( !config.testSpec().hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); std::size_t matchedTests = 0; std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { matchedTests++; TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); if( startsWith( testCaseInfo.name, "#" ) ) Catch::cout() << "\"" << testCaseInfo.name << "\"" << std::endl; else Catch::cout() << testCaseInfo.name << std::endl; } return matchedTests; } struct TagInfo { TagInfo() : count ( 0 ) {} void add( std::string const& spelling ) { ++count; spellings.insert( spelling ); } std::string all() const { std::string out; for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); it != itEnd; ++it ) out += "[" + *it + "]"; return out; } std::set spellings; std::size_t count; }; inline std::size_t listTags( Config const& config ) { TestSpec testSpec = config.testSpec(); if( config.testSpec().hasFilters() ) Catch::cout() << "Tags for matching test cases:\n"; else { Catch::cout() << "All available tags:\n"; testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::map tagCounts; std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), tagItEnd = it->getTestCaseInfo().tags.end(); tagIt != tagItEnd; ++tagIt ) { std::string tagName = *tagIt; std::string lcaseTagName = toLower( tagName ); std::map::iterator countIt = tagCounts.find( lcaseTagName ); if( countIt == tagCounts.end() ) countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; countIt->second.add( tagName ); } } for( std::map::const_iterator countIt = tagCounts.begin(), countItEnd = tagCounts.end(); countIt != countItEnd; ++countIt ) { std::ostringstream oss; oss << " " << std::setw(2) << countIt->second.count << " "; Text wrapper( countIt->second.all(), TextAttributes() .setInitialIndent( 0 ) .setIndent( oss.str().size() ) .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); Catch::cout() << oss.str() << wrapper << "\n"; } Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; return tagCounts.size(); } inline std::size_t listReporters( Config const& /*config*/ ) { Catch::cout() << "Available reporters:\n"; IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; std::size_t maxNameLen = 0; for(it = itBegin; it != itEnd; ++it ) maxNameLen = (std::max)( maxNameLen, it->first.size() ); for(it = itBegin; it != itEnd; ++it ) { Text wrapper( it->second->getDescription(), TextAttributes() .setInitialIndent( 0 ) .setIndent( 7+maxNameLen ) .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); Catch::cout() << " " << it->first << ":" << std::string( maxNameLen - it->first.size() + 2, ' ' ) << wrapper << "\n"; } Catch::cout() << std::endl; return factories.size(); } inline Option list( Config const& config ) { Option listedCount; if( config.listTests() ) listedCount = listedCount.valueOr(0) + listTests( config ); if( config.listTestNamesOnly() ) listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); if( config.listTags() ) listedCount = listedCount.valueOr(0) + listTags( config ); if( config.listReporters() ) listedCount = listedCount.valueOr(0) + listReporters( config ); return listedCount; } } // end namespace Catch // #included from: internal/catch_run_context.hpp #define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED // #included from: catch_test_case_tracker.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED #include #include #include #include namespace Catch { namespace TestCaseTracking { struct ITracker : SharedImpl<> { virtual ~ITracker(); // static queries virtual std::string name() const = 0; // dynamic queries virtual bool isComplete() const = 0; // Successfully completed or failed virtual bool isSuccessfullyCompleted() const = 0; virtual bool isOpen() const = 0; // Started but not complete virtual bool hasChildren() const = 0; virtual ITracker& parent() = 0; // actions virtual void close() = 0; // Successfully complete virtual void fail() = 0; virtual void markAsNeedingAnotherRun() = 0; virtual void addChild( Ptr const& child ) = 0; virtual ITracker* findChild( std::string const& name ) = 0; virtual void openChild() = 0; // Debug/ checking virtual bool isSectionTracker() const = 0; virtual bool isIndexTracker() const = 0; }; class TrackerContext { enum RunState { NotStarted, Executing, CompletedCycle }; Ptr m_rootTracker; ITracker* m_currentTracker; RunState m_runState; public: static TrackerContext& instance() { static TrackerContext s_instance; return s_instance; } TrackerContext() : m_currentTracker( CATCH_NULL ), m_runState( NotStarted ) {} ITracker& startRun(); void endRun() { m_rootTracker.reset(); m_currentTracker = CATCH_NULL; m_runState = NotStarted; } void startCycle() { m_currentTracker = m_rootTracker.get(); m_runState = Executing; } void completeCycle() { m_runState = CompletedCycle; } bool completedCycle() const { return m_runState == CompletedCycle; } ITracker& currentTracker() { return *m_currentTracker; } void setCurrentTracker( ITracker* tracker ) { m_currentTracker = tracker; } }; class TrackerBase : public ITracker { protected: enum CycleState { NotStarted, Executing, ExecutingChildren, NeedsAnotherRun, CompletedSuccessfully, Failed }; class TrackerHasName { std::string m_name; public: TrackerHasName( std::string const& name ) : m_name( name ) {} bool operator ()( Ptr const& tracker ) { return tracker->name() == m_name; } }; typedef std::vector > Children; std::string m_name; TrackerContext& m_ctx; ITracker* m_parent; Children m_children; CycleState m_runState; public: TrackerBase( std::string const& name, TrackerContext& ctx, ITracker* parent ) : m_name( name ), m_ctx( ctx ), m_parent( parent ), m_runState( NotStarted ) {} virtual ~TrackerBase(); virtual std::string name() const CATCH_OVERRIDE { return m_name; } virtual bool isComplete() const CATCH_OVERRIDE { return m_runState == CompletedSuccessfully || m_runState == Failed; } virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE { return m_runState == CompletedSuccessfully; } virtual bool isOpen() const CATCH_OVERRIDE { return m_runState != NotStarted && !isComplete(); } virtual bool hasChildren() const CATCH_OVERRIDE { return !m_children.empty(); } virtual void addChild( Ptr const& child ) CATCH_OVERRIDE { m_children.push_back( child ); } virtual ITracker* findChild( std::string const& name ) CATCH_OVERRIDE { Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( name ) ); return( it != m_children.end() ) ? it->get() : CATCH_NULL; } virtual ITracker& parent() CATCH_OVERRIDE { assert( m_parent ); // Should always be non-null except for root return *m_parent; } virtual void openChild() CATCH_OVERRIDE { if( m_runState != ExecutingChildren ) { m_runState = ExecutingChildren; if( m_parent ) m_parent->openChild(); } } virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; } virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; } void open() { m_runState = Executing; moveToThis(); if( m_parent ) m_parent->openChild(); } virtual void close() CATCH_OVERRIDE { // Close any still open children (e.g. generators) while( &m_ctx.currentTracker() != this ) m_ctx.currentTracker().close(); switch( m_runState ) { case NotStarted: case CompletedSuccessfully: case Failed: throw std::logic_error( "Illogical state" ); case NeedsAnotherRun: break;; case Executing: m_runState = CompletedSuccessfully; break; case ExecutingChildren: if( m_children.empty() || m_children.back()->isComplete() ) m_runState = CompletedSuccessfully; break; default: throw std::logic_error( "Unexpected state" ); } moveToParent(); m_ctx.completeCycle(); } virtual void fail() CATCH_OVERRIDE { m_runState = Failed; if( m_parent ) m_parent->markAsNeedingAnotherRun(); moveToParent(); m_ctx.completeCycle(); } virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE { m_runState = NeedsAnotherRun; } private: void moveToParent() { assert( m_parent ); m_ctx.setCurrentTracker( m_parent ); } void moveToThis() { m_ctx.setCurrentTracker( this ); } }; class SectionTracker : public TrackerBase { public: SectionTracker( std::string const& name, TrackerContext& ctx, ITracker* parent ) : TrackerBase( name, ctx, parent ) {} virtual ~SectionTracker(); virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; } static SectionTracker& acquire( TrackerContext& ctx, std::string const& name ) { SectionTracker* section = CATCH_NULL; ITracker& currentTracker = ctx.currentTracker(); if( ITracker* childTracker = currentTracker.findChild( name ) ) { assert( childTracker ); assert( childTracker->isSectionTracker() ); section = static_cast( childTracker ); } else { section = new SectionTracker( name, ctx, ¤tTracker ); currentTracker.addChild( section ); } if( !ctx.completedCycle() && !section->isComplete() ) { section->open(); } return *section; } }; class IndexTracker : public TrackerBase { int m_size; int m_index; public: IndexTracker( std::string const& name, TrackerContext& ctx, ITracker* parent, int size ) : TrackerBase( name, ctx, parent ), m_size( size ), m_index( -1 ) {} virtual ~IndexTracker(); virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; } static IndexTracker& acquire( TrackerContext& ctx, std::string const& name, int size ) { IndexTracker* tracker = CATCH_NULL; ITracker& currentTracker = ctx.currentTracker(); if( ITracker* childTracker = currentTracker.findChild( name ) ) { assert( childTracker ); assert( childTracker->isIndexTracker() ); tracker = static_cast( childTracker ); } else { tracker = new IndexTracker( name, ctx, ¤tTracker, size ); currentTracker.addChild( tracker ); } if( !ctx.completedCycle() && !tracker->isComplete() ) { if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) tracker->moveNext(); tracker->open(); } return *tracker; } int index() const { return m_index; } void moveNext() { m_index++; m_children.clear(); } virtual void close() CATCH_OVERRIDE { TrackerBase::close(); if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) m_runState = Executing; } }; inline ITracker& TrackerContext::startRun() { m_rootTracker = new SectionTracker( "{root}", *this, CATCH_NULL ); m_currentTracker = CATCH_NULL; m_runState = Executing; return *m_rootTracker; } } // namespace TestCaseTracking using TestCaseTracking::ITracker; using TestCaseTracking::TrackerContext; using TestCaseTracking::SectionTracker; using TestCaseTracking::IndexTracker; } // namespace Catch // #included from: catch_fatal_condition.hpp #define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED namespace Catch { // Report the error condition then exit the process inline void fatal( std::string const& message, int exitCode ) { IContext& context = Catch::getCurrentContext(); IResultCapture* resultCapture = context.getResultCapture(); resultCapture->handleFatalErrorCondition( message ); if( Catch::alwaysTrue() ) // avoids "no return" warnings exit( exitCode ); } } // namespace Catch #if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// namespace Catch { struct FatalConditionHandler { void reset() {} }; } // namespace Catch #else // Not Windows - assumed to be POSIX compatible ////////////////////////// #include namespace Catch { struct SignalDefs { int id; const char* name; }; extern SignalDefs signalDefs[]; SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, { SIGFPE, "SIGFPE - Floating point error signal" }, { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, { SIGTERM, "SIGTERM - Termination request signal" }, { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } }; struct FatalConditionHandler { static void handleSignal( int sig ) { for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) if( sig == signalDefs[i].id ) fatal( signalDefs[i].name, -sig ); fatal( "", -sig ); } FatalConditionHandler() : m_isSet( true ) { for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) signal( signalDefs[i].id, handleSignal ); } ~FatalConditionHandler() { reset(); } void reset() { if( m_isSet ) { for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) signal( signalDefs[i].id, SIG_DFL ); m_isSet = false; } } bool m_isSet; }; } // namespace Catch #endif // not Windows #include #include namespace Catch { class StreamRedirect { public: StreamRedirect( std::ostream& stream, std::string& targetString ) : m_stream( stream ), m_prevBuf( stream.rdbuf() ), m_targetString( targetString ) { stream.rdbuf( m_oss.rdbuf() ); } ~StreamRedirect() { m_targetString += m_oss.str(); m_stream.rdbuf( m_prevBuf ); } private: std::ostream& m_stream; std::streambuf* m_prevBuf; std::ostringstream m_oss; std::string& m_targetString; }; /////////////////////////////////////////////////////////////////////////// class RunContext : public IResultCapture, public IRunner { RunContext( RunContext const& ); void operator =( RunContext const& ); public: explicit RunContext( Ptr const& _config, Ptr const& reporter ) : m_runInfo( _config->name() ), m_context( getCurrentMutableContext() ), m_activeTestCase( CATCH_NULL ), m_config( _config ), m_reporter( reporter ) { m_context.setRunner( this ); m_context.setConfig( m_config ); m_context.setResultCapture( this ); m_reporter->testRunStarting( m_runInfo ); } virtual ~RunContext() { m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); } void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); } void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); } Totals runTest( TestCase const& testCase ) { Totals prevTotals = m_totals; std::string redirectedCout; std::string redirectedCerr; TestCaseInfo testInfo = testCase.getTestCaseInfo(); m_reporter->testCaseStarting( testInfo ); m_activeTestCase = &testCase; do { m_trackerContext.startRun(); do { m_trackerContext.startCycle(); m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, testInfo.name ); runCurrentTest( redirectedCout, redirectedCerr ); } while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() ); } // !TBD: deprecated - this will be replaced by indexed trackers while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); Totals deltaTotals = m_totals.delta( prevTotals ); if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) { deltaTotals.assertions.failed++; deltaTotals.testCases.passed--; deltaTotals.testCases.failed++; } m_totals.testCases += deltaTotals.testCases; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, redirectedCout, redirectedCerr, aborting() ) ); m_activeTestCase = CATCH_NULL; m_testCaseTracker = CATCH_NULL; return deltaTotals; } Ptr config() const { return m_config; } private: // IResultCapture virtual void assertionEnded( AssertionResult const& result ) { if( result.getResultType() == ResultWas::Ok ) { m_totals.assertions.passed++; } else if( !result.isOk() ) { m_totals.assertions.failed++; } if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) m_messages.clear(); // Reset working state m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); m_lastResult = result; } virtual bool sectionStarted ( SectionInfo const& sectionInfo, Counts& assertions ) { std::ostringstream oss; oss << sectionInfo.name << "@" << sectionInfo.lineInfo; ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, oss.str() ); if( !sectionTracker.isOpen() ) return false; m_activeSections.push_back( §ionTracker ); m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; m_reporter->sectionStarting( sectionInfo ); assertions = m_totals.assertions; return true; } bool testForMissingAssertions( Counts& assertions ) { if( assertions.total() != 0 ) return false; if( !m_config->warnAboutMissingAssertions() ) return false; if( m_trackerContext.currentTracker().hasChildren() ) return false; m_totals.assertions.failed++; assertions.failed++; return true; } virtual void sectionEnded( SectionEndInfo const& endInfo ) { Counts assertions = m_totals.assertions - endInfo.prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); if( !m_activeSections.empty() ) { m_activeSections.back()->close(); m_activeSections.pop_back(); } m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) ); m_messages.clear(); } virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) { if( m_unfinishedSections.empty() ) m_activeSections.back()->fail(); else m_activeSections.back()->close(); m_activeSections.pop_back(); m_unfinishedSections.push_back( endInfo ); } virtual void pushScopedMessage( MessageInfo const& message ) { m_messages.push_back( message ); } virtual void popScopedMessage( MessageInfo const& message ) { m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); } virtual std::string getCurrentTestName() const { return m_activeTestCase ? m_activeTestCase->getTestCaseInfo().name : ""; } virtual const AssertionResult* getLastResult() const { return &m_lastResult; } virtual void handleFatalErrorCondition( std::string const& message ) { ResultBuilder resultBuilder = makeUnexpectedResultBuilder(); resultBuilder.setResultType( ResultWas::FatalErrorCondition ); resultBuilder << message; resultBuilder.captureExpression(); handleUnfinishedSections(); // Recreate section for test case (as we will lose the one that was in scope) TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); Counts assertions; assertions.failed = 1; SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); m_reporter->sectionEnded( testCaseSectionStats ); TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); Totals deltaTotals; deltaTotals.testCases.failed = 1; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, "", "", false ) ); m_totals.testCases.failed++; testGroupEnded( "", m_totals, 1, 1 ); m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); } public: // !TBD We need to do this another way! bool aborting() const { return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); } private: void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); m_reporter->sectionStarting( testCaseSection ); Counts prevAssertions = m_totals.assertions; double duration = 0; try { m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); seedRng( *m_config ); Timer timer; timer.start(); if( m_reporter->getPreferences().shouldRedirectStdOut ) { StreamRedirect coutRedir( Catch::cout(), redirectedCout ); StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); invokeActiveTestCase(); } else { invokeActiveTestCase(); } duration = timer.getElapsedSeconds(); } catch( TestFailureException& ) { // This just means the test was aborted due to failure } catch(...) { makeUnexpectedResultBuilder().useActiveException(); } m_testCaseTracker->close(); handleUnfinishedSections(); m_messages.clear(); Counts assertions = m_totals.assertions - prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); if( testCaseInfo.okToFail() ) { std::swap( assertions.failedButOk, assertions.failed ); m_totals.assertions.failed -= assertions.failedButOk; m_totals.assertions.failedButOk += assertions.failedButOk; } SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); m_reporter->sectionEnded( testCaseSectionStats ); } void invokeActiveTestCase() { FatalConditionHandler fatalConditionHandler; // Handle signals m_activeTestCase->invoke(); fatalConditionHandler.reset(); } private: ResultBuilder makeUnexpectedResultBuilder() const { return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), m_lastAssertionInfo.lineInfo, m_lastAssertionInfo.capturedExpression.c_str(), m_lastAssertionInfo.resultDisposition ); } void handleUnfinishedSections() { // If sections ended prematurely due to an exception we stored their // infos here so we can tear them down outside the unwind process. for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), itEnd = m_unfinishedSections.rend(); it != itEnd; ++it ) sectionEnded( *it ); m_unfinishedSections.clear(); } TestRunInfo m_runInfo; IMutableContext& m_context; TestCase const* m_activeTestCase; ITracker* m_testCaseTracker; ITracker* m_currentSectionTracker; AssertionResult m_lastResult; Ptr m_config; Totals m_totals; Ptr m_reporter; std::vector m_messages; AssertionInfo m_lastAssertionInfo; std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; }; IResultCapture& getResultCapture() { if( IResultCapture* capture = getCurrentContext().getResultCapture() ) return *capture; else throw std::logic_error( "No result capture instance" ); } } // end namespace Catch // #included from: internal/catch_version.h #define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED namespace Catch { // Versioning information struct Version { Version( unsigned int _majorVersion, unsigned int _minorVersion, unsigned int _patchNumber, std::string const& _branchName, unsigned int _buildNumber ); unsigned int const majorVersion; unsigned int const minorVersion; unsigned int const patchNumber; // buildNumber is only used if branchName is not null std::string const branchName; unsigned int const buildNumber; friend std::ostream& operator << ( std::ostream& os, Version const& version ); private: void operator=( Version const& ); }; extern Version libraryVersion; } #include #include #include namespace Catch { Ptr createReporter( std::string const& reporterName, Ptr const& config ) { Ptr reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() ); if( !reporter ) { std::ostringstream oss; oss << "No reporter registered with name: '" << reporterName << "'"; throw std::domain_error( oss.str() ); } return reporter; } Ptr makeReporter( Ptr const& config ) { std::vector reporters = config->getReporterNames(); if( reporters.empty() ) reporters.push_back( "console" ); Ptr reporter; for( std::vector::const_iterator it = reporters.begin(), itEnd = reporters.end(); it != itEnd; ++it ) reporter = addReporter( reporter, createReporter( *it, config ) ); return reporter; } Ptr addListeners( Ptr const& config, Ptr reporters ) { IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners(); for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end(); it != itEnd; ++it ) reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) ); return reporters; } Totals runTests( Ptr const& config ) { Ptr iconfig = config.get(); Ptr reporter = makeReporter( config ); reporter = addListeners( iconfig, reporter ); RunContext context( iconfig, reporter ); Totals totals; context.testGroupStarting( config->name(), 1, 1 ); TestSpec testSpec = config->testSpec(); if( !testSpec.hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests std::vector const& allTestCases = getAllTestCasesSorted( *iconfig ); for( std::vector::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end(); it != itEnd; ++it ) { if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) ) totals += context.runTest( *it ); else reporter->skipTest( *it ); } context.testGroupEnded( iconfig->name(), totals, 1, 1 ); return totals; } void applyFilenamesAsTags( IConfig const& config ) { std::vector const& tests = getAllTestCasesSorted( config ); for(std::size_t i = 0; i < tests.size(); ++i ) { TestCase& test = const_cast( tests[i] ); std::set tags = test.tags; std::string filename = test.lineInfo.file; std::string::size_type lastSlash = filename.find_last_of( "\\/" ); if( lastSlash != std::string::npos ) filename = filename.substr( lastSlash+1 ); std::string::size_type lastDot = filename.find_last_of( "." ); if( lastDot != std::string::npos ) filename = filename.substr( 0, lastDot ); tags.insert( "#" + filename ); setTags( test, tags ); } } class Session : NonCopyable { static bool alreadyInstantiated; public: struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; Session() : m_cli( makeCommandLineParser() ) { if( alreadyInstantiated ) { std::string msg = "Only one instance of Catch::Session can ever be used"; Catch::cerr() << msg << std::endl; throw std::logic_error( msg ); } alreadyInstantiated = true; } ~Session() { Catch::cleanUp(); } void showHelp( std::string const& processName ) { Catch::cout() << "\nCatch v" << libraryVersion << "\n"; m_cli.usage( Catch::cout(), processName ); Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; } int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { try { m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData ); if( m_configData.showHelp ) showHelp( m_configData.processName ); m_config.reset(); } catch( std::exception& ex ) { { Colour colourGuard( Colour::Red ); Catch::cerr() << "\nError(s) in input:\n" << Text( ex.what(), TextAttributes().setIndent(2) ) << "\n\n"; } m_cli.usage( Catch::cout(), m_configData.processName ); return (std::numeric_limits::max)(); } return 0; } void useConfigData( ConfigData const& _configData ) { m_configData = _configData; m_config.reset(); } int run( int argc, char const* const* const argv ) { int returnCode = applyCommandLine( argc, argv ); if( returnCode == 0 ) returnCode = run(); return returnCode; } int run() { if( m_configData.showHelp ) return 0; try { config(); // Force config to be constructed seedRng( *m_config ); if( m_configData.filenamesAsTags ) applyFilenamesAsTags( *m_config ); // Handle list request if( Option listed = list( config() ) ) return static_cast( *listed ); return static_cast( runTests( m_config ).assertions.failed ); } catch( std::exception& ex ) { Catch::cerr() << ex.what() << std::endl; return (std::numeric_limits::max)(); } } Clara::CommandLine const& cli() const { return m_cli; } std::vector const& unusedTokens() const { return m_unusedTokens; } ConfigData& configData() { return m_configData; } Config& config() { if( !m_config ) m_config = new Config( m_configData ); return *m_config; } private: Clara::CommandLine m_cli; std::vector m_unusedTokens; ConfigData m_configData; Ptr m_config; }; bool Session::alreadyInstantiated = false; } // end namespace Catch // #included from: catch_registry_hub.hpp #define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED // #included from: catch_test_case_registry_impl.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED #include #include #include #include #include #ifdef CATCH_CPP14_OR_GREATER #include #endif namespace Catch { struct RandomNumberGenerator { typedef std::ptrdiff_t result_type; result_type operator()( result_type n ) const { return std::rand() % n; } #ifdef CATCH_CPP14_OR_GREATER static constexpr result_type min() { return 0; } static constexpr result_type max() { return 1000000; } result_type operator()() const { return std::rand() % max(); } #endif template static void shuffle( V& vector ) { RandomNumberGenerator rng; #ifdef CATCH_CPP14_OR_GREATER std::shuffle( vector.begin(), vector.end(), rng ); #else std::random_shuffle( vector.begin(), vector.end(), rng ); #endif } }; inline std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { std::vector sorted = unsortedTestCases; switch( config.runOrder() ) { case RunTests::InLexicographicalOrder: std::sort( sorted.begin(), sorted.end() ); break; case RunTests::InRandomOrder: { seedRng( config ); RandomNumberGenerator::shuffle( sorted ); } break; case RunTests::InDeclarationOrder: // already in declaration order break; } return sorted; } bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); } void enforceNoDuplicateTestCases( std::vector const& functions ) { std::set seenFunctions; for( std::vector::const_iterator it = functions.begin(), itEnd = functions.end(); it != itEnd; ++it ) { std::pair::const_iterator, bool> prev = seenFunctions.insert( *it ); if( !prev.second ) { std::ostringstream ss; ss << Colour( Colour::Red ) << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; throw std::runtime_error(ss.str()); } } } std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { std::vector filtered; filtered.reserve( testCases.size() ); for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); it != itEnd; ++it ) if( matchTest( *it, testSpec, config ) ) filtered.push_back( *it ); return filtered; } std::vector const& getAllTestCasesSorted( IConfig const& config ) { return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); } class TestRegistry : public ITestCaseRegistry { public: TestRegistry() : m_currentSortOrder( RunTests::InDeclarationOrder ), m_unnamedCount( 0 ) {} virtual ~TestRegistry(); virtual void registerTest( TestCase const& testCase ) { std::string name = testCase.getTestCaseInfo().name; if( name == "" ) { std::ostringstream oss; oss << "Anonymous test case " << ++m_unnamedCount; return registerTest( testCase.withName( oss.str() ) ); } m_functions.push_back( testCase ); } virtual std::vector const& getAllTests() const { return m_functions; } virtual std::vector const& getAllTestsSorted( IConfig const& config ) const { if( m_sortedFunctions.empty() ) enforceNoDuplicateTestCases( m_functions ); if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { m_sortedFunctions = sortTests( config, m_functions ); m_currentSortOrder = config.runOrder(); } return m_sortedFunctions; } private: std::vector m_functions; mutable RunTests::InWhatOrder m_currentSortOrder; mutable std::vector m_sortedFunctions; size_t m_unnamedCount; std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised }; /////////////////////////////////////////////////////////////////////////// class FreeFunctionTestCase : public SharedImpl { public: FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} virtual void invoke() const { m_fun(); } private: virtual ~FreeFunctionTestCase(); TestFunction m_fun; }; inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { std::string className = classOrQualifiedMethodName; if( startsWith( className, "&" ) ) { std::size_t lastColons = className.rfind( "::" ); std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); if( penultimateColons == std::string::npos ) penultimateColons = 1; className = className.substr( penultimateColons, lastColons-penultimateColons ); } return className; } void registerTestCase ( ITestCase* testCase, char const* classOrQualifiedMethodName, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ) { getMutableRegistryHub().registerTest ( makeTestCase ( testCase, extractClassName( classOrQualifiedMethodName ), nameAndDesc.name, nameAndDesc.description, lineInfo ) ); } void registerTestCaseFunction ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ) { registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); } /////////////////////////////////////////////////////////////////////////// AutoReg::AutoReg ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ) { registerTestCaseFunction( function, lineInfo, nameAndDesc ); } AutoReg::~AutoReg() {} } // end namespace Catch // #included from: catch_reporter_registry.hpp #define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED #include namespace Catch { class ReporterRegistry : public IReporterRegistry { public: virtual ~ReporterRegistry() CATCH_OVERRIDE {} virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const CATCH_OVERRIDE { FactoryMap::const_iterator it = m_factories.find( name ); if( it == m_factories.end() ) return CATCH_NULL; return it->second->create( ReporterConfig( config ) ); } void registerReporter( std::string const& name, Ptr const& factory ) { m_factories.insert( std::make_pair( name, factory ) ); } void registerListener( Ptr const& factory ) { m_listeners.push_back( factory ); } virtual FactoryMap const& getFactories() const CATCH_OVERRIDE { return m_factories; } virtual Listeners const& getListeners() const CATCH_OVERRIDE { return m_listeners; } private: FactoryMap m_factories; Listeners m_listeners; }; } // #included from: catch_exception_translator_registry.hpp #define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED #ifdef __OBJC__ #import "Foundation/Foundation.h" #endif namespace Catch { class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { public: ~ExceptionTranslatorRegistry() { deleteAll( m_translators ); } virtual void registerTranslator( const IExceptionTranslator* translator ) { m_translators.push_back( translator ); } virtual std::string translateActiveException() const { try { #ifdef __OBJC__ // In Objective-C try objective-c exceptions first @try { return tryTranslators(); } @catch (NSException *exception) { return Catch::toString( [exception description] ); } #else return tryTranslators(); #endif } catch( TestFailureException& ) { throw; } catch( std::exception& ex ) { return ex.what(); } catch( std::string& msg ) { return msg; } catch( const char* msg ) { return msg; } catch(...) { return "Unknown exception"; } } std::string tryTranslators() const { if( m_translators.empty() ) throw; else return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); } private: std::vector m_translators; }; } namespace Catch { namespace { class RegistryHub : public IRegistryHub, public IMutableRegistryHub { RegistryHub( RegistryHub const& ); void operator=( RegistryHub const& ); public: // IRegistryHub RegistryHub() { } virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE { return m_reporterRegistry; } virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE { return m_testCaseRegistry; } virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE { return m_exceptionTranslatorRegistry; } public: // IMutableRegistryHub virtual void registerReporter( std::string const& name, Ptr const& factory ) CATCH_OVERRIDE { m_reporterRegistry.registerReporter( name, factory ); } virtual void registerListener( Ptr const& factory ) CATCH_OVERRIDE { m_reporterRegistry.registerListener( factory ); } virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE { m_testCaseRegistry.registerTest( testInfo ); } virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE { m_exceptionTranslatorRegistry.registerTranslator( translator ); } private: TestRegistry m_testCaseRegistry; ReporterRegistry m_reporterRegistry; ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; }; // Single, global, instance inline RegistryHub*& getTheRegistryHub() { static RegistryHub* theRegistryHub = CATCH_NULL; if( !theRegistryHub ) theRegistryHub = new RegistryHub(); return theRegistryHub; } } IRegistryHub& getRegistryHub() { return *getTheRegistryHub(); } IMutableRegistryHub& getMutableRegistryHub() { return *getTheRegistryHub(); } void cleanUp() { delete getTheRegistryHub(); getTheRegistryHub() = CATCH_NULL; cleanUpContext(); } std::string translateActiveException() { return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); } } // end namespace Catch // #included from: catch_notimplemented_exception.hpp #define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED #include namespace Catch { NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) : m_lineInfo( lineInfo ) { std::ostringstream oss; oss << lineInfo << ": function "; oss << "not implemented"; m_what = oss.str(); } const char* NotImplementedException::what() const CATCH_NOEXCEPT { return m_what.c_str(); } } // end namespace Catch // #included from: catch_context_impl.hpp #define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED // #included from: catch_stream.hpp #define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED #include #include #include namespace Catch { template class StreamBufImpl : public StreamBufBase { char data[bufferSize]; WriterF m_writer; public: StreamBufImpl() { setp( data, data + sizeof(data) ); } ~StreamBufImpl() CATCH_NOEXCEPT { sync(); } private: int overflow( int c ) { sync(); if( c != EOF ) { if( pbase() == epptr() ) m_writer( std::string( 1, static_cast( c ) ) ); else sputc( static_cast( c ) ); } return 0; } int sync() { if( pbase() != pptr() ) { m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); setp( pbase(), epptr() ); } return 0; } }; /////////////////////////////////////////////////////////////////////////// FileStream::FileStream( std::string const& filename ) { m_ofs.open( filename.c_str() ); if( m_ofs.fail() ) { std::ostringstream oss; oss << "Unable to open file: '" << filename << "'"; throw std::domain_error( oss.str() ); } } std::ostream& FileStream::stream() const { return m_ofs; } struct OutputDebugWriter { void operator()( std::string const&str ) { writeToDebugConsole( str ); } }; DebugOutStream::DebugOutStream() : m_streamBuf( new StreamBufImpl() ), m_os( m_streamBuf.get() ) {} std::ostream& DebugOutStream::stream() const { return m_os; } // Store the streambuf from cout up-front because // cout may get redirected when running tests CoutStream::CoutStream() : m_os( Catch::cout().rdbuf() ) {} std::ostream& CoutStream::stream() const { return m_os; } #ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions std::ostream& cout() { return std::cout; } std::ostream& cerr() { return std::cerr; } #endif } namespace Catch { class Context : public IMutableContext { Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {} Context( Context const& ); void operator=( Context const& ); public: // IContext virtual IResultCapture* getResultCapture() { return m_resultCapture; } virtual IRunner* getRunner() { return m_runner; } virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { return getGeneratorsForCurrentTest() .getGeneratorInfo( fileInfo, totalSize ) .getCurrentIndex(); } virtual bool advanceGeneratorsForCurrentTest() { IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); return generators && generators->moveNext(); } virtual Ptr getConfig() const { return m_config; } public: // IMutableContext virtual void setResultCapture( IResultCapture* resultCapture ) { m_resultCapture = resultCapture; } virtual void setRunner( IRunner* runner ) { m_runner = runner; } virtual void setConfig( Ptr const& config ) { m_config = config; } friend IMutableContext& getCurrentMutableContext(); private: IGeneratorsForTest* findGeneratorsForCurrentTest() { std::string testName = getResultCapture()->getCurrentTestName(); std::map::const_iterator it = m_generatorsByTestName.find( testName ); return it != m_generatorsByTestName.end() ? it->second : CATCH_NULL; } IGeneratorsForTest& getGeneratorsForCurrentTest() { IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); if( !generators ) { std::string testName = getResultCapture()->getCurrentTestName(); generators = createGeneratorsForTest(); m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); } return *generators; } private: Ptr m_config; IRunner* m_runner; IResultCapture* m_resultCapture; std::map m_generatorsByTestName; }; namespace { Context* currentContext = CATCH_NULL; } IMutableContext& getCurrentMutableContext() { if( !currentContext ) currentContext = new Context(); return *currentContext; } IContext& getCurrentContext() { return getCurrentMutableContext(); } void cleanUpContext() { delete currentContext; currentContext = CATCH_NULL; } } // #included from: catch_console_colour_impl.hpp #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED namespace Catch { namespace { struct IColourImpl { virtual ~IColourImpl() {} virtual void use( Colour::Code _colourCode ) = 0; }; struct NoColourImpl : IColourImpl { void use( Colour::Code ) {} static IColourImpl* instance() { static NoColourImpl s_instance; return &s_instance; } }; } // anon namespace } // namespace Catch #if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) # ifdef CATCH_PLATFORM_WINDOWS # define CATCH_CONFIG_COLOUR_WINDOWS # else # define CATCH_CONFIG_COLOUR_ANSI # endif #endif #if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// #ifndef NOMINMAX #define NOMINMAX #endif #ifdef __AFXDLL #include #else #include #endif namespace Catch { namespace { class Win32ColourImpl : public IColourImpl { public: Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) { CONSOLE_SCREEN_BUFFER_INFO csbiInfo; GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); } virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { case Colour::None: return setTextAttribute( originalForegroundAttributes ); case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Red: return setTextAttribute( FOREGROUND_RED ); case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); case Colour::Grey: return setTextAttribute( 0 ); case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Bright: throw std::logic_error( "not a colour" ); } } private: void setTextAttribute( WORD _textAttribute ) { SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); } HANDLE stdoutHandle; WORD originalForegroundAttributes; WORD originalBackgroundAttributes; }; IColourImpl* platformColourInstance() { static Win32ColourImpl s_instance; Ptr config = getCurrentContext().getConfig(); UseColour::YesOrNo colourMode = config ? config->useColour() : UseColour::Auto; if( colourMode == UseColour::Auto ) colourMode = !isDebuggerActive() ? UseColour::Yes : UseColour::No; return colourMode == UseColour::Yes ? &s_instance : NoColourImpl::instance(); } } // end anon namespace } // end namespace Catch #elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// #include namespace Catch { namespace { // use POSIX/ ANSI console terminal codes // Thanks to Adam Strzelecki for original contribution // (http://github.com/nanoant) // https://github.com/philsquared/Catch/pull/131 class PosixColourImpl : public IColourImpl { public: virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { case Colour::None: case Colour::White: return setColour( "[0m" ); case Colour::Red: return setColour( "[0;31m" ); case Colour::Green: return setColour( "[0;32m" ); case Colour::Blue: return setColour( "[0:34m" ); case Colour::Cyan: return setColour( "[0;36m" ); case Colour::Yellow: return setColour( "[0;33m" ); case Colour::Grey: return setColour( "[1;30m" ); case Colour::LightGrey: return setColour( "[0;37m" ); case Colour::BrightRed: return setColour( "[1;31m" ); case Colour::BrightGreen: return setColour( "[1;32m" ); case Colour::BrightWhite: return setColour( "[1;37m" ); case Colour::Bright: throw std::logic_error( "not a colour" ); } } static IColourImpl* instance() { static PosixColourImpl s_instance; return &s_instance; } private: void setColour( const char* _escapeCode ) { Catch::cout() << '\033' << _escapeCode; } }; IColourImpl* platformColourInstance() { Ptr config = getCurrentContext().getConfig(); UseColour::YesOrNo colourMode = config ? config->useColour() : UseColour::Auto; if( colourMode == UseColour::Auto ) colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) ) ? UseColour::Yes : UseColour::No; return colourMode == UseColour::Yes ? PosixColourImpl::instance() : NoColourImpl::instance(); } } // end anon namespace } // end namespace Catch #else // not Windows or ANSI /////////////////////////////////////////////// namespace Catch { static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } } // end namespace Catch #endif // Windows/ ANSI/ None namespace Catch { Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } Colour::~Colour(){ if( !m_moved ) use( None ); } void Colour::use( Code _colourCode ) { static IColourImpl* impl = platformColourInstance(); impl->use( _colourCode ); } } // end namespace Catch // #included from: catch_generators_impl.hpp #define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED #include #include #include namespace Catch { struct GeneratorInfo : IGeneratorInfo { GeneratorInfo( std::size_t size ) : m_size( size ), m_currentIndex( 0 ) {} bool moveNext() { if( ++m_currentIndex == m_size ) { m_currentIndex = 0; return false; } return true; } std::size_t getCurrentIndex() const { return m_currentIndex; } std::size_t m_size; std::size_t m_currentIndex; }; /////////////////////////////////////////////////////////////////////////// class GeneratorsForTest : public IGeneratorsForTest { public: ~GeneratorsForTest() { deleteAll( m_generatorsInOrder ); } IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { std::map::const_iterator it = m_generatorsByName.find( fileInfo ); if( it == m_generatorsByName.end() ) { IGeneratorInfo* info = new GeneratorInfo( size ); m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); m_generatorsInOrder.push_back( info ); return *info; } return *it->second; } bool moveNext() { std::vector::const_iterator it = m_generatorsInOrder.begin(); std::vector::const_iterator itEnd = m_generatorsInOrder.end(); for(; it != itEnd; ++it ) { if( (*it)->moveNext() ) return true; } return false; } private: std::map m_generatorsByName; std::vector m_generatorsInOrder; }; IGeneratorsForTest* createGeneratorsForTest() { return new GeneratorsForTest(); } } // end namespace Catch // #included from: catch_assertionresult.hpp #define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED namespace Catch { AssertionInfo::AssertionInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, std::string const& _capturedExpression, ResultDisposition::Flags _resultDisposition ) : macroName( _macroName ), lineInfo( _lineInfo ), capturedExpression( _capturedExpression ), resultDisposition( _resultDisposition ) {} AssertionResult::AssertionResult() {} AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) : m_info( info ), m_resultData( data ) {} AssertionResult::~AssertionResult() {} // Result was a success bool AssertionResult::succeeded() const { return Catch::isOk( m_resultData.resultType ); } // Result was a success, or failure is suppressed bool AssertionResult::isOk() const { return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); } ResultWas::OfType AssertionResult::getResultType() const { return m_resultData.resultType; } bool AssertionResult::hasExpression() const { return !m_info.capturedExpression.empty(); } bool AssertionResult::hasMessage() const { return !m_resultData.message.empty(); } std::string AssertionResult::getExpression() const { if( isFalseTest( m_info.resultDisposition ) ) return "!" + m_info.capturedExpression; else return m_info.capturedExpression; } std::string AssertionResult::getExpressionInMacro() const { if( m_info.macroName.empty() ) return m_info.capturedExpression; else return m_info.macroName + "( " + m_info.capturedExpression + " )"; } bool AssertionResult::hasExpandedExpression() const { return hasExpression() && getExpandedExpression() != getExpression(); } std::string AssertionResult::getExpandedExpression() const { return m_resultData.reconstructedExpression; } std::string AssertionResult::getMessage() const { return m_resultData.message; } SourceLineInfo AssertionResult::getSourceInfo() const { return m_info.lineInfo; } std::string AssertionResult::getTestMacroName() const { return m_info.macroName; } } // end namespace Catch // #included from: catch_test_case_info.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED namespace Catch { inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { if( startsWith( tag, "." ) || tag == "hide" || tag == "!hide" ) return TestCaseInfo::IsHidden; else if( tag == "!throws" ) return TestCaseInfo::Throws; else if( tag == "!shouldfail" ) return TestCaseInfo::ShouldFail; else if( tag == "!mayfail" ) return TestCaseInfo::MayFail; else return TestCaseInfo::None; } inline bool isReservedTag( std::string const& tag ) { return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); } inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { if( isReservedTag( tag ) ) { { Colour colourGuard( Colour::Red ); Catch::cerr() << "Tag name [" << tag << "] not allowed.\n" << "Tag names starting with non alpha-numeric characters are reserved\n"; } { Colour colourGuard( Colour::FileName ); Catch::cerr() << _lineInfo << std::endl; } exit(1); } } TestCase makeTestCase( ITestCase* _testCase, std::string const& _className, std::string const& _name, std::string const& _descOrTags, SourceLineInfo const& _lineInfo ) { bool isHidden( startsWith( _name, "./" ) ); // Legacy support // Parse out tags std::set tags; std::string desc, tag; bool inTag = false; for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { char c = _descOrTags[i]; if( !inTag ) { if( c == '[' ) inTag = true; else desc += c; } else { if( c == ']' ) { TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); if( prop == TestCaseInfo::IsHidden ) isHidden = true; else if( prop == TestCaseInfo::None ) enforceNotReservedTag( tag, _lineInfo ); tags.insert( tag ); tag.clear(); inTag = false; } else tag += c; } } if( isHidden ) { tags.insert( "hide" ); tags.insert( "." ); } TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); return TestCase( _testCase, info ); } void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ) { testCaseInfo.tags = tags; testCaseInfo.lcaseTags.clear(); std::ostringstream oss; for( std::set::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) { oss << "[" << *it << "]"; std::string lcaseTag = toLower( *it ); testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); testCaseInfo.lcaseTags.insert( lcaseTag ); } testCaseInfo.tagsAsString = oss.str(); } TestCaseInfo::TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, std::set const& _tags, SourceLineInfo const& _lineInfo ) : name( _name ), className( _className ), description( _description ), lineInfo( _lineInfo ), properties( None ) { setTags( *this, _tags ); } TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) : name( other.name ), className( other.className ), description( other.description ), tags( other.tags ), lcaseTags( other.lcaseTags ), tagsAsString( other.tagsAsString ), lineInfo( other.lineInfo ), properties( other.properties ) {} bool TestCaseInfo::isHidden() const { return ( properties & IsHidden ) != 0; } bool TestCaseInfo::throws() const { return ( properties & Throws ) != 0; } bool TestCaseInfo::okToFail() const { return ( properties & (ShouldFail | MayFail ) ) != 0; } bool TestCaseInfo::expectedToFail() const { return ( properties & (ShouldFail ) ) != 0; } TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} TestCase::TestCase( TestCase const& other ) : TestCaseInfo( other ), test( other.test ) {} TestCase TestCase::withName( std::string const& _newName ) const { TestCase other( *this ); other.name = _newName; return other; } void TestCase::swap( TestCase& other ) { test.swap( other.test ); name.swap( other.name ); className.swap( other.className ); description.swap( other.description ); tags.swap( other.tags ); lcaseTags.swap( other.lcaseTags ); tagsAsString.swap( other.tagsAsString ); std::swap( TestCaseInfo::properties, static_cast( other ).properties ); std::swap( lineInfo, other.lineInfo ); } void TestCase::invoke() const { test->invoke(); } bool TestCase::operator == ( TestCase const& other ) const { return test.get() == other.test.get() && name == other.name && className == other.className; } bool TestCase::operator < ( TestCase const& other ) const { return name < other.name; } TestCase& TestCase::operator = ( TestCase const& other ) { TestCase temp( other ); swap( temp ); return *this; } TestCaseInfo const& TestCase::getTestCaseInfo() const { return *this; } } // end namespace Catch // #included from: catch_version.hpp #define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED namespace Catch { Version::Version ( unsigned int _majorVersion, unsigned int _minorVersion, unsigned int _patchNumber, std::string const& _branchName, unsigned int _buildNumber ) : majorVersion( _majorVersion ), minorVersion( _minorVersion ), patchNumber( _patchNumber ), branchName( _branchName ), buildNumber( _buildNumber ) {} std::ostream& operator << ( std::ostream& os, Version const& version ) { os << version.majorVersion << "." << version.minorVersion << "." << version.patchNumber; if( !version.branchName.empty() ) { os << "-" << version.branchName << "." << version.buildNumber; } return os; } Version libraryVersion( 1, 5, 9, "", 0 ); } // #included from: catch_message.hpp #define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED namespace Catch { MessageInfo::MessageInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ) : macroName( _macroName ), lineInfo( _lineInfo ), type( _type ), sequence( ++globalCount ) {} // This may need protecting if threading support is added unsigned int MessageInfo::globalCount = 0; //////////////////////////////////////////////////////////////////////////// ScopedMessage::ScopedMessage( MessageBuilder const& builder ) : m_info( builder.m_info ) { m_info.message = builder.m_stream.str(); getResultCapture().pushScopedMessage( m_info ); } ScopedMessage::ScopedMessage( ScopedMessage const& other ) : m_info( other.m_info ) {} ScopedMessage::~ScopedMessage() { getResultCapture().popScopedMessage( m_info ); } } // end namespace Catch // #included from: catch_legacy_reporter_adapter.hpp #define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED // #included from: catch_legacy_reporter_adapter.h #define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED namespace Catch { // Deprecated struct IReporter : IShared { virtual ~IReporter(); virtual bool shouldRedirectStdout() const = 0; virtual void StartTesting() = 0; virtual void EndTesting( Totals const& totals ) = 0; virtual void StartGroup( std::string const& groupName ) = 0; virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; virtual void Aborted() = 0; virtual void Result( AssertionResult const& result ) = 0; }; class LegacyReporterAdapter : public SharedImpl { public: LegacyReporterAdapter( Ptr const& legacyReporter ); virtual ~LegacyReporterAdapter(); virtual ReporterPreferences getPreferences() const; virtual void noMatchingTestCases( std::string const& ); virtual void testRunStarting( TestRunInfo const& ); virtual void testGroupStarting( GroupInfo const& groupInfo ); virtual void testCaseStarting( TestCaseInfo const& testInfo ); virtual void sectionStarting( SectionInfo const& sectionInfo ); virtual void assertionStarting( AssertionInfo const& ); virtual bool assertionEnded( AssertionStats const& assertionStats ); virtual void sectionEnded( SectionStats const& sectionStats ); virtual void testCaseEnded( TestCaseStats const& testCaseStats ); virtual void testGroupEnded( TestGroupStats const& testGroupStats ); virtual void testRunEnded( TestRunStats const& testRunStats ); virtual void skipTest( TestCaseInfo const& ); private: Ptr m_legacyReporter; }; } namespace Catch { LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) : m_legacyReporter( legacyReporter ) {} LegacyReporterAdapter::~LegacyReporterAdapter() {} ReporterPreferences LegacyReporterAdapter::getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); return prefs; } void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { m_legacyReporter->StartTesting(); } void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { m_legacyReporter->StartGroup( groupInfo.name ); } void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { m_legacyReporter->StartTestCase( testInfo ); } void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); } void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { // Not on legacy interface } bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); it != itEnd; ++it ) { if( it->type == ResultWas::Info ) { ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); rb << it->message; rb.setResultType( ResultWas::Info ); AssertionResult result = rb.build(); m_legacyReporter->Result( result ); } } } m_legacyReporter->Result( assertionStats.assertionResult ); return true; } void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { if( sectionStats.missingAssertions ) m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); } void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { m_legacyReporter->EndTestCase ( testCaseStats.testInfo, testCaseStats.totals, testCaseStats.stdOut, testCaseStats.stdErr ); } void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { if( testGroupStats.aborting ) m_legacyReporter->Aborted(); m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); } void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { m_legacyReporter->EndTesting( testRunStats.totals ); } void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { } } // #included from: catch_timer.hpp #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc++11-long-long" #endif #ifdef CATCH_PLATFORM_WINDOWS #include #else #include #endif namespace Catch { namespace { #ifdef CATCH_PLATFORM_WINDOWS uint64_t getCurrentTicks() { static uint64_t hz=0, hzo=0; if (!hz) { QueryPerformanceFrequency( reinterpret_cast( &hz ) ); QueryPerformanceCounter( reinterpret_cast( &hzo ) ); } uint64_t t; QueryPerformanceCounter( reinterpret_cast( &t ) ); return ((t-hzo)*1000000)/hz; } #else uint64_t getCurrentTicks() { timeval t; gettimeofday(&t,CATCH_NULL); return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); } #endif } void Timer::start() { m_ticks = getCurrentTicks(); } unsigned int Timer::getElapsedMicroseconds() const { return static_cast(getCurrentTicks() - m_ticks); } unsigned int Timer::getElapsedMilliseconds() const { return static_cast(getElapsedMicroseconds()/1000); } double Timer::getElapsedSeconds() const { return getElapsedMicroseconds()/1000000.0; } } // namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif // #included from: catch_common.hpp #define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED namespace Catch { bool startsWith( std::string const& s, std::string const& prefix ) { return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; } bool endsWith( std::string const& s, std::string const& suffix ) { return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; } bool contains( std::string const& s, std::string const& infix ) { return s.find( infix ) != std::string::npos; } char toLowerCh(char c) { return static_cast( ::tolower( c ) ); } void toLowerInPlace( std::string& s ) { std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); } std::string toLower( std::string const& s ) { std::string lc = s; toLowerInPlace( lc ); return lc; } std::string trim( std::string const& str ) { static char const* whitespaceChars = "\n\r\t "; std::string::size_type start = str.find_first_not_of( whitespaceChars ); std::string::size_type end = str.find_last_not_of( whitespaceChars ); return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; } bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { bool replaced = false; std::size_t i = str.find( replaceThis ); while( i != std::string::npos ) { replaced = true; str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); if( i < str.size()-withThis.size() ) i = str.find( replaceThis, i+withThis.size() ); else i = std::string::npos; } return replaced; } pluralise::pluralise( std::size_t count, std::string const& label ) : m_count( count ), m_label( label ) {} std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { os << pluraliser.m_count << " " << pluraliser.m_label; if( pluraliser.m_count != 1 ) os << "s"; return os; } SourceLineInfo::SourceLineInfo() : line( 0 ){} SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) : file( _file ), line( _line ) {} SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) : file( other.file ), line( other.line ) {} bool SourceLineInfo::empty() const { return file.empty(); } bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { return line == other.line && file == other.file; } bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { return line < other.line || ( line == other.line && file < other.file ); } void seedRng( IConfig const& config ) { if( config.rngSeed() != 0 ) std::srand( config.rngSeed() ); } unsigned int rngSeed() { return getCurrentContext().getConfig()->rngSeed(); } std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { #ifndef __GNUG__ os << info.file << "(" << info.line << ")"; #else os << info.file << ":" << info.line; #endif return os; } void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { std::ostringstream oss; oss << locationInfo << ": Internal Catch error: '" << message << "'"; if( alwaysTrue() ) throw std::logic_error( oss.str() ); } } // #included from: catch_section.hpp #define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED namespace Catch { SectionInfo::SectionInfo ( SourceLineInfo const& _lineInfo, std::string const& _name, std::string const& _description ) : name( _name ), description( _description ), lineInfo( _lineInfo ) {} Section::Section( SectionInfo const& info ) : m_info( info ), m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) { m_timer.start(); } Section::~Section() { if( m_sectionIncluded ) { SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); if( std::uncaught_exception() ) getResultCapture().sectionEndedEarly( endInfo ); else getResultCapture().sectionEnded( endInfo ); } } // This indicates whether the section should be executed or not Section::operator bool() const { return m_sectionIncluded; } } // end namespace Catch // #included from: catch_debugger.hpp #define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED #include #ifdef CATCH_PLATFORM_MAC #include #include #include #include #include namespace Catch{ // The following function is taken directly from the following technical note: // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). bool isDebuggerActive(){ int mib[4]; struct kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); // Call sysctl. size = sizeof(info); if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) { Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; return false; } // We're being debugged if the P_TRACED flag is set. return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); } } // namespace Catch #elif defined(_MSC_VER) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); namespace Catch { bool isDebuggerActive() { return IsDebuggerPresent() != 0; } } #elif defined(__MINGW32__) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); namespace Catch { bool isDebuggerActive() { return IsDebuggerPresent() != 0; } } #else namespace Catch { inline bool isDebuggerActive() { return false; } } #endif // Platform #ifdef CATCH_PLATFORM_WINDOWS extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); namespace Catch { void writeToDebugConsole( std::string const& text ) { ::OutputDebugStringA( text.c_str() ); } } #else namespace Catch { void writeToDebugConsole( std::string const& text ) { // !TBD: Need a version for Mac/ XCode and other IDEs Catch::cout() << text; } } #endif // Platform // #included from: catch_tostring.hpp #define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED namespace Catch { namespace Detail { const std::string unprintableString = "{?}"; namespace { const int hexThreshold = 255; struct Endianness { enum Arch { Big, Little }; static Arch which() { union _{ int asInt; char asChar[sizeof (int)]; } u; u.asInt = 1; return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; } }; } std::string rawMemoryToString( const void *object, std::size_t size ) { // Reverse order for little endian architectures int i = 0, end = static_cast( size ), inc = 1; if( Endianness::which() == Endianness::Little ) { i = end-1; end = inc = -1; } unsigned char const *bytes = static_cast(object); std::ostringstream os; os << "0x" << std::setfill('0') << std::hex; for( ; i != end; i += inc ) os << std::setw(2) << static_cast(bytes[i]); return os.str(); } } std::string toString( std::string const& value ) { std::string s = value; if( getCurrentContext().getConfig()->showInvisibles() ) { for(size_t i = 0; i < s.size(); ++i ) { std::string subs; switch( s[i] ) { case '\n': subs = "\\n"; break; case '\t': subs = "\\t"; break; default: break; } if( !subs.empty() ) { s = s.substr( 0, i ) + subs + s.substr( i+1 ); ++i; } } } return "\"" + s + "\""; } std::string toString( std::wstring const& value ) { std::string s; s.reserve( value.size() ); for(size_t i = 0; i < value.size(); ++i ) s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; return Catch::toString( s ); } std::string toString( const char* const value ) { return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); } std::string toString( char* const value ) { return Catch::toString( static_cast( value ) ); } std::string toString( const wchar_t* const value ) { return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); } std::string toString( wchar_t* const value ) { return Catch::toString( static_cast( value ) ); } std::string toString( int value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } std::string toString( unsigned long value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } std::string toString( unsigned int value ) { return Catch::toString( static_cast( value ) ); } template std::string fpToString( T value, int precision ) { std::ostringstream oss; oss << std::setprecision( precision ) << std::fixed << value; std::string d = oss.str(); std::size_t i = d.find_last_not_of( '0' ); if( i != std::string::npos && i != d.size()-1 ) { if( d[i] == '.' ) i++; d = d.substr( 0, i+1 ); } return d; } std::string toString( const double value ) { return fpToString( value, 10 ); } std::string toString( const float value ) { return fpToString( value, 5 ) + "f"; } std::string toString( bool value ) { return value ? "true" : "false"; } std::string toString( char value ) { return value < ' ' ? toString( static_cast( value ) ) : Detail::makeString( value ); } std::string toString( signed char value ) { return toString( static_cast( value ) ); } std::string toString( unsigned char value ) { return toString( static_cast( value ) ); } #ifdef CATCH_CONFIG_CPP11_LONG_LONG std::string toString( long long value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } std::string toString( unsigned long long value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } #endif #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ) { return "nullptr"; } #endif #ifdef __OBJC__ std::string toString( NSString const * const& nsstring ) { if( !nsstring ) return "nil"; return "@" + toString([nsstring UTF8String]); } std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { if( !nsstring ) return "nil"; return "@" + toString([nsstring UTF8String]); } std::string toString( NSObject* const& nsObject ) { return toString( [nsObject description] ); } #endif } // end namespace Catch // #included from: catch_result_builder.hpp #define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED namespace Catch { std::string capturedExpressionWithSecondArgument( std::string const& capturedExpression, std::string const& secondArg ) { return secondArg.empty() || secondArg == "\"\"" ? capturedExpression : capturedExpression + ", " + secondArg; } ResultBuilder::ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, ResultDisposition::Flags resultDisposition, char const* secondArg ) : m_assertionInfo( macroName, lineInfo, capturedExpressionWithSecondArgument( capturedExpression, secondArg ), resultDisposition ), m_shouldDebugBreak( false ), m_shouldThrow( false ) {} ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { m_data.resultType = result; return *this; } ResultBuilder& ResultBuilder::setResultType( bool result ) { m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; return *this; } ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { m_exprComponents.lhs = lhs; return *this; } ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { m_exprComponents.rhs = rhs; return *this; } ResultBuilder& ResultBuilder::setOp( std::string const& op ) { m_exprComponents.op = op; return *this; } void ResultBuilder::endExpression() { m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); captureExpression(); } void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { m_assertionInfo.resultDisposition = resultDisposition; m_stream.oss << Catch::translateActiveException(); captureResult( ResultWas::ThrewException ); } void ResultBuilder::captureResult( ResultWas::OfType resultType ) { setResultType( resultType ); captureExpression(); } void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { if( expectedMessage.empty() ) captureExpectedException( Matchers::Impl::Generic::AllOf() ); else captureExpectedException( Matchers::Equals( expectedMessage ) ); } void ResultBuilder::captureExpectedException( Matchers::Impl::Matcher const& matcher ) { assert( m_exprComponents.testFalse == false ); AssertionResultData data = m_data; data.resultType = ResultWas::Ok; data.reconstructedExpression = m_assertionInfo.capturedExpression; std::string actualMessage = Catch::translateActiveException(); if( !matcher.match( actualMessage ) ) { data.resultType = ResultWas::ExpressionFailed; data.reconstructedExpression = actualMessage; } AssertionResult result( m_assertionInfo, data ); handleResult( result ); } void ResultBuilder::captureExpression() { AssertionResult result = build(); handleResult( result ); } void ResultBuilder::handleResult( AssertionResult const& result ) { getResultCapture().assertionEnded( result ); if( !result.isOk() ) { if( getCurrentContext().getConfig()->shouldDebugBreak() ) m_shouldDebugBreak = true; if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) m_shouldThrow = true; } } void ResultBuilder::react() { if( m_shouldThrow ) throw Catch::TestFailureException(); } bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } AssertionResult ResultBuilder::build() const { assert( m_data.resultType != ResultWas::Unknown ); AssertionResultData data = m_data; // Flip bool results if testFalse is set if( m_exprComponents.testFalse ) { if( data.resultType == ResultWas::Ok ) data.resultType = ResultWas::ExpressionFailed; else if( data.resultType == ResultWas::ExpressionFailed ) data.resultType = ResultWas::Ok; } data.message = m_stream.oss.str(); data.reconstructedExpression = reconstructExpression(); if( m_exprComponents.testFalse ) { if( m_exprComponents.op == "" ) data.reconstructedExpression = "!" + data.reconstructedExpression; else data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; } return AssertionResult( m_assertionInfo, data ); } std::string ResultBuilder::reconstructExpression() const { if( m_exprComponents.op == "" ) return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs; else if( m_exprComponents.op == "matches" ) return m_exprComponents.lhs + " " + m_exprComponents.rhs; else if( m_exprComponents.op != "!" ) { if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && m_exprComponents.lhs.find("\n") == std::string::npos && m_exprComponents.rhs.find("\n") == std::string::npos ) return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; else return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; } else return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; } } // end namespace Catch // #included from: catch_tag_alias_registry.hpp #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED // #included from: catch_tag_alias_registry.h #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED #include namespace Catch { class TagAliasRegistry : public ITagAliasRegistry { public: virtual ~TagAliasRegistry(); virtual Option find( std::string const& alias ) const; virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); static TagAliasRegistry& get(); private: std::map m_registry; }; } // end namespace Catch #include #include namespace Catch { TagAliasRegistry::~TagAliasRegistry() {} Option TagAliasRegistry::find( std::string const& alias ) const { std::map::const_iterator it = m_registry.find( alias ); if( it != m_registry.end() ) return it->second; else return Option(); } std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { std::string expandedTestSpec = unexpandedTestSpec; for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); it != itEnd; ++it ) { std::size_t pos = expandedTestSpec.find( it->first ); if( pos != std::string::npos ) { expandedTestSpec = expandedTestSpec.substr( 0, pos ) + it->second.tag + expandedTestSpec.substr( pos + it->first.size() ); } } return expandedTestSpec; } void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { std::ostringstream oss; oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; throw std::domain_error( oss.str().c_str() ); } if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { std::ostringstream oss; oss << "error: tag alias, \"" << alias << "\" already registered.\n" << "\tFirst seen at " << find(alias)->lineInfo << "\n" << "\tRedefined at " << lineInfo; throw std::domain_error( oss.str().c_str() ); } } TagAliasRegistry& TagAliasRegistry::get() { static TagAliasRegistry instance; return instance; } ITagAliasRegistry::~ITagAliasRegistry() {} ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { try { TagAliasRegistry::get().add( alias, tag, lineInfo ); } catch( std::exception& ex ) { Colour colourGuard( Colour::Red ); Catch::cerr() << ex.what() << std::endl; exit(1); } } } // end namespace Catch // #included from: ../reporters/catch_reporter_multi.hpp #define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED namespace Catch { class MultipleReporters : public SharedImpl { typedef std::vector > Reporters; Reporters m_reporters; public: void add( Ptr const& reporter ) { m_reporters.push_back( reporter ); } public: // IStreamingReporter virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { return m_reporters[0]->getPreferences(); } virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->noMatchingTestCases( spec ); } virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testRunStarting( testRunInfo ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testGroupStarting( groupInfo ); } virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testCaseStarting( testInfo ); } virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->sectionStarting( sectionInfo ); } virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->assertionStarting( assertionInfo ); } // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { bool clearBuffer = false; for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) clearBuffer |= (*it)->assertionEnded( assertionStats ); return clearBuffer; } virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->sectionEnded( sectionStats ); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testCaseEnded( testCaseStats ); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testGroupEnded( testGroupStats ); } virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testRunEnded( testRunStats ); } virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->skipTest( testInfo ); } virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE { return this; } }; Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ) { Ptr resultingReporter; if( existingReporter ) { MultipleReporters* multi = existingReporter->tryAsMulti(); if( !multi ) { multi = new MultipleReporters; resultingReporter = Ptr( multi ); if( existingReporter ) multi->add( existingReporter ); } else resultingReporter = existingReporter; multi->add( additionalReporter ); } else resultingReporter = additionalReporter; return resultingReporter; } } // end namespace Catch // #included from: ../reporters/catch_reporter_xml.hpp #define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED // #included from: catch_reporter_bases.hpp #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED #include namespace Catch { struct StreamingReporterBase : SharedImpl { StreamingReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = false; } virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { return m_reporterPrefs; } virtual ~StreamingReporterBase() CATCH_OVERRIDE; virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {} virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE { currentTestRunInfo = _testRunInfo; } virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE { currentGroupInfo = _groupInfo; } virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE { currentTestCaseInfo = _testInfo; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_sectionStack.push_back( _sectionInfo ); } virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE { m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); } virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE { currentGroupInfo.reset(); } virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); currentGroupInfo.reset(); currentTestRunInfo.reset(); } virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE { // Don't do anything with this by default. // It can optionally be overridden in the derived class. } Ptr m_config; std::ostream& stream; LazyStat currentTestRunInfo; LazyStat currentGroupInfo; LazyStat currentTestCaseInfo; std::vector m_sectionStack; ReporterPreferences m_reporterPrefs; }; struct CumulativeReporterBase : SharedImpl { template struct Node : SharedImpl<> { explicit Node( T const& _value ) : value( _value ) {} virtual ~Node() {} typedef std::vector > ChildNodes; T value; ChildNodes children; }; struct SectionNode : SharedImpl<> { explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} virtual ~SectionNode(); bool operator == ( SectionNode const& other ) const { return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; } bool operator == ( Ptr const& other ) const { return operator==( *other ); } SectionStats stats; typedef std::vector > ChildSections; typedef std::vector Assertions; ChildSections childSections; Assertions assertions; std::string stdOut; std::string stdErr; }; struct BySectionInfo { BySectionInfo( SectionInfo const& other ) : m_other( other ) {} BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} bool operator() ( Ptr const& node ) const { return node->stats.sectionInfo.lineInfo == m_other.lineInfo; } private: void operator=( BySectionInfo const& ); SectionInfo const& m_other; }; typedef Node TestCaseNode; typedef Node TestGroupNode; typedef Node TestRunNode; CumulativeReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = false; } ~CumulativeReporterBase(); virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { return m_reporterPrefs; } virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {} virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {} virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {} virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); Ptr node; if( m_sectionStack.empty() ) { if( !m_rootSection ) m_rootSection = new SectionNode( incompleteStats ); node = m_rootSection; } else { SectionNode& parentNode = *m_sectionStack.back(); SectionNode::ChildSections::const_iterator it = std::find_if( parentNode.childSections.begin(), parentNode.childSections.end(), BySectionInfo( sectionInfo ) ); if( it == parentNode.childSections.end() ) { node = new SectionNode( incompleteStats ); parentNode.childSections.push_back( node ); } else node = *it; } m_sectionStack.push_back( node ); m_deepestSection = node; } virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& sectionNode = *m_sectionStack.back(); sectionNode.assertions.push_back( assertionStats ); return true; } virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& node = *m_sectionStack.back(); node.stats = sectionStats; m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { Ptr node = new TestCaseNode( testCaseStats ); assert( m_sectionStack.size() == 0 ); node->children.push_back( m_rootSection ); m_testCases.push_back( node ); m_rootSection.reset(); assert( m_deepestSection ); m_deepestSection->stdOut = testCaseStats.stdOut; m_deepestSection->stdErr = testCaseStats.stdErr; } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { Ptr node = new TestGroupNode( testGroupStats ); node->children.swap( m_testCases ); m_testGroups.push_back( node ); } virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { Ptr node = new TestRunNode( testRunStats ); node->children.swap( m_testGroups ); m_testRuns.push_back( node ); testRunEndedCumulative(); } virtual void testRunEndedCumulative() = 0; virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {} Ptr m_config; std::ostream& stream; std::vector m_assertions; std::vector > > m_sections; std::vector > m_testCases; std::vector > m_testGroups; std::vector > m_testRuns; Ptr m_rootSection; Ptr m_deepestSection; std::vector > m_sectionStack; ReporterPreferences m_reporterPrefs; }; template char const* getLineOfChars() { static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; if( !*line ) { memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; } return line; } struct TestEventListenerBase : StreamingReporterBase { TestEventListenerBase( ReporterConfig const& _config ) : StreamingReporterBase( _config ) {} virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE { return false; } }; } // end namespace Catch // #included from: ../internal/catch_reporter_registrars.hpp #define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED namespace Catch { template class LegacyReporterRegistrar { class ReporterFactory : public IReporterFactory { virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new LegacyReporterAdapter( new T( config ) ); } virtual std::string getDescription() const { return T::getDescription(); } }; public: LegacyReporterRegistrar( std::string const& name ) { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; template class ReporterRegistrar { class ReporterFactory : public SharedImpl { // *** Please Note ***: // - If you end up here looking at a compiler error because it's trying to register // your custom reporter class be aware that the native reporter interface has changed // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. // However please consider updating to the new interface as the old one is now // deprecated and will probably be removed quite soon! // Please contact me via github if you have any questions at all about this. // In fact, ideally, please contact me anyway to let me know you've hit this - as I have // no idea who is actually using custom reporters at all (possibly no-one!). // The new interface is designed to minimise exposure to interface changes in the future. virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new T( config ); } virtual std::string getDescription() const { return T::getDescription(); } }; public: ReporterRegistrar( std::string const& name ) { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; template class ListenerRegistrar { class ListenerFactory : public SharedImpl { virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new T( config ); } virtual std::string getDescription() const { return ""; } }; public: ListenerRegistrar() { getMutableRegistryHub().registerListener( new ListenerFactory() ); } }; } #define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } #define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } #define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \ namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } // #included from: ../internal/catch_xmlwriter.hpp #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED #include #include #include #include namespace Catch { class XmlEncode { public: enum ForWhat { ForTextNodes, ForAttributes }; XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) : m_str( str ), m_forWhat( forWhat ) {} void encodeTo( std::ostream& os ) const { // Apostrophe escaping not necessary if we always use " to write attributes // (see: http://www.w3.org/TR/xml/#syntax) for( std::size_t i = 0; i < m_str.size(); ++ i ) { char c = m_str[i]; switch( c ) { case '<': os << "<"; break; case '&': os << "&"; break; case '>': // See: http://www.w3.org/TR/xml/#syntax if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) os << ">"; else os << c; break; case '\"': if( m_forWhat == ForAttributes ) os << """; else os << c; break; default: // Escape control chars - based on contribution by @espenalb in PR #465 and // by @mrpi PR #588 if ( ( c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) os << "&#x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast( c ) << ';'; else os << c; } } } friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { xmlEncode.encodeTo( os ); return os; } private: std::string m_str; ForWhat m_forWhat; }; class XmlWriter { public: class ScopedElement { public: ScopedElement( XmlWriter* writer ) : m_writer( writer ) {} ScopedElement( ScopedElement const& other ) : m_writer( other.m_writer ){ other.m_writer = CATCH_NULL; } ~ScopedElement() { if( m_writer ) m_writer->endElement(); } ScopedElement& writeText( std::string const& text, bool indent = true ) { m_writer->writeText( text, indent ); return *this; } template ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { m_writer->writeAttribute( name, attribute ); return *this; } private: mutable XmlWriter* m_writer; }; XmlWriter() : m_tagIsOpen( false ), m_needsNewline( false ), m_os( &Catch::cout() ) { // We encode control characters, which requires // XML 1.1 // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 *m_os << "\n"; } XmlWriter( std::ostream& os ) : m_tagIsOpen( false ), m_needsNewline( false ), m_os( &os ) { *m_os << "\n"; } ~XmlWriter() { while( !m_tags.empty() ) endElement(); } XmlWriter& startElement( std::string const& name ) { ensureTagClosed(); newlineIfNecessary(); stream() << m_indent << "<" << name; m_tags.push_back( name ); m_indent += " "; m_tagIsOpen = true; return *this; } ScopedElement scopedElement( std::string const& name ) { ScopedElement scoped( this ); startElement( name ); return scoped; } XmlWriter& endElement() { newlineIfNecessary(); m_indent = m_indent.substr( 0, m_indent.size()-2 ); if( m_tagIsOpen ) { stream() << "/>\n"; m_tagIsOpen = false; } else { stream() << m_indent << "\n"; } m_tags.pop_back(); return *this; } XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { if( !name.empty() && !attribute.empty() ) stream() << " " << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << "\""; return *this; } XmlWriter& writeAttribute( std::string const& name, bool attribute ) { stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; return *this; } template XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { std::ostringstream oss; oss << attribute; return writeAttribute( name, oss.str() ); } XmlWriter& writeText( std::string const& text, bool indent = true ) { if( !text.empty() ){ bool tagWasOpen = m_tagIsOpen; ensureTagClosed(); if( tagWasOpen && indent ) stream() << m_indent; stream() << XmlEncode( text ); m_needsNewline = true; } return *this; } XmlWriter& writeComment( std::string const& text ) { ensureTagClosed(); stream() << m_indent << ""; m_needsNewline = true; return *this; } XmlWriter& writeBlankLine() { ensureTagClosed(); stream() << "\n"; return *this; } void setStream( std::ostream& os ) { m_os = &os; } private: XmlWriter( XmlWriter const& ); void operator=( XmlWriter const& ); std::ostream& stream() { return *m_os; } void ensureTagClosed() { if( m_tagIsOpen ) { stream() << ">\n"; m_tagIsOpen = false; } } void newlineIfNecessary() { if( m_needsNewline ) { stream() << "\n"; m_needsNewline = false; } } bool m_tagIsOpen; bool m_needsNewline; std::vector m_tags; std::string m_indent; std::ostream* m_os; }; } // #included from: catch_reenable_warnings.h #define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(pop) # else # pragma clang diagnostic pop # endif #elif defined __GNUC__ # pragma GCC diagnostic pop #endif namespace Catch { class XmlReporter : public StreamingReporterBase { public: XmlReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_xml(_config.stream()), m_sectionDepth( 0 ) { m_reporterPrefs.shouldRedirectStdOut = true; } virtual ~XmlReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as an XML document"; } public: // StreamingReporterBase virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE { StreamingReporterBase::noMatchingTestCases( s ); } virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testRunStarting( testInfo ); m_xml.startElement( "Catch" ); if( !m_config->name().empty() ) m_xml.writeAttribute( "name", m_config->name() ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { StreamingReporterBase::testGroupStarting( groupInfo ); m_xml.startElement( "Group" ) .writeAttribute( "name", groupInfo.name ); } virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testCaseStarting(testInfo); m_xml.startElement( "TestCase" ).writeAttribute( "name", testInfo.name ); if ( m_config->showDurations() == ShowDurations::Always ) m_testCaseTimer.start(); } virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { StreamingReporterBase::sectionStarting( sectionInfo ); if( m_sectionDepth++ > 0 ) { m_xml.startElement( "Section" ) .writeAttribute( "name", trim( sectionInfo.name ) ) .writeAttribute( "description", sectionInfo.description ); } } virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { const AssertionResult& assertionResult = assertionStats.assertionResult; // Print any info messages in tags. if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); it != itEnd; ++it ) { if( it->type == ResultWas::Info ) { m_xml.scopedElement( "Info" ) .writeText( it->message ); } else if ( it->type == ResultWas::Warning ) { m_xml.scopedElement( "Warning" ) .writeText( it->message ); } } } // Drop out if result was successful but we're not printing them. if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) ) return true; // Print the expression if there is one. if( assertionResult.hasExpression() ) { m_xml.startElement( "Expression" ) .writeAttribute( "success", assertionResult.succeeded() ) .writeAttribute( "type", assertionResult.getTestMacroName() ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ); m_xml.scopedElement( "Original" ) .writeText( assertionResult.getExpression() ); m_xml.scopedElement( "Expanded" ) .writeText( assertionResult.getExpandedExpression() ); } // And... Print a result applicable to each result type. switch( assertionResult.getResultType() ) { case ResultWas::ThrewException: m_xml.scopedElement( "Exception" ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ) .writeText( assertionResult.getMessage() ); break; case ResultWas::FatalErrorCondition: m_xml.scopedElement( "FatalErrorCondition" ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ) .writeText( assertionResult.getMessage() ); break; case ResultWas::Info: m_xml.scopedElement( "Info" ) .writeText( assertionResult.getMessage() ); break; case ResultWas::Warning: // Warning will already have been written break; case ResultWas::ExplicitFailure: m_xml.scopedElement( "Failure" ) .writeText( assertionResult.getMessage() ); break; default: break; } if( assertionResult.hasExpression() ) m_xml.endElement(); return true; } virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { StreamingReporterBase::sectionEnded( sectionStats ); if( --m_sectionDepth > 0 ) { XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); e.writeAttribute( "successes", sectionStats.assertions.passed ); e.writeAttribute( "failures", sectionStats.assertions.failed ); e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); if ( m_config->showDurations() == ShowDurations::Always ) e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); m_xml.endElement(); } } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( testCaseStats ); XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); if ( m_config->showDurations() == ShowDurations::Always ) e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); m_xml.endElement(); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { StreamingReporterBase::testGroupEnded( testGroupStats ); // TODO: Check testGroupStats.aborting and act accordingly. m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); m_xml.endElement(); } virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { StreamingReporterBase::testRunEnded( testRunStats ); m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testRunStats.totals.assertions.passed ) .writeAttribute( "failures", testRunStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); m_xml.endElement(); } private: Timer m_testCaseTimer; XmlWriter m_xml; int m_sectionDepth; }; INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_junit.hpp #define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED #include namespace Catch { class JunitReporter : public CumulativeReporterBase { public: JunitReporter( ReporterConfig const& _config ) : CumulativeReporterBase( _config ), xml( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = true; } virtual ~JunitReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results in an XML format that looks like Ant's junitreport target"; } virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {} virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE { CumulativeReporterBase::testRunStarting( runInfo ); xml.startElement( "testsuites" ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { suiteTimer.start(); stdOutForSuite.str(""); stdErrForSuite.str(""); unexpectedExceptions = 0; CumulativeReporterBase::testGroupStarting( groupInfo ); } virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) unexpectedExceptions++; return CumulativeReporterBase::assertionEnded( assertionStats ); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { stdOutForSuite << testCaseStats.stdOut; stdErrForSuite << testCaseStats.stdErr; CumulativeReporterBase::testCaseEnded( testCaseStats ); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { double suiteTime = suiteTimer.getElapsedSeconds(); CumulativeReporterBase::testGroupEnded( testGroupStats ); writeGroup( *m_testGroups.back(), suiteTime ); } virtual void testRunEndedCumulative() CATCH_OVERRIDE { xml.endElement(); } void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); TestGroupStats const& stats = groupNode.value; xml.writeAttribute( "name", stats.groupInfo.name ); xml.writeAttribute( "errors", unexpectedExceptions ); xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); xml.writeAttribute( "tests", stats.totals.assertions.total() ); xml.writeAttribute( "hostname", "tbd" ); // !TBD if( m_config->showDurations() == ShowDurations::Never ) xml.writeAttribute( "time", "" ); else xml.writeAttribute( "time", suiteTime ); xml.writeAttribute( "timestamp", "tbd" ); // !TBD // Write test cases for( TestGroupNode::ChildNodes::const_iterator it = groupNode.children.begin(), itEnd = groupNode.children.end(); it != itEnd; ++it ) writeTestCase( **it ); xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); } void writeTestCase( TestCaseNode const& testCaseNode ) { TestCaseStats const& stats = testCaseNode.value; // All test cases have exactly one section - which represents the // test case itself. That section may have 0-n nested sections assert( testCaseNode.children.size() == 1 ); SectionNode const& rootSection = *testCaseNode.children.front(); std::string className = stats.testInfo.className; if( className.empty() ) { if( rootSection.childSections.empty() ) className = "global"; } writeSection( className, "", rootSection ); } void writeSection( std::string const& className, std::string const& rootName, SectionNode const& sectionNode ) { std::string name = trim( sectionNode.stats.sectionInfo.name ); if( !rootName.empty() ) name = rootName + "/" + name; if( !sectionNode.assertions.empty() || !sectionNode.stdOut.empty() || !sectionNode.stdErr.empty() ) { XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); if( className.empty() ) { xml.writeAttribute( "classname", name ); xml.writeAttribute( "name", "root" ); } else { xml.writeAttribute( "classname", className ); xml.writeAttribute( "name", name ); } xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); writeAssertions( sectionNode ); if( !sectionNode.stdOut.empty() ) xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); if( !sectionNode.stdErr.empty() ) xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); } for( SectionNode::ChildSections::const_iterator it = sectionNode.childSections.begin(), itEnd = sectionNode.childSections.end(); it != itEnd; ++it ) if( className.empty() ) writeSection( name, "", **it ); else writeSection( className, name, **it ); } void writeAssertions( SectionNode const& sectionNode ) { for( SectionNode::Assertions::const_iterator it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); it != itEnd; ++it ) writeAssertion( *it ); } void writeAssertion( AssertionStats const& stats ) { AssertionResult const& result = stats.assertionResult; if( !result.isOk() ) { std::string elementName; switch( result.getResultType() ) { case ResultWas::ThrewException: case ResultWas::FatalErrorCondition: elementName = "error"; break; case ResultWas::ExplicitFailure: elementName = "failure"; break; case ResultWas::ExpressionFailed: elementName = "failure"; break; case ResultWas::DidntThrowException: elementName = "failure"; break; // We should never see these here: case ResultWas::Info: case ResultWas::Warning: case ResultWas::Ok: case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: elementName = "internalError"; break; } XmlWriter::ScopedElement e = xml.scopedElement( elementName ); xml.writeAttribute( "message", result.getExpandedExpression() ); xml.writeAttribute( "type", result.getTestMacroName() ); std::ostringstream oss; if( !result.getMessage().empty() ) oss << result.getMessage() << "\n"; for( std::vector::const_iterator it = stats.infoMessages.begin(), itEnd = stats.infoMessages.end(); it != itEnd; ++it ) if( it->type == ResultWas::Info ) oss << it->message << "\n"; oss << "at " << result.getSourceInfo(); xml.writeText( oss.str(), false ); } } XmlWriter xml; Timer suiteTimer; std::ostringstream stdOutForSuite; std::ostringstream stdErrForSuite; unsigned int unexpectedExceptions; }; INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_console.hpp #define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED namespace Catch { struct ConsoleReporter : StreamingReporterBase { ConsoleReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_headerPrinted( false ) {} virtual ~ConsoleReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as plain lines of text"; } virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { stream << "No test cases matched '" << spec << "'" << std::endl; } virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; // Drop out if result was successful and we're not printing those if( !m_config->includeSuccessfulResults() && result.isOk() ) { if( result.getResultType() != ResultWas::Warning ) return false; printInfoMessages = false; } lazyPrint(); AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); printer.print(); stream << std::endl; return true; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_headerPrinted = false; StreamingReporterBase::sectionStarting( _sectionInfo ); } virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE { if( _sectionStats.missingAssertions ) { lazyPrint(); Colour colour( Colour::ResultError ); if( m_sectionStack.size() > 1 ) stream << "\nNo assertions in section"; else stream << "\nNo assertions in test case"; stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; } if( m_headerPrinted ) { if( m_config->showDurations() == ShowDurations::Always ) stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; m_headerPrinted = false; } else { if( m_config->showDurations() == ShowDurations::Always ) stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; } StreamingReporterBase::sectionEnded( _sectionStats ); } virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( _testCaseStats ); m_headerPrinted = false; } virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE { if( currentGroupInfo.used ) { printSummaryDivider(); stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; printTotals( _testGroupStats.totals ); stream << "\n" << std::endl; } StreamingReporterBase::testGroupEnded( _testGroupStats ); } virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE { printTotalsDivider( _testRunStats.totals ); printTotals( _testRunStats.totals ); stream << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } private: class AssertionPrinter { void operator= ( AssertionPrinter const& ); public: AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) : stream( _stream ), stats( _stats ), result( _stats.assertionResult ), colour( Colour::None ), message( result.getMessage() ), messages( _stats.infoMessages ), printInfoMessages( _printInfoMessages ) { switch( result.getResultType() ) { case ResultWas::Ok: colour = Colour::Success; passOrFail = "PASSED"; //if( result.hasMessage() ) if( _stats.infoMessages.size() == 1 ) messageLabel = "with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "with messages"; break; case ResultWas::ExpressionFailed: if( result.isOk() ) { colour = Colour::Success; passOrFail = "FAILED - but was ok"; } else { colour = Colour::Error; passOrFail = "FAILED"; } if( _stats.infoMessages.size() == 1 ) messageLabel = "with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "with messages"; break; case ResultWas::ThrewException: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "due to unexpected exception with message"; break; case ResultWas::FatalErrorCondition: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "due to a fatal error condition"; break; case ResultWas::DidntThrowException: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "because no exception was thrown where one was expected"; break; case ResultWas::Info: messageLabel = "info"; break; case ResultWas::Warning: messageLabel = "warning"; break; case ResultWas::ExplicitFailure: passOrFail = "FAILED"; colour = Colour::Error; if( _stats.infoMessages.size() == 1 ) messageLabel = "explicitly with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "explicitly with messages"; break; // These cases are here to prevent compiler warnings case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: passOrFail = "** internal error **"; colour = Colour::Error; break; } } void print() const { printSourceInfo(); if( stats.totals.assertions.total() > 0 ) { if( result.isOk() ) stream << "\n"; printResultType(); printOriginalExpression(); printReconstructedExpression(); } else { stream << "\n"; } printMessage(); } private: void printResultType() const { if( !passOrFail.empty() ) { Colour colourGuard( colour ); stream << passOrFail << ":\n"; } } void printOriginalExpression() const { if( result.hasExpression() ) { Colour colourGuard( Colour::OriginalExpression ); stream << " "; stream << result.getExpressionInMacro(); stream << "\n"; } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { stream << "with expansion:\n"; Colour colourGuard( Colour::ReconstructedExpression ); stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; } } void printMessage() const { if( !messageLabel.empty() ) stream << messageLabel << ":" << "\n"; for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); it != itEnd; ++it ) { // If this assertion is a warning ignore any INFO messages if( printInfoMessages || it->type != ResultWas::Info ) stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; } } void printSourceInfo() const { Colour colourGuard( Colour::FileName ); stream << result.getSourceInfo() << ": "; } std::ostream& stream; AssertionStats const& stats; AssertionResult const& result; Colour::Code colour; std::string passOrFail; std::string messageLabel; std::string message; std::vector messages; bool printInfoMessages; }; void lazyPrint() { if( !currentTestRunInfo.used ) lazyPrintRunInfo(); if( !currentGroupInfo.used ) lazyPrintGroupInfo(); if( !m_headerPrinted ) { printTestCaseAndSectionHeader(); m_headerPrinted = true; } } void lazyPrintRunInfo() { stream << "\n" << getLineOfChars<'~'>() << "\n"; Colour colour( Colour::SecondaryText ); stream << currentTestRunInfo->name << " is a Catch v" << libraryVersion << " host application.\n" << "Run with -? for options\n\n"; if( m_config->rngSeed() != 0 ) stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; currentTestRunInfo.used = true; } void lazyPrintGroupInfo() { if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { printClosedHeader( "Group: " + currentGroupInfo->name ); currentGroupInfo.used = true; } } void printTestCaseAndSectionHeader() { assert( !m_sectionStack.empty() ); printOpenHeader( currentTestCaseInfo->name ); if( m_sectionStack.size() > 1 ) { Colour colourGuard( Colour::Headers ); std::vector::const_iterator it = m_sectionStack.begin()+1, // Skip first section (test case) itEnd = m_sectionStack.end(); for( ; it != itEnd; ++it ) printHeaderString( it->name, 2 ); } SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; if( !lineInfo.empty() ){ stream << getLineOfChars<'-'>() << "\n"; Colour colourGuard( Colour::FileName ); stream << lineInfo << "\n"; } stream << getLineOfChars<'.'>() << "\n" << std::endl; } void printClosedHeader( std::string const& _name ) { printOpenHeader( _name ); stream << getLineOfChars<'.'>() << "\n"; } void printOpenHeader( std::string const& _name ) { stream << getLineOfChars<'-'>() << "\n"; { Colour colourGuard( Colour::Headers ); printHeaderString( _name ); } } // if string has a : in first line will set indent to follow it on // subsequent lines void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { std::size_t i = _string.find( ": " ); if( i != std::string::npos ) i+=2; else i = 0; stream << Text( _string, TextAttributes() .setIndent( indent+i) .setInitialIndent( indent ) ) << "\n"; } struct SummaryColumn { SummaryColumn( std::string const& _label, Colour::Code _colour ) : label( _label ), colour( _colour ) {} SummaryColumn addRow( std::size_t count ) { std::ostringstream oss; oss << count; std::string row = oss.str(); for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { while( it->size() < row.size() ) *it = " " + *it; while( it->size() > row.size() ) row = " " + row; } rows.push_back( row ); return *this; } std::string label; Colour::Code colour; std::vector rows; }; void printTotals( Totals const& totals ) { if( totals.testCases.total() == 0 ) { stream << Colour( Colour::Warning ) << "No tests ran\n"; } else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { stream << Colour( Colour::ResultSuccess ) << "All tests passed"; stream << " (" << pluralise( totals.assertions.passed, "assertion" ) << " in " << pluralise( totals.testCases.passed, "test case" ) << ")" << "\n"; } else { std::vector columns; columns.push_back( SummaryColumn( "", Colour::None ) .addRow( totals.testCases.total() ) .addRow( totals.assertions.total() ) ); columns.push_back( SummaryColumn( "passed", Colour::Success ) .addRow( totals.testCases.passed ) .addRow( totals.assertions.passed ) ); columns.push_back( SummaryColumn( "failed", Colour::ResultError ) .addRow( totals.testCases.failed ) .addRow( totals.assertions.failed ) ); columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) .addRow( totals.testCases.failedButOk ) .addRow( totals.assertions.failedButOk ) ); printSummaryRow( "test cases", columns, 0 ); printSummaryRow( "assertions", columns, 1 ); } } void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { std::string value = it->rows[row]; if( it->label.empty() ) { stream << label << ": "; if( value != "0" ) stream << value; else stream << Colour( Colour::Warning ) << "- none -"; } else if( value != "0" ) { stream << Colour( Colour::LightGrey ) << " | "; stream << Colour( it->colour ) << value << " " << it->label; } } stream << "\n"; } static std::size_t makeRatio( std::size_t number, std::size_t total ) { std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; return ( ratio == 0 && number > 0 ) ? 1 : ratio; } static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { if( i > j && i > k ) return i; else if( j > k ) return j; else return k; } void printTotalsDivider( Totals const& totals ) { if( totals.testCases.total() > 0 ) { std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) findMax( failedRatio, failedButOkRatio, passedRatio )++; while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) findMax( failedRatio, failedButOkRatio, passedRatio )--; stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); if( totals.testCases.allPassed() ) stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); else stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); } else { stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); } stream << "\n"; } void printSummaryDivider() { stream << getLineOfChars<'-'>() << "\n"; } private: bool m_headerPrinted; }; INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_compact.hpp #define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED namespace Catch { struct CompactReporter : StreamingReporterBase { CompactReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ) {} virtual ~CompactReporter(); static std::string getDescription() { return "Reports test results on a single line, suitable for IDEs"; } virtual ReporterPreferences getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = false; return prefs; } virtual void noMatchingTestCases( std::string const& spec ) { stream << "No test cases matched '" << spec << "'" << std::endl; } virtual void assertionStarting( AssertionInfo const& ) { } virtual bool assertionEnded( AssertionStats const& _assertionStats ) { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; // Drop out if result was successful and we're not printing those if( !m_config->includeSuccessfulResults() && result.isOk() ) { if( result.getResultType() != ResultWas::Warning ) return false; printInfoMessages = false; } AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); printer.print(); stream << std::endl; return true; } virtual void testRunEnded( TestRunStats const& _testRunStats ) { printTotals( _testRunStats.totals ); stream << "\n" << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } private: class AssertionPrinter { void operator= ( AssertionPrinter const& ); public: AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) : stream( _stream ) , stats( _stats ) , result( _stats.assertionResult ) , messages( _stats.infoMessages ) , itMessage( _stats.infoMessages.begin() ) , printInfoMessages( _printInfoMessages ) {} void print() { printSourceInfo(); itMessage = messages.begin(); switch( result.getResultType() ) { case ResultWas::Ok: printResultType( Colour::ResultSuccess, passedString() ); printOriginalExpression(); printReconstructedExpression(); if ( ! result.hasExpression() ) printRemainingMessages( Colour::None ); else printRemainingMessages(); break; case ResultWas::ExpressionFailed: if( result.isOk() ) printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); else printResultType( Colour::Error, failedString() ); printOriginalExpression(); printReconstructedExpression(); printRemainingMessages(); break; case ResultWas::ThrewException: printResultType( Colour::Error, failedString() ); printIssue( "unexpected exception with message:" ); printMessage(); printExpressionWas(); printRemainingMessages(); break; case ResultWas::FatalErrorCondition: printResultType( Colour::Error, failedString() ); printIssue( "fatal error condition with message:" ); printMessage(); printExpressionWas(); printRemainingMessages(); break; case ResultWas::DidntThrowException: printResultType( Colour::Error, failedString() ); printIssue( "expected exception, got none" ); printExpressionWas(); printRemainingMessages(); break; case ResultWas::Info: printResultType( Colour::None, "info" ); printMessage(); printRemainingMessages(); break; case ResultWas::Warning: printResultType( Colour::None, "warning" ); printMessage(); printRemainingMessages(); break; case ResultWas::ExplicitFailure: printResultType( Colour::Error, failedString() ); printIssue( "explicitly" ); printRemainingMessages( Colour::None ); break; // These cases are here to prevent compiler warnings case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: printResultType( Colour::Error, "** internal error **" ); break; } } private: // Colour::LightGrey static Colour::Code dimColour() { return Colour::FileName; } #ifdef CATCH_PLATFORM_MAC static const char* failedString() { return "FAILED"; } static const char* passedString() { return "PASSED"; } #else static const char* failedString() { return "failed"; } static const char* passedString() { return "passed"; } #endif void printSourceInfo() const { Colour colourGuard( Colour::FileName ); stream << result.getSourceInfo() << ":"; } void printResultType( Colour::Code colour, std::string passOrFail ) const { if( !passOrFail.empty() ) { { Colour colourGuard( colour ); stream << " " << passOrFail; } stream << ":"; } } void printIssue( std::string issue ) const { stream << " " << issue; } void printExpressionWas() { if( result.hasExpression() ) { stream << ";"; { Colour colour( dimColour() ); stream << " expression was:"; } printOriginalExpression(); } } void printOriginalExpression() const { if( result.hasExpression() ) { stream << " " << result.getExpression(); } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { { Colour colour( dimColour() ); stream << " for: "; } stream << result.getExpandedExpression(); } } void printMessage() { if ( itMessage != messages.end() ) { stream << " '" << itMessage->message << "'"; ++itMessage; } } void printRemainingMessages( Colour::Code colour = dimColour() ) { if ( itMessage == messages.end() ) return; // using messages.end() directly yields compilation error: std::vector::const_iterator itEnd = messages.end(); const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); { Colour colourGuard( colour ); stream << " with " << pluralise( N, "message" ) << ":"; } for(; itMessage != itEnd; ) { // If this assertion is a warning ignore any INFO messages if( printInfoMessages || itMessage->type != ResultWas::Info ) { stream << " '" << itMessage->message << "'"; if ( ++itMessage != itEnd ) { Colour colourGuard( dimColour() ); stream << " and"; } } } } private: std::ostream& stream; AssertionStats const& stats; AssertionResult const& result; std::vector messages; std::vector::const_iterator itMessage; bool printInfoMessages; }; // Colour, message variants: // - white: No tests ran. // - red: Failed [both/all] N test cases, failed [both/all] M assertions. // - white: Passed [both/all] N test cases (no assertions). // - red: Failed N tests cases, failed M assertions. // - green: Passed [both/all] N tests cases with M assertions. std::string bothOrAll( std::size_t count ) const { return count == 1 ? "" : count == 2 ? "both " : "all " ; } void printTotals( const Totals& totals ) const { if( totals.testCases.total() == 0 ) { stream << "No tests ran."; } else if( totals.testCases.failed == totals.testCases.total() ) { Colour colour( Colour::ResultError ); const std::string qualify_assertions_failed = totals.assertions.failed == totals.assertions.total() ? bothOrAll( totals.assertions.failed ) : ""; stream << "Failed " << bothOrAll( totals.testCases.failed ) << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << qualify_assertions_failed << pluralise( totals.assertions.failed, "assertion" ) << "."; } else if( totals.assertions.total() == 0 ) { stream << "Passed " << bothOrAll( totals.testCases.total() ) << pluralise( totals.testCases.total(), "test case" ) << " (no assertions)."; } else if( totals.assertions.failed ) { Colour colour( Colour::ResultError ); stream << "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; } else { Colour colour( Colour::ResultSuccess ); stream << "Passed " << bothOrAll( totals.testCases.passed ) << pluralise( totals.testCases.passed, "test case" ) << " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; } } }; INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) } // end namespace Catch namespace Catch { // These are all here to avoid warnings about not having any out of line // virtual methods NonCopyable::~NonCopyable() {} IShared::~IShared() {} IStream::~IStream() CATCH_NOEXCEPT {} FileStream::~FileStream() CATCH_NOEXCEPT {} CoutStream::~CoutStream() CATCH_NOEXCEPT {} DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {} StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} IContext::~IContext() {} IResultCapture::~IResultCapture() {} ITestCase::~ITestCase() {} ITestCaseRegistry::~ITestCaseRegistry() {} IRegistryHub::~IRegistryHub() {} IMutableRegistryHub::~IMutableRegistryHub() {} IExceptionTranslator::~IExceptionTranslator() {} IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} IReporter::~IReporter() {} IReporterFactory::~IReporterFactory() {} IReporterRegistry::~IReporterRegistry() {} IStreamingReporter::~IStreamingReporter() {} AssertionStats::~AssertionStats() {} SectionStats::~SectionStats() {} TestCaseStats::~TestCaseStats() {} TestGroupStats::~TestGroupStats() {} TestRunStats::~TestRunStats() {} CumulativeReporterBase::SectionNode::~SectionNode() {} CumulativeReporterBase::~CumulativeReporterBase() {} StreamingReporterBase::~StreamingReporterBase() {} ConsoleReporter::~ConsoleReporter() {} CompactReporter::~CompactReporter() {} IRunner::~IRunner() {} IMutableContext::~IMutableContext() {} IConfig::~IConfig() {} XmlReporter::~XmlReporter() {} JunitReporter::~JunitReporter() {} TestRegistry::~TestRegistry() {} FreeFunctionTestCase::~FreeFunctionTestCase() {} IGeneratorInfo::~IGeneratorInfo() {} IGeneratorsForTest::~IGeneratorsForTest() {} WildcardPattern::~WildcardPattern() {} TestSpec::Pattern::~Pattern() {} TestSpec::NamePattern::~NamePattern() {} TestSpec::TagPattern::~TagPattern() {} TestSpec::ExcludedPattern::~ExcludedPattern() {} Matchers::Impl::StdString::Equals::~Equals() {} Matchers::Impl::StdString::Contains::~Contains() {} Matchers::Impl::StdString::StartsWith::~StartsWith() {} Matchers::Impl::StdString::EndsWith::~EndsWith() {} void Config::dummy() {} namespace TestCaseTracking { ITracker::~ITracker() {} TrackerBase::~TrackerBase() {} SectionTracker::~SectionTracker() {} IndexTracker::~IndexTracker() {} } } #ifdef __clang__ #pragma clang diagnostic pop #endif #endif #ifdef CATCH_CONFIG_MAIN // #included from: internal/catch_default_main.hpp #define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED #ifndef __OBJC__ // Standard C/C++ main entry point int main (int argc, char * argv[]) { return Catch::Session().run( argc, argv ); } #else // __OBJC__ // Objective-C entry point int main (int argc, char * const argv[]) { #if !CATCH_ARC_ENABLED NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; #endif Catch::registerTestMethods(); int result = Catch::Session().run( argc, (char* const*)argv ); #if !CATCH_ARC_ENABLED [pool drain]; #endif return result; } #endif // __OBJC__ #endif #ifdef CLARA_CONFIG_MAIN_NOT_DEFINED # undef CLARA_CONFIG_MAIN #endif ////// // If this config identifier is defined then all CATCH macros are prefixed with CATCH_ #ifdef CATCH_CONFIG_PREFIX_ALL #define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) #define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) #define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "CATCH_REQUIRE_THROWS" ) #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) #define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "CATCH_REQUIRE_THROWS_WITH" ) #define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) #define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) #define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) #define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) #define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) #define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) #define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) #define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CATCH_CHECK_THROWS_WITH" ) #define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) #define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) #define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) #define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) #ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) #else #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description ) #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) #endif #define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) #define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) #define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) #define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) // "BDD-style" convenience wrappers #ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) #else #define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) #define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif #define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" ) #define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" ) #define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) #define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" ) #define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else #define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) #define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) #define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "REQUIRE_THROWS" ) #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) #define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "REQUIRE_THROWS_WITH" ) #define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) #define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) #define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) #define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) #define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) #define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) #define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CHECK_THROWS" ) #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) #define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CHECK_THROWS_WITH" ) #define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) #define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) #define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) #define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) #define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) #define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) #define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) #ifdef CATCH_CONFIG_VARIADIC_MACROS #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) #else #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description ) #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) #endif #define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) #define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) #define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) #define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) #endif #define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) // "BDD-style" convenience wrappers #ifdef CATCH_CONFIG_VARIADIC_MACROS #define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) #else #define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) #define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif #define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" ) #define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" ) #define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" ) #define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" ) #define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" ) using Catch::Detail::Approx; #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED libcuckoo-0.3.1/tests/pcg/000077500000000000000000000000001426042121400153745ustar00rootroot00000000000000libcuckoo-0.3.1/tests/pcg/CMakeLists.txt000066400000000000000000000002341426042121400201330ustar00rootroot00000000000000add_library(pcg INTERFACE) # Include relative to the base directory target_include_directories(pcg INTERFACE $ ) libcuckoo-0.3.1/tests/pcg/pcg_extras.hpp000066400000000000000000000461571426042121400202610ustar00rootroot00000000000000/* * PCG Random Number Generation for C++ * * Copyright 2014 Melissa O'Neill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * For additional information about the PCG random number generation scheme, * including its license and other licensing options, visit * * http://www.pcg-random.org */ /* * This file provides support code that is useful for random-number generation * but not specific to the PCG generation scheme, including: * - 128-bit int support for platforms where it isn't available natively * - bit twiddling operations * - I/O of 128-bit and 8-bit integers * - Handling the evilness of SeedSeq * - Support for efficiently producing random numbers less than a given * bound */ #ifndef PCG_EXTRAS_HPP_INCLUDED #define PCG_EXTRAS_HPP_INCLUDED 1 #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __GNUC__ #include #endif /* * Abstractions for compiler-specific directives */ #ifdef __GNUC__ #define PCG_NOINLINE __attribute__((noinline)) #else #define PCG_NOINLINE #endif /* * Some members of the PCG library use 128-bit math. When compiling on 64-bit * platforms, both GCC and Clang provide 128-bit integer types that are ideal * for the job. * * On 32-bit platforms (or with other compilers), we fall back to a C++ * class that provides 128-bit unsigned integers instead. It may seem * like we're reinventing the wheel here, because libraries already exist * that support large integers, but most existing libraries provide a very * generic multiprecision code, but here we're operating at a fixed size. * Also, most other libraries are fairly heavyweight. So we use a direct * implementation. Sadly, it's much slower than hand-coded assembly or * direct CPU support. * */ #if __SIZEOF_INT128__ namespace pcg_extras { typedef __uint128_t pcg128_t; } #define PCG_128BIT_CONSTANT(high,low) \ ((pcg128_t(high) << 64) + low) #else #include "pcg_uint128.hpp" namespace pcg_extras { typedef pcg_extras::uint_x4 pcg128_t; } #define PCG_128BIT_CONSTANT(high,low) \ pcg128_t(high,low) #define PCG_EMULATED_128BIT_MATH 1 #endif namespace pcg_extras { /* * We often need to represent a "number of bits". When used normally, these * numbers are never greater than 128, so an unsigned char is plenty. * If you're using a nonstandard generator of a larger size, you can set * PCG_BITCOUNT_T to have it define it as a larger size. (Some compilers * might produce faster code if you set it to an unsigned int.) */ #ifndef PCG_BITCOUNT_T typedef uint8_t bitcount_t; #else typedef PCG_BITCOUNT_T bitcount_t; #endif /* * C++ requires us to be able to serialize RNG state by printing or reading * it from a stream. Because we use 128-bit ints, we also need to be able * ot print them, so here is code to do so. * * This code provides enough functionality to print 128-bit ints in decimal * and zero-padded in hex. It's not a full-featured implementation. */ template std::basic_ostream& operator<<(std::basic_ostream& out, pcg128_t value) { auto desired_base = out.flags() & out.basefield; bool want_hex = desired_base == out.hex; if (want_hex) { uint64_t highpart = uint64_t(value >> 64); uint64_t lowpart = uint64_t(value); auto desired_width = out.width(); if (desired_width > 16) { out.width(desired_width - 16); } if (highpart != 0 || desired_width > 16) out << highpart; CharT oldfill; if (highpart != 0) { out.width(16); oldfill = out.fill('0'); } auto oldflags = out.setf(decltype(desired_base){}, out.showbase); out << lowpart; out.setf(oldflags); if (highpart != 0) { out.fill(oldfill); } return out; } constexpr size_t MAX_CHARS_128BIT = 40; char buffer[MAX_CHARS_128BIT]; char* pos = buffer+sizeof(buffer); *(--pos) = '\0'; constexpr auto BASE = pcg128_t(10ULL); do { auto div = value / BASE; auto mod = static_cast(value - (div * BASE)); *(--pos) = '0' + mod; value = div; } while(value != pcg128_t(0ULL)); return out << pos; } template std::basic_istream& operator>>(std::basic_istream& in, pcg128_t& value) { typename std::basic_istream::sentry s(in); if (!s) return in; constexpr auto BASE = pcg128_t(10ULL); pcg128_t current(0ULL); bool did_nothing = true; bool overflow = false; for(;;) { CharT wide_ch = in.get(); if (!in.good()) break; auto ch = in.narrow(wide_ch, '\0'); if (ch < '0' || ch > '9') { in.unget(); break; } did_nothing = false; pcg128_t digit(uint32_t(ch - '0')); pcg128_t timesbase = current*BASE; overflow = overflow || timesbase < current; current = timesbase + digit; overflow = overflow || current < digit; } if (did_nothing || overflow) { in.setstate(std::ios::failbit); if (overflow) current = ~pcg128_t(0ULL); } value = current; return in; } /* * Likewise, if people use tiny rngs, we'll be serializing uint8_t. * If we just used the provided IO operators, they'd read/write chars, * not ints, so we need to define our own. We *can* redefine this operator * here because we're in our own namespace. */ template std::basic_ostream& operator<<(std::basic_ostream&out, uint8_t value) { return out << uint32_t(value); } template std::basic_istream& operator>>(std::basic_istream& in, uint8_t& target) { uint32_t value = 0xdecea5edU; in >> value; if (!in && value == 0xdecea5edU) return in; if (value > uint8_t(~0)) { in.setstate(std::ios::failbit); value = ~0U; } target = uint8_t(value); return in; } /* Unfortunately, the above functions don't get found in preference to the * built in ones, so we create some more specific overloads that will. * Ugh. */ inline std::ostream& operator<<(std::ostream& out, uint8_t value) { return pcg_extras::operator<< (out, value); } inline std::istream& operator>>(std::istream& in, uint8_t& value) { return pcg_extras::operator>> (in, value); } /* * Useful bitwise operations. */ /* * XorShifts are invertable, but they are someting of a pain to invert. * This function backs them out. It's used by the whacky "inside out" * generator defined later. */ template inline itype unxorshift(itype x, bitcount_t bits, bitcount_t shift) { if (2*shift >= bits) { return x ^ (x >> shift); } itype lowmask1 = (itype(1U) << (bits - shift*2)) - 1; itype highmask1 = ~lowmask1; itype top1 = x; itype bottom1 = x & lowmask1; top1 ^= top1 >> shift; top1 &= highmask1; x = top1 | bottom1; itype lowmask2 = (itype(1U) << (bits - shift)) - 1; itype bottom2 = x & lowmask2; bottom2 = unxorshift(bottom2, bits - shift, shift); bottom2 &= lowmask1; return top1 | bottom2; } /* * Rotate left and right. * * In ideal world, compilers would spot idiomatic rotate code and convert it * to a rotate instruction. Of course, opinions vary on what the correct * idiom is and how to spot it. For clang, sometimes it generates better * (but still crappy) code if you define PCG_USE_ZEROCHECK_ROTATE_IDIOM. */ template inline itype rotl(itype value, bitcount_t rot) { constexpr bitcount_t bits = sizeof(itype) * 8; constexpr bitcount_t mask = bits - 1; #if PCG_USE_ZEROCHECK_ROTATE_IDIOM return rot ? (value << rot) | (value >> (bits - rot)) : value; #else return (value << rot) | (value >> ((- rot) & mask)); #endif } template inline itype rotr(itype value, bitcount_t rot) { constexpr bitcount_t bits = sizeof(itype) * 8; constexpr bitcount_t mask = bits - 1; #if PCG_USE_ZEROCHECK_ROTATE_IDIOM return rot ? (value >> rot) | (value << (bits - rot)) : value; #else return (value >> rot) | (value << ((- rot) & mask)); #endif } /* Unfortunately, both Clang and GCC sometimes perform poorly when it comes * to properly recognizing idiomatic rotate code, so for we also provide * assembler directives (enabled with PCG_USE_INLINE_ASM). Boo, hiss. * (I hope that these compilers get better so that this code can die.) * * These overloads will be preferred over the general template code above. */ #if PCG_USE_INLINE_ASM && __GNUC__ && (__x86_64__ || __i386__) inline uint8_t rotr(uint8_t value, bitcount_t rot) { asm ("rorb %%cl, %0" : "=r" (value) : "0" (value), "c" (rot)); return value; } inline uint16_t rotr(uint16_t value, bitcount_t rot) { asm ("rorw %%cl, %0" : "=r" (value) : "0" (value), "c" (rot)); return value; } inline uint32_t rotr(uint32_t value, bitcount_t rot) { asm ("rorl %%cl, %0" : "=r" (value) : "0" (value), "c" (rot)); return value; } #if __x86_64__ inline uint64_t rotr(uint64_t value, bitcount_t rot) { asm ("rorq %%cl, %0" : "=r" (value) : "0" (value), "c" (rot)); return value; } #endif // __x86_64__ #endif // PCG_USE_INLINE_ASM /* * The C++ SeedSeq concept (modelled by seed_seq) can fill an array of * 32-bit integers with seed data, but sometimes we want to produce * larger or smaller integers. * * The following code handles this annoyance. * * uneven_copy will copy an array of 32-bit ints to an array of larger or * smaller ints (actually, the code is general it only needing forward * iterators). The copy is identical to the one that would be performed if * we just did memcpy on a standard little-endian machine, but works * regardless of the endian of the machine (or the weirdness of the ints * involved). * * generate_to initializes an array of integers using a SeedSeq * object. It is given the size as a static constant at compile time and * tries to avoid memory allocation. If we're filling in 32-bit constants * we just do it directly. If we need a separate buffer and it's small, * we allocate it on the stack. Otherwise, we fall back to heap allocation. * Ugh. * * generate_one produces a single value of some integral type using a * SeedSeq object. */ /* uneven_copy helper, case where destination ints are less than 32 bit. */ template SrcIter uneven_copy_impl( SrcIter src_first, DestIter dest_first, DestIter dest_last, std::true_type) { typedef typename std::iterator_traits::value_type src_t; typedef typename std::iterator_traits::value_type dest_t; constexpr bitcount_t SRC_SIZE = sizeof(src_t); constexpr bitcount_t DEST_SIZE = sizeof(dest_t); constexpr bitcount_t DEST_BITS = DEST_SIZE * 8; constexpr bitcount_t SCALE = SRC_SIZE / DEST_SIZE; size_t count = 0; src_t value; while (dest_first != dest_last) { if ((count++ % SCALE) == 0) value = *src_first++; // Get more bits else value >>= DEST_BITS; // Move down bits *dest_first++ = dest_t(value); // Truncates, ignores high bits. } return src_first; } /* uneven_copy helper, case where destination ints are more than 32 bit. */ template SrcIter uneven_copy_impl( SrcIter src_first, DestIter dest_first, DestIter dest_last, std::false_type) { typedef typename std::iterator_traits::value_type src_t; typedef typename std::iterator_traits::value_type dest_t; constexpr auto SRC_SIZE = sizeof(src_t); constexpr auto SRC_BITS = SRC_SIZE * 8; constexpr auto DEST_SIZE = sizeof(dest_t); constexpr auto SCALE = (DEST_SIZE+SRC_SIZE-1) / SRC_SIZE; while (dest_first != dest_last) { dest_t value(0UL); unsigned int shift = 0; for (size_t i = 0; i < SCALE; ++i) { value |= dest_t(*src_first++) << shift; shift += SRC_BITS; } *dest_first++ = value; } return src_first; } /* uneven_copy, call the right code for larger vs. smaller */ template inline SrcIter uneven_copy(SrcIter src_first, DestIter dest_first, DestIter dest_last) { typedef typename std::iterator_traits::value_type src_t; typedef typename std::iterator_traits::value_type dest_t; constexpr bool DEST_IS_SMALLER = sizeof(dest_t) < sizeof(src_t); return uneven_copy_impl(src_first, dest_first, dest_last, std::integral_constant{}); } /* generate_to, fill in a fixed-size array of integral type using a SeedSeq * (actually works for any random-access iterator) */ template inline void generate_to_impl(SeedSeq&& generator, DestIter dest, std::true_type) { generator.generate(dest, dest+size); } template void generate_to_impl(SeedSeq&& generator, DestIter dest, std::false_type) { typedef typename std::iterator_traits::value_type dest_t; constexpr auto DEST_SIZE = sizeof(dest_t); constexpr auto GEN_SIZE = sizeof(uint32_t); constexpr bool GEN_IS_SMALLER = GEN_SIZE < DEST_SIZE; constexpr size_t FROM_ELEMS = GEN_IS_SMALLER ? size * ((DEST_SIZE+GEN_SIZE-1) / GEN_SIZE) : (size + (GEN_SIZE / DEST_SIZE) - 1) / ((GEN_SIZE / DEST_SIZE) + GEN_IS_SMALLER); // this odd code ^^^^^^^^^^^^^^^^^ is work-around for // a bug: http://llvm.org/bugs/show_bug.cgi?id=21287 if (FROM_ELEMS <= 1024) { uint32_t buffer[FROM_ELEMS]; generator.generate(buffer, buffer+FROM_ELEMS); uneven_copy(buffer, dest, dest+size); } else { uint32_t* buffer = (uint32_t*) malloc(GEN_SIZE * FROM_ELEMS); generator.generate(buffer, buffer+FROM_ELEMS); uneven_copy(buffer, dest, dest+size); free(buffer); } } template inline void generate_to(SeedSeq&& generator, DestIter dest) { typedef typename std::iterator_traits::value_type dest_t; constexpr bool IS_32BIT = sizeof(dest_t) == sizeof(uint32_t); generate_to_impl(std::forward(generator), dest, std::integral_constant{}); } /* generate_one, produce a value of integral type using a SeedSeq * (optionally, we can have it produce more than one and pick which one * we want) */ template inline UInt generate_one(SeedSeq&& generator) { UInt result[N]; generate_to(std::forward(generator), result); return result[i]; } template auto bounded_rand(RngType& rng, typename RngType::result_type upper_bound) -> typename RngType::result_type { typedef typename RngType::result_type rtype; rtype threshold = (RngType::max() - RngType::min() + rtype(1) - upper_bound) % upper_bound; for (;;) { rtype r = rng() - RngType::min(); if (r >= threshold) return r % upper_bound; } } template void shuffle(Iter from, Iter to, RandType&& rng) { typedef typename std::iterator_traits::difference_type delta_t; auto count = to - from; while (count > 1) { delta_t chosen(bounded_rand(rng, count)); --count; --to; using std::swap; swap(*(from+chosen), *to); } } /* * Although std::seed_seq is useful, it isn't everything. Often we want to * initialize a random-number generator some other way, such as from a random * device. * * Technically, it does not meet the requirements of a SeedSequence because * it lacks some of the rarely-used member functions (some of which would * be impossible to provide). However the C++ standard is quite specific * that actual engines only called the generate method, so it ought not to be * a problem in practice. */ template class seed_seq_from { private: RngType rng_; typedef uint_least32_t result_type; public: template seed_seq_from(Args&&... args) : rng_(std::forward(args)...) { // Nothing (else) to do... } template void generate(Iter start, Iter finish) { for (auto i = start; i != finish; ++i) *i = result_type(rng_()); } constexpr size_t size() const { return (sizeof(typename RngType::result_type) > sizeof(result_type) && RngType::max() > ~size_t(0UL)) ? ~size_t(0UL) : size_t(RngType::max()); } }; /* * Sometimes you might want a distinct seed based on when the program * was compiled. That way, a particular instance of the program will * behave the same way, but when recompiled it'll produce a different * value. */ template struct static_arbitrary_seed { private: static constexpr IntType fnv(IntType hash, const char* pos) { return *pos == '\0' ? hash : fnv((hash * IntType(16777619U)) ^ *pos, (pos+1)); } public: static constexpr IntType value = fnv(IntType(2166136261U ^ sizeof(IntType)), __DATE__ __TIME__ __FILE__); }; // Sometimes, when debugging or testing, it's handy to be able print the name // of a (in human-readable form). This code allows the idiom: // // cout << printable_typename() // // to print out my_foo_type_t (or its concrete type if it is a synonym) template struct printable_typename {}; template std::ostream& operator<<(std::ostream& out, printable_typename) { const char *implementation_typename = typeid(T).name(); #ifdef __GNUC__ int status; const char* pretty_name = abi::__cxa_demangle(implementation_typename, NULL, NULL, &status); if (status == 0) out << pretty_name; free((void*) pretty_name); if (status == 0) return out; #endif out << implementation_typename; return out; } } // namespace pcg_extras #endif // PCG_EXTRAS_HPP_INCLUDED libcuckoo-0.3.1/tests/pcg/pcg_random.hpp000066400000000000000000001744661426042121400202400ustar00rootroot00000000000000/* * PCG Random Number Generation for C++ * * Copyright 2014 Melissa O'Neill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * For additional information about the PCG random number generation scheme, * including its license and other licensing options, visit * * http://www.pcg-random.org */ /* * This code provides the reference implementation of the PCG family of * random number generators. The code is complex because it implements * * - several members of the PCG family, specifically members corresponding * to the output functions: * - XSH RR (good for 64-bit state, 32-bit output) * - XSH RS (good for 64-bit state, 32-bit output) * - XSL RR (good for 128-bit state, 64-bit output) * - RXS M XS (statistically most powerful generator) * - XSL RR RR (good for 128-bit state, 128-bit output) * - and RXS, RXS M, XSH, XSL (mostly for testing) * - at potentially *arbitrary* bit sizes * - with four different techniques for random streams (MCG, one-stream * LCG, settable-stream LCG, unique-stream LCG) * - and the extended generation schemes allowing arbitrary periods * - with all features of C++11 random number generation (and more), * some of which are somewhat painful, including * - initializing with a SeedSequence which writes 32-bit values * to memory, even though the state of the generator may not * use 32-bit values (it might use smaller or larger integers) * - I/O for RNGs and a prescribed format, which needs to handle * the issue that 8-bit and 128-bit integers don't have working * I/O routines (e.g., normally 8-bit = char, not integer) * - equality and inequality for RNGs * - and a number of convenience typedefs to mask all the complexity * * The code employes a fairly heavy level of abstraction, and has to deal * with various C++ minutia. If you're looking to learn about how the PCG * scheme works, you're probably best of starting with one of the other * codebases (see www.pcg-random.org). But if you're curious about the * constants for the various output functions used in those other, simpler, * codebases, this code shows how they are calculated. * * On the positive side, at least there are convenience typedefs so that you * can say * * pcg32 myRNG; * * rather than: * * pcg_detail::engine< * uint32_t, // Output Type * uint64_t, // State Type * pcg_detail::xsh_rr_mixin, true, // Output Func * pcg_detail::specific_stream, // Stream Kind * pcg_detail::default_multiplier // LCG Mult * > myRNG; * */ #ifndef PCG_RAND_HPP_INCLUDED #define PCG_RAND_HPP_INCLUDED 1 #include #include #include #include #include #include #include #include #include #include #include #include /* * The pcg_extras namespace contains some support code that is likley to * be useful for a variety of RNGs, including: * - 128-bit int support for platforms where it isn't available natively * - bit twiddling operations * - I/O of 128-bit and 8-bit integers * - Handling the evilness of SeedSeq * - Support for efficiently producing random numbers less than a given * bound */ #include "pcg_extras.hpp" namespace pcg_detail { using namespace pcg_extras; /* * The LCG generators need some constants to function. This code lets you * look up the constant by *type*. For example * * default_multiplier::multiplier() * * gives you the default multipler for 32-bit integers. We use the name * of the constant and not a generic word like value to allow these classes * to be used as mixins. */ template struct default_multiplier { // Not defined for an arbitrary type }; template struct default_increment { // Not defined for an arbitrary type }; #define PCG_DEFINE_CONSTANT(type, what, kind, constant) \ template <> \ struct what ## _ ## kind { \ static constexpr type kind() { \ return constant; \ } \ }; PCG_DEFINE_CONSTANT(uint8_t, default, multiplier, 141U) PCG_DEFINE_CONSTANT(uint8_t, default, increment, 77U) PCG_DEFINE_CONSTANT(uint16_t, default, multiplier, 12829U) PCG_DEFINE_CONSTANT(uint16_t, default, increment, 47989U) PCG_DEFINE_CONSTANT(uint32_t, default, multiplier, 747796405U) PCG_DEFINE_CONSTANT(uint32_t, default, increment, 2891336453U) PCG_DEFINE_CONSTANT(uint64_t, default, multiplier, 6364136223846793005ULL) PCG_DEFINE_CONSTANT(uint64_t, default, increment, 1442695040888963407ULL) PCG_DEFINE_CONSTANT(pcg128_t, default, multiplier, PCG_128BIT_CONSTANT(2549297995355413924ULL,4865540595714422341ULL)) PCG_DEFINE_CONSTANT(pcg128_t, default, increment, PCG_128BIT_CONSTANT(6364136223846793005ULL,1442695040888963407ULL)) /* * Each PCG generator is available in four variants, based on how it applies * the additive constant for its underlying LCG; the variations are: * * single stream - all instances use the same fixed constant, thus * the RNG always somewhere in same sequence * mcg - adds zero, resulting in a single stream and reduced * period * specific stream - the constant can be changed at any time, selecting * a different random sequence * unique stream - the constant is based on the memory addresss of the * object, thus every RNG has its own unique sequence * * This variation is provided though mixin classes which define a function * value called increment() that returns the nesessary additive constant. */ /* * unique stream */ template class unique_stream { protected: static constexpr bool is_mcg = false; // Is never called, but is provided for symmetry with specific_stream void set_stream(...) { abort(); } public: typedef itype state_type; constexpr itype increment() const { return itype(reinterpret_cast(this) | 1); } constexpr itype stream() const { return increment() >> 1; } static constexpr bool can_specify_stream = false; static constexpr size_t streams_pow2() { return (sizeof(itype) < sizeof(size_t) ? sizeof(itype) : sizeof(size_t))*8 - 1u; } protected: constexpr unique_stream() = default; }; /* * no stream (mcg) */ template class no_stream { protected: static constexpr bool is_mcg = true; // Is never called, but is provided for symmetry with specific_stream void set_stream(...) { abort(); } public: typedef itype state_type; static constexpr itype increment() { return 0; } static constexpr bool can_specify_stream = false; static constexpr size_t streams_pow2() { return 0u; } protected: constexpr no_stream() = default; }; /* * single stream/sequence (oneseq) */ template class oneseq_stream : public default_increment { protected: static constexpr bool is_mcg = false; // Is never called, but is provided for symmetry with specific_stream void set_stream(...) { abort(); } public: typedef itype state_type; static constexpr itype stream() { return default_increment::increment() >> 1; } static constexpr bool can_specify_stream = false; static constexpr size_t streams_pow2() { return 0u; } protected: constexpr oneseq_stream() = default; }; /* * specific stream */ template class specific_stream { protected: static constexpr bool is_mcg = false; itype inc_ = default_increment::increment(); public: typedef itype state_type; typedef itype stream_state; constexpr itype increment() const { return inc_; } itype stream() { return inc_ >> 1; } void set_stream(itype specific_seq) { inc_ = (specific_seq << 1) | 1; } static constexpr bool can_specify_stream = true; static constexpr size_t streams_pow2() { return (sizeof(itype)*8) - 1u; } protected: specific_stream() = default; specific_stream(itype specific_seq) : inc_((specific_seq << 1) | itype(1U)) { // Nothing (else) to do. } }; /* * This is where it all comes together. This function joins together three * mixin classes which define * - the LCG additive constant (the stream) * - the LCG multiplier * - the output function * in addition, we specify the type of the LCG state, and the result type, * and whether to use the pre-advance version of the state for the output * (increasing instruction-level parallelism) or the post-advance version * (reducing register pressure). * * Given the high level of parameterization, the code has to use some * template-metaprogramming tricks to handle some of the suble variations * involved. */ template , typename multiplier_mixin = default_multiplier > class engine : protected output_mixin, public stream_mixin, protected multiplier_mixin { protected: itype state_; struct can_specify_stream_tag {}; struct no_specifiable_stream_tag {}; using stream_mixin::increment; using multiplier_mixin::multiplier; public: typedef xtype result_type; typedef itype state_type; static constexpr size_t period_pow2() { return sizeof(state_type)*8 - 2*stream_mixin::is_mcg; } // It would be nice to use std::numeric_limits for these, but // we can't be sure that it'd be defined for the 128-bit types. static constexpr result_type min() { return result_type(0UL); } static constexpr result_type max() { return ~result_type(0UL); } protected: itype bump(itype state) { return state * multiplier() + increment(); } itype base_generate() { return state_ = bump(state_); } itype base_generate0() { itype old_state = state_; state_ = bump(state_); return old_state; } public: result_type operator()() { if (output_previous) return this->output(base_generate0()); else return this->output(base_generate()); } result_type operator()(result_type upper_bound) { return bounded_rand(*this, upper_bound); } protected: static itype advance(itype state, itype delta, itype cur_mult, itype cur_plus); static itype distance(itype cur_state, itype newstate, itype cur_mult, itype cur_plus, itype mask = ~itype(0U)); itype distance(itype newstate, itype mask = ~itype(0U)) const { return distance(state_, newstate, multiplier(), increment(), mask); } public: void advance(itype delta) { state_ = advance(state_, delta, this->multiplier(), this->increment()); } void backstep(itype delta) { advance(-delta); } void discard(itype delta) { advance(delta); } bool wrapped() { if (stream_mixin::is_mcg) { // For MCGs, the low order two bits never change. In this // implementation, we keep them fixed at 3 to make this test // easier. return state_ == 3; } else { return state_ == 0; } } engine(itype state = itype(0xcafef00dd15ea5e5ULL)) : state_(this->is_mcg ? state|state_type(3U) : bump(state + this->increment())) { // Nothing else to do. } // This function may or may not exist. It thus has to be a template // to use SFINAE; users don't have to worry about its template-ness. template engine(itype state, typename sm::stream_state stream_seed) : stream_mixin(stream_seed), state_(this->is_mcg ? state|state_type(3U) : bump(state + this->increment())) { // Nothing else to do. } template engine(SeedSeq&& seedSeq, typename std::enable_if< !stream_mixin::can_specify_stream && !std::is_convertible::value && !std::is_convertible::value, no_specifiable_stream_tag>::type = {}) : engine(generate_one(std::forward(seedSeq))) { // Nothing else to do. } template engine(SeedSeq&& seedSeq, typename std::enable_if< stream_mixin::can_specify_stream && !std::is_convertible::value && !std::is_convertible::value, can_specify_stream_tag>::type = {}) : engine(generate_one(seedSeq), generate_one(seedSeq)) { // Nothing else to do. } template void seed(Args&&... args) { new (this) engine(std::forward(args)...); } template friend bool operator==(const engine&, const engine&); template friend itype1 operator-(const engine&, const engine&); template friend std::basic_ostream& operator<<(std::basic_ostream& out, const engine&); template friend std::basic_istream& operator>>(std::basic_istream& in, engine& rng); }; template std::basic_ostream& operator<<(std::basic_ostream& out, const engine& rng) { auto orig_flags = out.flags(std::ios_base::dec | std::ios_base::left); auto space = out.widen(' '); auto orig_fill = out.fill(); out << rng.multiplier() << space << rng.increment() << space << rng.state_; out.flags(orig_flags); out.fill(orig_fill); return out; } template std::basic_istream& operator>>(std::basic_istream& in, engine& rng) { auto orig_flags = in.flags(std::ios_base::dec | std::ios_base::skipws); itype multiplier, increment, state; in >> multiplier >> increment >> state; if (!in.fail()) { bool good = true; if (multiplier != rng.multiplier()) { good = false; } else if (rng.can_specify_stream) { rng.set_stream(increment >> 1); } else if (increment != rng.increment()) { good = false; } if (good) { rng.state_ = state; } else { in.clear(std::ios::failbit); } } in.flags(orig_flags); return in; } template itype engine::advance( itype state, itype delta, itype cur_mult, itype cur_plus) { // The method used here is based on Brown, "Random Number Generation // with Arbitrary Stride,", Transactions of the American Nuclear // Society (Nov. 1994). The algorithm is very similar to fast // exponentiation. // // Even though delta is an unsigned integer, we can pass a // signed integer to go backwards, it just goes "the long way round". constexpr itype ZERO = 0u; // itype may be a non-trivial types, so constexpr itype ONE = 1u; // we define some ugly constants. itype acc_mult = 1; itype acc_plus = 0; while (delta > ZERO) { if (delta & ONE) { acc_mult *= cur_mult; acc_plus = acc_plus*cur_mult + cur_plus; } cur_plus = (cur_mult+ONE)*cur_plus; cur_mult *= cur_mult; delta >>= 1; } return acc_mult * state + acc_plus; } template itype engine::distance( itype cur_state, itype newstate, itype cur_mult, itype cur_plus, itype mask) { constexpr itype ONE = 1u; // itype could be weird, so use constant itype the_bit = stream_mixin::is_mcg ? itype(4u) : itype(1u); itype distance = 0u; while ((cur_state & mask) != (newstate & mask)) { if ((cur_state & the_bit) != (newstate & the_bit)) { cur_state = cur_state * cur_mult + cur_plus; distance |= the_bit; } assert((cur_state & the_bit) == (newstate & the_bit)); the_bit <<= 1; cur_plus = (cur_mult+ONE)*cur_plus; cur_mult *= cur_mult; } return stream_mixin::is_mcg ? distance >> 2 : distance; } template itype operator-(const engine& lhs, const engine& rhs) { if (lhs.multiplier() != rhs.multiplier() || lhs.increment() != rhs.increment()) throw std::logic_error("incomparable generators"); return rhs.distance(lhs.state_); } template bool operator==(const engine& lhs, const engine& rhs) { return (lhs.multiplier() == rhs.multiplier()) && (lhs.increment() == rhs.increment()) && (lhs.state_ == rhs.state_); } template inline bool operator!=(const engine& lhs, const engine& rhs) { return !operator==(lhs,rhs); } template class output_mixin, bool output_previous = (sizeof(itype) <= 8)> using oneseq_base = engine, output_previous, oneseq_stream >; template class output_mixin, bool output_previous = (sizeof(itype) <= 8)> using unique_base = engine, output_previous, unique_stream >; template class output_mixin, bool output_previous = (sizeof(itype) <= 8)> using setseq_base = engine, output_previous, specific_stream >; template class output_mixin, bool output_previous = (sizeof(itype) <= 8)> using mcg_base = engine, output_previous, no_stream >; /* * OUTPUT FUNCTIONS. * * These are the core of the PCG generation scheme. They specify how to * turn the base LCG's internal state into the output value of the final * generator. * * They're implemented as mixin classes. * * All of the classes have code that is written to allow it to be applied * at *arbitrary* bit sizes, although in practice they'll only be used at * standard sizes supported by C++. */ /* * XSH RS -- high xorshift, followed by a random shift * * Fast. A good performer. */ template struct xsh_rs_mixin { static xtype output(itype internal) { constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8); constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype) * 8); constexpr bitcount_t sparebits = bits - xtypebits; constexpr bitcount_t opbits = sparebits-5 >= 64 ? 5 : sparebits-4 >= 32 ? 4 : sparebits-3 >= 16 ? 3 : sparebits-2 >= 4 ? 2 : sparebits-1 >= 1 ? 1 : 0; constexpr bitcount_t mask = (1 << opbits) - 1; constexpr bitcount_t maxrandshift = mask; constexpr bitcount_t topspare = opbits; constexpr bitcount_t bottomspare = sparebits - topspare; constexpr bitcount_t xshift = topspare + (xtypebits+maxrandshift)/2; bitcount_t rshift = opbits ? bitcount_t(internal >> (bits - opbits)) & mask : 0; internal ^= internal >> xshift; xtype result = xtype(internal >> (bottomspare - maxrandshift + rshift)); return result; } }; /* * XSH RR -- high xorshift, followed by a random rotate * * Fast. A good performer. Slightly better statistically than XSH RS. */ template struct xsh_rr_mixin { static xtype output(itype internal) { constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8); constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype)*8); constexpr bitcount_t sparebits = bits - xtypebits; constexpr bitcount_t wantedopbits = xtypebits >= 128 ? 7 : xtypebits >= 64 ? 6 : xtypebits >= 32 ? 5 : xtypebits >= 16 ? 4 : 3; constexpr bitcount_t opbits = sparebits >= wantedopbits ? wantedopbits : sparebits; constexpr bitcount_t amplifier = wantedopbits - opbits; constexpr bitcount_t mask = (1 << opbits) - 1; constexpr bitcount_t topspare = opbits; constexpr bitcount_t bottomspare = sparebits - topspare; constexpr bitcount_t xshift = (topspare + xtypebits)/2; bitcount_t rot = opbits ? bitcount_t(internal >> (bits - opbits)) & mask : 0; bitcount_t amprot = (rot << amplifier) & mask; internal ^= internal >> xshift; xtype result = xtype(internal >> bottomspare); result = rotr(result, amprot); return result; } }; /* * RXS -- random xorshift */ template struct rxs_mixin { static xtype output_rxs(itype internal) { constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8); constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype)*8); constexpr bitcount_t shift = bits - xtypebits; constexpr bitcount_t extrashift = (xtypebits - shift)/2; bitcount_t rshift = shift > 64+8 ? (internal >> (bits - 6)) & 63 : shift > 32+4 ? (internal >> (bits - 5)) & 31 : shift > 16+2 ? (internal >> (bits - 4)) & 15 : shift > 8+1 ? (internal >> (bits - 3)) & 7 : shift > 4+1 ? (internal >> (bits - 2)) & 3 : shift > 2+1 ? (internal >> (bits - 1)) & 1 : 0; internal ^= internal >> (shift + extrashift - rshift); xtype result = internal >> rshift; return result; } }; /* * RXS M XS -- random xorshift, mcg multiply, fixed xorshift * * The most statistically powerful generator, but all those steps * make it slower than some of the others. We give it the rottenest jobs. * * Because it's usually used in contexts where the state type and the * result type are the same, it is a permutation and is thus invertable. * We thus provide a function to invert it. This function is used to * for the "inside out" generator used by the extended generator. */ /* Defined type-based concepts for the multiplication step. They're actually * all derived by truncating the 128-bit, which was computed to be a good * "universal" constant. */ template struct mcg_multiplier { // Not defined for an arbitrary type }; template struct mcg_unmultiplier { // Not defined for an arbitrary type }; PCG_DEFINE_CONSTANT(uint8_t, mcg, multiplier, 217U) PCG_DEFINE_CONSTANT(uint8_t, mcg, unmultiplier, 105U) PCG_DEFINE_CONSTANT(uint16_t, mcg, multiplier, 62169U) PCG_DEFINE_CONSTANT(uint16_t, mcg, unmultiplier, 28009U) PCG_DEFINE_CONSTANT(uint32_t, mcg, multiplier, 277803737U) PCG_DEFINE_CONSTANT(uint32_t, mcg, unmultiplier, 2897767785U) PCG_DEFINE_CONSTANT(uint64_t, mcg, multiplier, 12605985483714917081ULL) PCG_DEFINE_CONSTANT(uint64_t, mcg, unmultiplier, 15009553638781119849ULL) PCG_DEFINE_CONSTANT(pcg128_t, mcg, multiplier, PCG_128BIT_CONSTANT(17766728186571221404ULL, 12605985483714917081ULL)) PCG_DEFINE_CONSTANT(pcg128_t, mcg, unmultiplier, PCG_128BIT_CONSTANT(14422606686972528997ULL, 15009553638781119849ULL)) template struct rxs_m_xs_mixin { static xtype output(itype internal) { constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype) * 8); constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8); constexpr bitcount_t opbits = xtypebits >= 128 ? 6 : xtypebits >= 64 ? 5 : xtypebits >= 32 ? 4 : xtypebits >= 16 ? 3 : 2; constexpr bitcount_t shift = bits - xtypebits; constexpr bitcount_t mask = (1 << opbits) - 1; bitcount_t rshift = opbits ? bitcount_t(internal >> (bits - opbits)) & mask : 0; internal ^= internal >> (opbits + rshift); internal *= mcg_multiplier::multiplier(); xtype result = internal >> shift; result ^= result >> ((2U*xtypebits+2U)/3U); return result; } static itype unoutput(itype internal) { constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8); constexpr bitcount_t opbits = bits >= 128 ? 6 : bits >= 64 ? 5 : bits >= 32 ? 4 : bits >= 16 ? 3 : 2; constexpr bitcount_t mask = (1 << opbits) - 1; internal = unxorshift(internal, bits, (2U*bits+2U)/3U); internal *= mcg_unmultiplier::unmultiplier(); bitcount_t rshift = opbits ? (internal >> (bits - opbits)) & mask : 0; internal = unxorshift(internal, bits, opbits + rshift); return internal; } }; /* * RXS M -- random xorshift, mcg multiply */ template struct rxs_m_mixin { static xtype output(itype internal) { constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype) * 8); constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8); constexpr bitcount_t opbits = xtypebits >= 128 ? 6 : xtypebits >= 64 ? 5 : xtypebits >= 32 ? 4 : xtypebits >= 16 ? 3 : 2; constexpr bitcount_t shift = bits - xtypebits; constexpr bitcount_t mask = (1 << opbits) - 1; bitcount_t rshift = opbits ? (internal >> (bits - opbits)) & mask : 0; internal ^= internal >> (opbits + rshift); internal *= mcg_multiplier::multiplier(); xtype result = internal >> shift; return result; } }; /* * XSL RR -- fixed xorshift (to low bits), random rotate * * Useful for 128-bit types that are split across two CPU registers. */ template struct xsl_rr_mixin { static xtype output(itype internal) { constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype) * 8); constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8); constexpr bitcount_t sparebits = bits - xtypebits; constexpr bitcount_t wantedopbits = xtypebits >= 128 ? 7 : xtypebits >= 64 ? 6 : xtypebits >= 32 ? 5 : xtypebits >= 16 ? 4 : 3; constexpr bitcount_t opbits = sparebits >= wantedopbits ? wantedopbits : sparebits; constexpr bitcount_t amplifier = wantedopbits - opbits; constexpr bitcount_t mask = (1 << opbits) - 1; constexpr bitcount_t topspare = sparebits; constexpr bitcount_t bottomspare = sparebits - topspare; constexpr bitcount_t xshift = (topspare + xtypebits) / 2; bitcount_t rot = opbits ? bitcount_t(internal >> (bits - opbits)) & mask : 0; bitcount_t amprot = (rot << amplifier) & mask; internal ^= internal >> xshift; xtype result = xtype(internal >> bottomspare); result = rotr(result, amprot); return result; } }; /* * XSL RR RR -- fixed xorshift (to low bits), random rotate (both parts) * * Useful for 128-bit types that are split across two CPU registers. * If you really want an invertable 128-bit RNG, I guess this is the one. */ template struct halfsize_trait {}; template <> struct halfsize_trait { typedef uint64_t type; }; template <> struct halfsize_trait { typedef uint32_t type; }; template <> struct halfsize_trait { typedef uint16_t type; }; template <> struct halfsize_trait { typedef uint8_t type; }; template struct xsl_rr_rr_mixin { typedef typename halfsize_trait::type htype; static itype output(itype internal) { constexpr bitcount_t htypebits = bitcount_t(sizeof(htype) * 8); constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8); constexpr bitcount_t sparebits = bits - htypebits; constexpr bitcount_t wantedopbits = htypebits >= 128 ? 7 : htypebits >= 64 ? 6 : htypebits >= 32 ? 5 : htypebits >= 16 ? 4 : 3; constexpr bitcount_t opbits = sparebits >= wantedopbits ? wantedopbits : sparebits; constexpr bitcount_t amplifier = wantedopbits - opbits; constexpr bitcount_t mask = (1 << opbits) - 1; constexpr bitcount_t topspare = sparebits; constexpr bitcount_t xshift = (topspare + htypebits) / 2; bitcount_t rot = opbits ? bitcount_t(internal >> (bits - opbits)) & mask : 0; bitcount_t amprot = (rot << amplifier) & mask; internal ^= internal >> xshift; htype lowbits = htype(internal); lowbits = rotr(lowbits, amprot); htype highbits = htype(internal >> topspare); bitcount_t rot2 = lowbits & mask; bitcount_t amprot2 = (rot2 << amplifier) & mask; highbits = rotr(highbits, amprot2); return (itype(highbits) << topspare) ^ itype(lowbits); } }; /* * XSH -- fixed xorshift (to high bits) * * You shouldn't use this at 64-bits or less. */ template struct xsh_mixin { static xtype output(itype internal) { constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype) * 8); constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8); constexpr bitcount_t sparebits = bits - xtypebits; constexpr bitcount_t topspare = 0; constexpr bitcount_t bottomspare = sparebits - topspare; constexpr bitcount_t xshift = (topspare + xtypebits) / 2; internal ^= internal >> xshift; xtype result = internal >> bottomspare; return result; } }; /* * XSL -- fixed xorshift (to low bits) * * You shouldn't use this at 64-bits or less. */ template struct xsl_mixin { inline xtype output(itype internal) { constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype) * 8); constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8); constexpr bitcount_t sparebits = bits - xtypebits; constexpr bitcount_t topspare = sparebits; constexpr bitcount_t bottomspare = sparebits - topspare; constexpr bitcount_t xshift = (topspare + xtypebits) / 2; internal ^= internal >> xshift; xtype result = internal >> bottomspare; return result; } }; /* ---- End of Output Functions ---- */ template struct inside_out : private baseclass { inside_out() = delete; typedef typename baseclass::result_type result_type; typedef typename baseclass::state_type state_type; static_assert(sizeof(result_type) == sizeof(state_type), "Require a RNG whose output function is a permutation"); static bool external_step(result_type& randval, size_t i) { state_type state = baseclass::unoutput(randval); state = state * baseclass::multiplier() + baseclass::increment() + state_type(i*2); result_type result = baseclass::output(state); randval = result; state_type zero = baseclass::is_mcg ? state & state_type(3U) : state_type(0U); return result == zero; } static bool external_advance(result_type& randval, size_t i, result_type delta, bool forwards = true) { state_type state = baseclass::unoutput(randval); state_type mult = baseclass::multiplier(); state_type inc = baseclass::increment() + state_type(i*2); state_type zero = baseclass::is_mcg ? state & state_type(3U) : state_type(0U); state_type dist_to_zero = baseclass::distance(state, zero, mult, inc); bool crosses_zero = forwards ? dist_to_zero <= delta : (-dist_to_zero) <= delta; if (!forwards) delta = -delta; state = baseclass::advance(state, delta, mult, inc); randval = baseclass::output(state); return crosses_zero; } }; template class extended : public baseclass { public: typedef typename baseclass::state_type state_type; typedef typename baseclass::result_type result_type; typedef inside_out insideout; private: static constexpr bitcount_t rtypebits = sizeof(result_type)*8; static constexpr bitcount_t stypebits = sizeof(state_type)*8; static constexpr bitcount_t tick_limit_pow2 = 64U; static constexpr size_t table_size = 1UL << table_pow2; static constexpr size_t table_shift = stypebits - table_pow2; static constexpr state_type table_mask = (state_type(1U) << table_pow2) - state_type(1U); static constexpr bool may_tick = (advance_pow2 < stypebits) && (advance_pow2 < tick_limit_pow2); static constexpr size_t tick_shift = stypebits - advance_pow2; static constexpr state_type tick_mask = may_tick ? state_type( (uint64_t(1) << (advance_pow2*may_tick)) - 1) // ^-- stupidity to appease GCC warnings : ~state_type(0U); static constexpr bool may_tock = stypebits < tick_limit_pow2; result_type data_[table_size]; PCG_NOINLINE void advance_table(); PCG_NOINLINE void advance_table(state_type delta, bool isForwards = true); result_type& get_extended_value() { state_type state = this->state_; if (kdd && baseclass::is_mcg) { // The low order bits of an MCG are constant, so drop them. state >>= 2; } size_t index = kdd ? state & table_mask : state >> table_shift; if (may_tick) { bool tick = kdd ? (state & tick_mask) == state_type(0u) : (state >> tick_shift) == state_type(0u); if (tick) advance_table(); } if (may_tock) { bool tock = state == state_type(0u); if (tock) advance_table(); } return data_[index]; } public: static constexpr size_t period_pow2() { return baseclass::period_pow2() + table_size*extvalclass::period_pow2(); } result_type operator()() { result_type rhs = get_extended_value(); result_type lhs = this->baseclass::operator()(); return lhs ^ rhs; } result_type operator()(result_type upper_bound) { return bounded_rand(*this, upper_bound); } void set(result_type wanted) { result_type& rhs = get_extended_value(); result_type lhs = this->baseclass::operator()(); rhs = lhs ^ wanted; } void advance(state_type distance, bool forwards = true); void backstep(state_type distance) { advance(distance, false); } extended(const result_type* data) : baseclass() { datainit(data); } extended(const result_type* data, state_type seed_) : baseclass(seed_) { datainit(data); } // This function may or may not exist. It thus has to be a template // to use SFINAE; users don't have to worry about its template-ness. template extended(const result_type* data, state_type seed_, typename bc::stream_state stream_seed) : baseclass(seed_, stream_seed) { datainit(data); } extended() : baseclass() { selfinit(); } extended(state_type seed_) : baseclass(seed_) { selfinit(); } // This function may or may not exist. It thus has to be a template // to use SFINAE; users don't have to worry about its template-ness. template extended(state_type seed_, typename bc::stream_state stream_seed) : baseclass(seed_, stream_seed) { selfinit(); } private: void selfinit(); void datainit(const result_type* data); public: template::value && !std::is_convertible::value>::type> extended(SeedSeq&& seedSeq) : baseclass(seedSeq) { generate_to(seedSeq, data_); } template void seed(Args&&... args) { new (this) extended(std::forward(args)...); } template friend bool operator==(const extended&, const extended&); template friend std::basic_ostream& operator<<(std::basic_ostream& out, const extended&); template friend std::basic_istream& operator>>(std::basic_istream& in, extended&); }; template void extended::datainit( const result_type* data) { for (size_t i = 0; i < table_size; ++i) data_[i] = data[i]; } template void extended::selfinit() { // We need to fill the extended table with something, and we have // very little provided data, so we use the base generator to // produce values. Although not ideal (use a seed sequence, folks!), // unexpected correlations are mitigated by // - using XOR differences rather than the number directly // - the way the table is accessed, its values *won't* be accessed // in the same order the were written. // - any strange correlations would only be apparent if we // were to backstep the generator so that the base generator // was generating the same values again result_type xdiff = baseclass::operator()() - baseclass::operator()(); for (size_t i = 0; i < table_size; ++i) { data_[i] = baseclass::operator()() ^ xdiff; } } template bool operator==(const extended& lhs, const extended& rhs) { auto& base_lhs = static_cast(lhs); auto& base_rhs = static_cast(rhs); return base_lhs == base_rhs && !memcmp((void*) lhs.data_, (void*) rhs.data_, sizeof(lhs.data_)); } template inline bool operator!=(const extended& lhs, const extended& rhs) { return lhs != rhs; } template std::basic_ostream& operator<<(std::basic_ostream& out, const extended& rng) { auto orig_flags = out.flags(std::ios_base::dec | std::ios_base::left); auto space = out.widen(' '); auto orig_fill = out.fill(); out << rng.multiplier() << space << rng.increment() << space << rng.state_; for (const auto& datum : rng.data_) out << space << datum; out.flags(orig_flags); out.fill(orig_fill); return out; } template std::basic_istream& operator>>(std::basic_istream& in, extended& rng) { extended new_rng; auto& base_rng = static_cast(new_rng); in >> base_rng; if (in.fail()) return in; auto orig_flags = in.flags(std::ios_base::dec | std::ios_base::skipws); for (auto& datum : new_rng.data_) { in >> datum; if (in.fail()) goto bail; } rng = new_rng; bail: in.flags(orig_flags); return in; } template void extended::advance_table() { bool carry = false; for (size_t i = 0; i < table_size; ++i) { if (carry) { carry = insideout::external_step(data_[i],i+1); } bool carry2 = insideout::external_step(data_[i],i+1); carry = carry || carry2; } } template void extended::advance_table( state_type delta, bool isForwards) { typedef typename baseclass::state_type base_state_t; typedef typename extvalclass::state_type ext_state_t; constexpr bitcount_t basebits = sizeof(base_state_t)*8; constexpr bitcount_t extbits = sizeof(ext_state_t)*8; static_assert(basebits <= extbits || advance_pow2 > 0, "Current implementation might overflow its carry"); base_state_t carry = 0; for (size_t i = 0; i < table_size; ++i) { base_state_t total_delta = carry + delta; ext_state_t trunc_delta = ext_state_t(total_delta); if (basebits > extbits) { carry = total_delta >> extbits; } else { carry = 0; } carry += insideout::external_advance(data_[i],i+1, trunc_delta, isForwards); } } template void extended::advance( state_type distance, bool forwards) { static_assert(kdd, "Efficient advance is too hard for non-kdd extension. " "For a weak advance, cast to base class"); state_type zero = baseclass::is_mcg ? this->state_ & state_type(3U) : state_type(0U); if (may_tick) { state_type ticks = distance >> (advance_pow2*may_tick); // ^-- stupidity to appease GCC // warnings state_type adv_mask = baseclass::is_mcg ? tick_mask << 2 : tick_mask; state_type next_advance_distance = this->distance(zero, adv_mask); if (!forwards) next_advance_distance = (-next_advance_distance) & tick_mask; if (next_advance_distance < (distance & tick_mask)) { ++ticks; } if (ticks) advance_table(ticks, forwards); } if (forwards) { if (may_tock && this->distance(zero) <= distance) advance_table(); baseclass::advance(distance); } else { if (may_tock && -(this->distance(zero)) <= distance) advance_table(state_type(1U), false); baseclass::advance(-distance); } } } // namespace pcg_detail namespace pcg_engines { using namespace pcg_detail; /* Predefined types for XSH RS */ typedef oneseq_base oneseq_xsh_rs_16_8; typedef oneseq_base oneseq_xsh_rs_32_16; typedef oneseq_base oneseq_xsh_rs_64_32; typedef oneseq_base oneseq_xsh_rs_128_64; typedef unique_base unique_xsh_rs_16_8; typedef unique_base unique_xsh_rs_32_16; typedef unique_base unique_xsh_rs_64_32; typedef unique_base unique_xsh_rs_128_64; typedef setseq_base setseq_xsh_rs_16_8; typedef setseq_base setseq_xsh_rs_32_16; typedef setseq_base setseq_xsh_rs_64_32; typedef setseq_base setseq_xsh_rs_128_64; typedef mcg_base mcg_xsh_rs_16_8; typedef mcg_base mcg_xsh_rs_32_16; typedef mcg_base mcg_xsh_rs_64_32; typedef mcg_base mcg_xsh_rs_128_64; /* Predefined types for XSH RR */ typedef oneseq_base oneseq_xsh_rr_16_8; typedef oneseq_base oneseq_xsh_rr_32_16; typedef oneseq_base oneseq_xsh_rr_64_32; typedef oneseq_base oneseq_xsh_rr_128_64; typedef unique_base unique_xsh_rr_16_8; typedef unique_base unique_xsh_rr_32_16; typedef unique_base unique_xsh_rr_64_32; typedef unique_base unique_xsh_rr_128_64; typedef setseq_base setseq_xsh_rr_16_8; typedef setseq_base setseq_xsh_rr_32_16; typedef setseq_base setseq_xsh_rr_64_32; typedef setseq_base setseq_xsh_rr_128_64; typedef mcg_base mcg_xsh_rr_16_8; typedef mcg_base mcg_xsh_rr_32_16; typedef mcg_base mcg_xsh_rr_64_32; typedef mcg_base mcg_xsh_rr_128_64; /* Predefined types for RXS M XS */ typedef oneseq_base oneseq_rxs_m_xs_8_8; typedef oneseq_base oneseq_rxs_m_xs_16_16; typedef oneseq_base oneseq_rxs_m_xs_32_32; typedef oneseq_base oneseq_rxs_m_xs_64_64; typedef oneseq_base oneseq_rxs_m_xs_128_128; typedef unique_base unique_rxs_m_xs_8_8; typedef unique_base unique_rxs_m_xs_16_16; typedef unique_base unique_rxs_m_xs_32_32; typedef unique_base unique_rxs_m_xs_64_64; typedef unique_base unique_rxs_m_xs_128_128; typedef setseq_base setseq_rxs_m_xs_8_8; typedef setseq_base setseq_rxs_m_xs_16_16; typedef setseq_base setseq_rxs_m_xs_32_32; typedef setseq_base setseq_rxs_m_xs_64_64; typedef setseq_base setseq_rxs_m_xs_128_128; // MCG versions don't make sense here, so aren't defined. /* Predefined types for XSL RR (only defined for "large" types) */ typedef oneseq_base oneseq_xsl_rr_64_32; typedef oneseq_base oneseq_xsl_rr_128_64; typedef unique_base unique_xsl_rr_64_32; typedef unique_base unique_xsl_rr_128_64; typedef setseq_base setseq_xsl_rr_64_32; typedef setseq_base setseq_xsl_rr_128_64; typedef mcg_base mcg_xsl_rr_64_32; typedef mcg_base mcg_xsl_rr_128_64; /* Predefined types for XSL RR RR (only defined for "large" types) */ typedef oneseq_base oneseq_xsl_rr_rr_64_64; typedef oneseq_base oneseq_xsl_rr_rr_128_128; typedef unique_base unique_xsl_rr_rr_64_64; typedef unique_base unique_xsl_rr_rr_128_128; typedef setseq_base setseq_xsl_rr_rr_64_64; typedef setseq_base setseq_xsl_rr_rr_128_128; // MCG versions don't make sense here, so aren't defined. /* Extended generators */ template using ext_std8 = extended; template using ext_std16 = extended; template using ext_std32 = extended; template using ext_std64 = extended; template using ext_oneseq_rxs_m_xs_32_32 = ext_std32; template using ext_mcg_xsh_rs_64_32 = ext_std32; template using ext_oneseq_xsh_rs_64_32 = ext_std32; template using ext_setseq_xsh_rr_64_32 = ext_std32; template using ext_mcg_xsl_rr_128_64 = ext_std64; template using ext_oneseq_xsl_rr_128_64 = ext_std64; template using ext_setseq_xsl_rr_128_64 = ext_std64; } // namespace pcg_engines typedef pcg_engines::setseq_xsh_rr_64_32 pcg32; typedef pcg_engines::oneseq_xsh_rr_64_32 pcg32_oneseq; typedef pcg_engines::unique_xsh_rr_64_32 pcg32_unique; typedef pcg_engines::mcg_xsh_rs_64_32 pcg32_fast; typedef pcg_engines::setseq_xsl_rr_128_64 pcg64; typedef pcg_engines::oneseq_xsl_rr_128_64 pcg64_oneseq; typedef pcg_engines::unique_xsl_rr_128_64 pcg64_unique; typedef pcg_engines::mcg_xsl_rr_128_64 pcg64_fast; typedef pcg_engines::setseq_rxs_m_xs_8_8 pcg8_once_insecure; typedef pcg_engines::setseq_rxs_m_xs_16_16 pcg16_once_insecure; typedef pcg_engines::setseq_rxs_m_xs_32_32 pcg32_once_insecure; typedef pcg_engines::setseq_rxs_m_xs_64_64 pcg64_once_insecure; typedef pcg_engines::setseq_xsl_rr_rr_128_128 pcg128_once_insecure; typedef pcg_engines::oneseq_rxs_m_xs_8_8 pcg8_oneseq_once_insecure; typedef pcg_engines::oneseq_rxs_m_xs_16_16 pcg16_oneseq_once_insecure; typedef pcg_engines::oneseq_rxs_m_xs_32_32 pcg32_oneseq_once_insecure; typedef pcg_engines::oneseq_rxs_m_xs_64_64 pcg64_oneseq_once_insecure; typedef pcg_engines::oneseq_xsl_rr_rr_128_128 pcg128_oneseq_once_insecure; // These two extended RNGs provide two-dimensionally equidistributed // 32-bit generators. pcg32_k2_fast occupies the same space as pcg64, // and can be called twice to generate 64 bits, but does not required // 128-bit math; on 32-bit systems, it's faster than pcg64 as well. typedef pcg_engines::ext_setseq_xsh_rr_64_32<6,16,true> pcg32_k2; typedef pcg_engines::ext_oneseq_xsh_rs_64_32<6,32,true> pcg32_k2_fast; // These eight extended RNGs have about as much state as arc4random // // - the k variants are k-dimensionally equidistributed // - the c variants offer better crypographic security // // (just how good the cryptographic security is is an open question) typedef pcg_engines::ext_setseq_xsh_rr_64_32<6,16,true> pcg32_k64; typedef pcg_engines::ext_mcg_xsh_rs_64_32<6,32,true> pcg32_k64_oneseq; typedef pcg_engines::ext_oneseq_xsh_rs_64_32<6,32,true> pcg32_k64_fast; typedef pcg_engines::ext_setseq_xsh_rr_64_32<6,16,false> pcg32_c64; typedef pcg_engines::ext_oneseq_xsh_rs_64_32<6,32,false> pcg32_c64_oneseq; typedef pcg_engines::ext_mcg_xsh_rs_64_32<6,32,false> pcg32_c64_fast; typedef pcg_engines::ext_setseq_xsl_rr_128_64<5,16,true> pcg64_k32; typedef pcg_engines::ext_oneseq_xsl_rr_128_64<5,128,true> pcg64_k32_oneseq; typedef pcg_engines::ext_mcg_xsl_rr_128_64<5,128,true> pcg64_k32_fast; typedef pcg_engines::ext_setseq_xsl_rr_128_64<5,16,false> pcg64_c32; typedef pcg_engines::ext_oneseq_xsl_rr_128_64<5,128,false> pcg64_c32_oneseq; typedef pcg_engines::ext_mcg_xsl_rr_128_64<5,128,false> pcg64_c32_fast; // These eight extended RNGs have more state than the Mersenne twister // // - the k variants are k-dimensionally equidistributed // - the c variants offer better crypographic security // // (just how good the cryptographic security is is an open question) typedef pcg_engines::ext_setseq_xsh_rr_64_32<10,16,true> pcg32_k1024; typedef pcg_engines::ext_oneseq_xsh_rs_64_32<10,32,true> pcg32_k1024_fast; typedef pcg_engines::ext_setseq_xsh_rr_64_32<10,16,false> pcg32_c1024; typedef pcg_engines::ext_oneseq_xsh_rs_64_32<10,32,false> pcg32_c1024_fast; typedef pcg_engines::ext_setseq_xsl_rr_128_64<10,16,true> pcg64_k1024; typedef pcg_engines::ext_oneseq_xsl_rr_128_64<10,128,true> pcg64_k1024_fast; typedef pcg_engines::ext_setseq_xsl_rr_128_64<10,16,false> pcg64_c1024; typedef pcg_engines::ext_oneseq_xsl_rr_128_64<10,128,false> pcg64_c1024_fast; // These generators have an insanely huge period (2^524352), and is suitable // for silly party tricks, such as dumping out 64 KB ZIP files at an arbitrary // point in the future. [Actually, over the full period of the generator, it // will produce every 64 KB ZIP file 2^64 times!] typedef pcg_engines::ext_setseq_xsh_rr_64_32<14,16,true> pcg32_k16384; typedef pcg_engines::ext_oneseq_xsh_rs_64_32<14,32,true> pcg32_k16384_fast; #endif // PCG_RAND_HPP_INCLUDED libcuckoo-0.3.1/tests/pcg/pcg_uint128.hpp000066400000000000000000000512311426042121400201520ustar00rootroot00000000000000/* * PCG Random Number Generation for C++ * * Copyright 2014 Melissa O'Neill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * For additional information about the PCG random number generation scheme, * including its license and other licensing options, visit * * http://www.pcg-random.org */ /* * This code provides a a C++ class that can provide 128-bit (or higher) * integers. To produce 2K-bit integers, it uses two K-bit integers, * placed in a union that allowes the code to also see them as four K/2 bit * integers (and access them either directly name, or by index). * * It may seem like we're reinventing the wheel here, because several * libraries already exist that support large integers, but most existing * libraries provide a very generic multiprecision code, but here we're * operating at a fixed size. Also, most other libraries are fairly * heavyweight. So we use a direct implementation. Sadly, it's much slower * than hand-coded assembly or direct CPU support. */ #ifndef PCG_UINT128_HPP_INCLUDED #define PCG_UINT128_HPP_INCLUDED 1 #include #include #include #include #include #include #include /* * We want to lay the type out the same way that a native type would be laid * out, which means we must know the machine's endian, at compile time. * This ugliness attempts to do so. */ #ifndef PCG_LITTLE_ENDIAN #if defined(__BYTE_ORDER__) #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define PCG_LITTLE_ENDIAN 1 #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #define PCG_LITTLE_ENDIAN 0 #else #error __BYTE_ORDER__ does not match a standard endian, pick a side #endif #elif __LITTLE_ENDIAN__ || _LITTLE_ENDIAN #define PCG_LITTLE_ENDIAN 1 #elif __BIG_ENDIAN__ || _BIG_ENDIAN #define PCG_LITTLE_ENDIAN 0 #elif __x86_64 || __x86_64__ || __i386 || __i386__ || _M_IX86 || _M_X64 #define PCG_LITTLE_ENDIAN 1 #elif __powerpc__ || __POWERPC__ || __ppc__ || __PPC__ \ || __m68k__ || __mc68000__ #define PCG_LITTLE_ENDIAN 0 #else #error Unable to determine target endianness #endif #endif namespace pcg_extras { // Recent versions of GCC have intrinsics we can use to quickly calculate // the number of leading and trailing zeros in a number. If possible, we // use them, otherwise we fall back to old-fashioned bit twiddling to figure // them out. #ifndef PCG_BITCOUNT_T typedef uint8_t bitcount_t; #else typedef PCG_BITCOUNT_T bitcount_t; #endif /* * Provide some useful helper functions * * flog2 floor(log2(x)) * * trailingzeros number of trailing zero bits */ #ifdef __GNUC__ // Any GNU-compatible compiler supporting C++11 has // some useful intrinsics we can use. inline bitcount_t flog2(uint32_t v) { return 31 - __builtin_clz(v); } inline bitcount_t trailingzeros(uint32_t v) { return __builtin_ctz(v); } inline bitcount_t flog2(uint64_t v) { #if UINT64_MAX == ULONG_MAX return 63 - __builtin_clzl(v); #elif UINT64_MAX == ULLONG_MAX return 63 - __builtin_clzll(v); #else #error Cannot find a function for uint64_t #endif } inline bitcount_t trailingzeros(uint64_t v) { #if UINT64_MAX == ULONG_MAX return __builtin_ctzl(v); #elif UINT64_MAX == ULLONG_MAX return __builtin_ctzll(v); #else #error Cannot find a function for uint64_t #endif } #else // Otherwise, we fall back to bit twiddling // implementations inline bitcount_t flog2(uint32_t v) { // Based on code by Eric Cole and Mark Dickinson, which appears at // https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn static const uint8_t multiplyDeBruijnBitPos[32] = { 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 }; v |= v >> 1; // first round down to one less than a power of 2 v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; return multiplyDeBruijnBitPos[(uint32_t)(v * 0x07C4ACDDU) >> 27]; } inline bitcount_t trailingzeros(uint32_t v) { static const uint8_t multiplyDeBruijnBitPos[32] = { 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 }; return multiplyDeBruijnBitPos[((uint32_t)((v & -v) * 0x077CB531U)) >> 27]; } inline bitcount_t flog2(uint64_t v) { uint32_t high = v >> 32; uint32_t low = uint32_t(v); return high ? 32+flog2(high) : flog2(low); } inline bitcount_t trailingzeros(uint64_t v) { uint32_t high = v >> 32; uint32_t low = uint32_t(v); return low ? trailingzeros(low) : trailingzeros(high)+32; } #endif template inline bitcount_t clog2(UInt v) { return flog2(v) + ((v & (-v)) != v); } template inline UInt addwithcarry(UInt x, UInt y, bool carryin, bool* carryout) { UInt half_result = y + carryin; UInt result = x + half_result; *carryout = (half_result < y) || (result < x); return result; } template inline UInt subwithcarry(UInt x, UInt y, bool carryin, bool* carryout) { UInt half_result = y + carryin; UInt result = x - half_result; *carryout = (half_result < y) || (result > x); return result; } template class uint_x4 { // private: public: union { #if PCG_LITTLE_ENDIAN struct { UInt v0, v1, v2, v3; } w; struct { UIntX2 v01, v23; } d; #else struct { UInt v3, v2, v1, v0; } w; struct { UIntX2 v23, v01; } d; #endif // For the array access versions, the code that uses the array // must handle endian itself. Yuck. UInt wa[4]; UIntX2 da[2]; }; public: uint_x4() = default; constexpr uint_x4(UInt v3, UInt v2, UInt v1, UInt v0) #if PCG_LITTLE_ENDIAN : w{v0, v1, v2, v3} #else : w{v3, v2, v1, v0} #endif { // Nothing (else) to do } constexpr uint_x4(UIntX2 v23, UIntX2 v01) #if PCG_LITTLE_ENDIAN : d{v01,v23} #else : d{v23,v01} #endif { // Nothing (else) to do } template::value && sizeof(Integral) <= sizeof(UIntX2)) >::type* = nullptr> constexpr uint_x4(Integral v01) #if PCG_LITTLE_ENDIAN : d{UIntX2(v01),0UL} #else : d{0UL,UIntX2(v01)} #endif { // Nothing (else) to do } explicit constexpr operator uint64_t() const { return d.v01; } explicit constexpr operator uint32_t() const { return w.v0; } explicit constexpr operator int() const { return w.v0; } explicit constexpr operator uint16_t() const { return w.v0; } explicit constexpr operator uint8_t() const { return w.v0; } typedef typename std::conditional::value, unsigned long long, unsigned long>::type uint_missing_t; explicit constexpr operator uint_missing_t() const { return d.v01; } explicit constexpr operator bool() const { return d.v01 || d.v23; } template friend uint_x4 operator*(const uint_x4&, const uint_x4&); template friend std::pair< uint_x4,uint_x4 > divmod(const uint_x4&, const uint_x4&); template friend uint_x4 operator+(const uint_x4&, const uint_x4&); template friend uint_x4 operator-(const uint_x4&, const uint_x4&); template friend uint_x4 operator<<(const uint_x4&, const uint_x4&); template friend uint_x4 operator>>(const uint_x4&, const uint_x4&); template friend uint_x4 operator&(const uint_x4&, const uint_x4&); template friend uint_x4 operator|(const uint_x4&, const uint_x4&); template friend uint_x4 operator^(const uint_x4&, const uint_x4&); template friend bool operator==(const uint_x4&, const uint_x4&); template friend bool operator!=(const uint_x4&, const uint_x4&); template friend bool operator<(const uint_x4&, const uint_x4&); template friend bool operator<=(const uint_x4&, const uint_x4&); template friend bool operator>(const uint_x4&, const uint_x4&); template friend bool operator>=(const uint_x4&, const uint_x4&); template friend uint_x4 operator~(const uint_x4&); template friend uint_x4 operator-(const uint_x4&); template friend bitcount_t flog2(const uint_x4&); template friend bitcount_t trailingzeros(const uint_x4&); uint_x4& operator*=(const uint_x4& rhs) { uint_x4 result = *this * rhs; return *this = result; } uint_x4& operator/=(const uint_x4& rhs) { uint_x4 result = *this / rhs; return *this = result; } uint_x4& operator%=(const uint_x4& rhs) { uint_x4 result = *this % rhs; return *this = result; } uint_x4& operator+=(const uint_x4& rhs) { uint_x4 result = *this + rhs; return *this = result; } uint_x4& operator-=(const uint_x4& rhs) { uint_x4 result = *this - rhs; return *this = result; } uint_x4& operator&=(const uint_x4& rhs) { uint_x4 result = *this & rhs; return *this = result; } uint_x4& operator|=(const uint_x4& rhs) { uint_x4 result = *this | rhs; return *this = result; } uint_x4& operator^=(const uint_x4& rhs) { uint_x4 result = *this ^ rhs; return *this = result; } uint_x4& operator>>=(bitcount_t shift) { uint_x4 result = *this >> shift; return *this = result; } uint_x4& operator<<=(bitcount_t shift) { uint_x4 result = *this << shift; return *this = result; } }; template bitcount_t flog2(const uint_x4& v) { #if PCG_LITTLE_ENDIAN for (uint8_t i = 4; i !=0; /* dec in loop */) { --i; #else for (uint8_t i = 0; i < 4; ++i) { #endif if (v.wa[i] == 0) continue; return flog2(v.wa[i]) + (sizeof(U)*CHAR_BIT)*i; } abort(); } template bitcount_t trailingzeros(const uint_x4& v) { #if PCG_LITTLE_ENDIAN for (uint8_t i = 0; i < 4; ++i) { #else for (uint8_t i = 4; i !=0; /* dec in loop */) { --i; #endif if (v.wa[i] != 0) return trailingzeros(v.wa[i]) + (sizeof(U)*CHAR_BIT)*i; } return (sizeof(U)*CHAR_BIT)*4; } template std::pair< uint_x4, uint_x4 > divmod(const uint_x4& orig_dividend, const uint_x4& divisor) { // If the dividend is less than the divisor, the answer is always zero. // This takes care of boundary cases like 0/x (which would otherwise be // problematic because we can't take the log of zero. (The boundary case // of division by zero is undefined.) if (orig_dividend < divisor) return { uint_x4(0UL), orig_dividend }; auto dividend = orig_dividend; auto log2_divisor = flog2(divisor); auto log2_dividend = flog2(dividend); // assert(log2_dividend >= log2_divisor); bitcount_t logdiff = log2_dividend - log2_divisor; constexpr uint_x4 ONE(1UL); if (logdiff == 0) return { ONE, dividend - divisor }; // Now we change the log difference to // floor(log2(divisor)) - ceil(log2(dividend)) // to ensure that we *underestimate* the result. logdiff -= 1; uint_x4 quotient(0UL); auto qfactor = ONE << logdiff; auto factor = divisor << logdiff; do { dividend -= factor; quotient += qfactor; while (dividend < factor) { factor >>= 1; qfactor >>= 1; } } while (dividend >= divisor); return { quotient, dividend }; } template uint_x4 operator/(const uint_x4& dividend, const uint_x4& divisor) { return divmod(dividend, divisor).first; } template uint_x4 operator%(const uint_x4& dividend, const uint_x4& divisor) { return divmod(dividend, divisor).second; } template uint_x4 operator*(const uint_x4& a, const uint_x4& b) { uint_x4 r = {0U, 0U, 0U, 0U}; bool carryin = false; bool carryout; UIntX2 a0b0 = UIntX2(a.w.v0) * UIntX2(b.w.v0); r.w.v0 = UInt(a0b0); r.w.v1 = UInt(a0b0 >> 32); UIntX2 a1b0 = UIntX2(a.w.v1) * UIntX2(b.w.v0); r.w.v2 = UInt(a1b0 >> 32); r.w.v1 = addwithcarry(r.w.v1, UInt(a1b0), carryin, &carryout); carryin = carryout; r.w.v2 = addwithcarry(r.w.v2, UInt(0U), carryin, &carryout); carryin = carryout; r.w.v3 = addwithcarry(r.w.v3, UInt(0U), carryin, &carryout); UIntX2 a0b1 = UIntX2(a.w.v0) * UIntX2(b.w.v1); carryin = false; r.w.v2 = addwithcarry(r.w.v2, UInt(a0b1 >> 32), carryin, &carryout); carryin = carryout; r.w.v3 = addwithcarry(r.w.v3, UInt(0U), carryin, &carryout); carryin = false; r.w.v1 = addwithcarry(r.w.v1, UInt(a0b1), carryin, &carryout); carryin = carryout; r.w.v2 = addwithcarry(r.w.v2, UInt(0U), carryin, &carryout); carryin = carryout; r.w.v3 = addwithcarry(r.w.v3, UInt(0U), carryin, &carryout); UIntX2 a1b1 = UIntX2(a.w.v1) * UIntX2(b.w.v1); carryin = false; r.w.v2 = addwithcarry(r.w.v2, UInt(a1b1), carryin, &carryout); carryin = carryout; r.w.v3 = addwithcarry(r.w.v3, UInt(a1b1 >> 32), carryin, &carryout); r.d.v23 += a.d.v01 * b.d.v23 + a.d.v23 * b.d.v01; return r; } template uint_x4 operator+(const uint_x4& a, const uint_x4& b) { uint_x4 r = {0U, 0U, 0U, 0U}; bool carryin = false; bool carryout; r.w.v0 = addwithcarry(a.w.v0, b.w.v0, carryin, &carryout); carryin = carryout; r.w.v1 = addwithcarry(a.w.v1, b.w.v1, carryin, &carryout); carryin = carryout; r.w.v2 = addwithcarry(a.w.v2, b.w.v2, carryin, &carryout); carryin = carryout; r.w.v3 = addwithcarry(a.w.v3, b.w.v3, carryin, &carryout); return r; } template uint_x4 operator-(const uint_x4& a, const uint_x4& b) { uint_x4 r = {0U, 0U, 0U, 0U}; bool carryin = false; bool carryout; r.w.v0 = subwithcarry(a.w.v0, b.w.v0, carryin, &carryout); carryin = carryout; r.w.v1 = subwithcarry(a.w.v1, b.w.v1, carryin, &carryout); carryin = carryout; r.w.v2 = subwithcarry(a.w.v2, b.w.v2, carryin, &carryout); carryin = carryout; r.w.v3 = subwithcarry(a.w.v3, b.w.v3, carryin, &carryout); return r; } template uint_x4 operator&(const uint_x4& a, const uint_x4& b) { return uint_x4(a.d.v23 & b.d.v23, a.d.v01 & b.d.v01); } template uint_x4 operator|(const uint_x4& a, const uint_x4& b) { return uint_x4(a.d.v23 | b.d.v23, a.d.v01 | b.d.v01); } template uint_x4 operator^(const uint_x4& a, const uint_x4& b) { return uint_x4(a.d.v23 ^ b.d.v23, a.d.v01 ^ b.d.v01); } template uint_x4 operator~(const uint_x4& v) { return uint_x4(~v.d.v23, ~v.d.v01); } template uint_x4 operator-(const uint_x4& v) { return uint_x4(0UL,0UL) - v; } template bool operator==(const uint_x4& a, const uint_x4& b) { return (a.d.v01 == b.d.v01) && (a.d.v23 == b.d.v23); } template bool operator!=(const uint_x4& a, const uint_x4& b) { return !operator==(a,b); } template bool operator<(const uint_x4& a, const uint_x4& b) { return (a.d.v23 < b.d.v23) || ((a.d.v23 == b.d.v23) && (a.d.v01 < b.d.v01)); } template bool operator>(const uint_x4& a, const uint_x4& b) { return operator<(b,a); } template bool operator<=(const uint_x4& a, const uint_x4& b) { return !(operator<(b,a)); } template bool operator>=(const uint_x4& a, const uint_x4& b) { return !(operator<(a,b)); } template uint_x4 operator<<(const uint_x4& v, const bitcount_t shift) { uint_x4 r = {0U, 0U, 0U, 0U}; const bitcount_t bits = sizeof(UInt) * CHAR_BIT; const bitcount_t bitmask = bits - 1; const bitcount_t shiftdiv = shift / bits; const bitcount_t shiftmod = shift & bitmask; if (shiftmod) { UInt carryover = 0; #if PCG_LITTLE_ENDIAN for (uint8_t out = shiftdiv, in = 0; out < 4; ++out, ++in) { #else for (uint8_t out = 4-shiftdiv, in = 4; out != 0; /* dec in loop */) { --out, --in; #endif r.wa[out] = (v.wa[in] << shiftmod) | carryover; carryover = (v.wa[in] >> (bits - shiftmod)); } } else { #if PCG_LITTLE_ENDIAN for (uint8_t out = shiftdiv, in = 0; out < 4; ++out, ++in) { #else for (uint8_t out = 4-shiftdiv, in = 4; out != 0; /* dec in loop */) { --out, --in; #endif r.wa[out] = v.wa[in]; } } return r; } template uint_x4 operator>>(const uint_x4& v, const bitcount_t shift) { uint_x4 r = {0U, 0U, 0U, 0U}; const bitcount_t bits = sizeof(UInt) * CHAR_BIT; const bitcount_t bitmask = bits - 1; const bitcount_t shiftdiv = shift / bits; const bitcount_t shiftmod = shift & bitmask; if (shiftmod) { UInt carryover = 0; #if PCG_LITTLE_ENDIAN for (uint8_t out = 4-shiftdiv, in = 4; out != 0; /* dec in loop */) { --out, --in; #else for (uint8_t out = shiftdiv, in = 0; out < 4; ++out, ++in) { #endif r.wa[out] = (v.wa[in] >> shiftmod) | carryover; carryover = (v.wa[in] << (bits - shiftmod)); } } else { #if PCG_LITTLE_ENDIAN for (uint8_t out = 4-shiftdiv, in = 4; out != 0; /* dec in loop */) { --out, --in; #else for (uint8_t out = shiftdiv, in = 0; out < 4; ++out, ++in) { #endif r.wa[out] = v.wa[in]; } } return r; } } // namespace pcg_extras #endif // PCG_UINT128_HPP_INCLUDED libcuckoo-0.3.1/tests/stress-tests/000077500000000000000000000000001426042121400173065ustar00rootroot00000000000000libcuckoo-0.3.1/tests/stress-tests/CMakeLists.txt000066400000000000000000000006361426042121400220530ustar00rootroot00000000000000add_executable(stress_checked stress_checked.cc) target_link_libraries(stress_checked PRIVATE test_util PRIVATE pcg PRIVATE libcuckoo ) add_executable(stress_unchecked stress_unchecked.cc) target_link_libraries(stress_unchecked PRIVATE test_util PRIVATE pcg PRIVATE libcuckoo ) add_test(NAME stress_checked COMMAND stress_checked) add_test(NAME stress_unchecked COMMAND stress_unchecked) libcuckoo-0.3.1/tests/stress-tests/stress_checked.cc000066400000000000000000000273201426042121400226120ustar00rootroot00000000000000// Tests concurrent inserts, deletes, updates, and finds. The test makes sure // that multiple operations are not run on the same key, so that the accuracy of // the operations can be verified. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef uint32_t KeyType; typedef std::string KeyType2; typedef uint32_t ValType; typedef int32_t ValType2; // The number of keys to size the table with, expressed as a power of // 2. This can be set with the command line flag --power size_t g_power = 25; size_t g_numkeys; // Holds 2^power // The number of threads spawned for each type of operation. This can // be set with the command line flag --thread-num size_t g_thread_num = 4; // Whether to disable inserts or not. This can be set with the command // line flag --disable-inserts bool g_disable_inserts = false; // Whether to disable deletes or not. This can be set with the command // line flag --disable-deletes bool g_disable_deletes = false; // Whether to disable updates or not. This can be set with the command // line flag --disable-updates bool g_disable_updates = false; // Whether to disable finds or not. This can be set with the command // line flag --disable-finds bool g_disable_finds = false; // How many seconds to run the test for. This can be set with the // command line flag --time size_t g_test_len = 10; // The seed for the random number generator. If this isn't set to a // nonzero value with the --seed flag, the current time is used size_t g_seed = 0; // Whether to use strings as the key bool g_use_strings = false; std::atomic num_inserts = ATOMIC_VAR_INIT(0); std::atomic num_deletes = ATOMIC_VAR_INIT(0); std::atomic num_updates = ATOMIC_VAR_INIT(0); std::atomic num_finds = ATOMIC_VAR_INIT(0); template class AllEnvironment { public: AllEnvironment() : table(g_numkeys), table2(g_numkeys), keys(g_numkeys), vals(g_numkeys), vals2(g_numkeys), in_table(new bool[g_numkeys]), in_use(new std::atomic_flag[g_numkeys]), val_dist(std::numeric_limits::min(), std::numeric_limits::max()), val_dist2(std::numeric_limits::min(), std::numeric_limits::max()), ind_dist(0, g_numkeys - 1), finished(false) { // Sets up the random number generator if (g_seed == 0) { g_seed = std::chrono::system_clock::now().time_since_epoch().count(); } std::cout << "seed = " << g_seed << std::endl; gen_seed = g_seed; // Fills in all the vectors except vals, which will be filled // in by the insertion threads. for (size_t i = 0; i < g_numkeys; i++) { keys[i] = generateKey(i); in_table[i] = false; in_use[i].clear(); } } libcuckoo::cuckoohash_map table; libcuckoo::cuckoohash_map table2; std::vector keys; std::vector vals; std::vector vals2; std::unique_ptr in_table; std::unique_ptr in_use; std::uniform_int_distribution val_dist; std::uniform_int_distribution val_dist2; std::uniform_int_distribution ind_dist; size_t gen_seed; // When set to true, it signals to the threads to stop running std::atomic finished; }; template void stress_insert_thread(AllEnvironment *env) { pcg64_fast gen(env->gen_seed); while (!env->finished.load()) { // Pick a random number between 0 and g_numkeys. If that slot is // not in use, lock the slot. Insert a random value into both // tables. The inserts should only be successful if the key // wasn't in the table. If the inserts succeeded, check that // the insertion were actually successful with another find // operation, and then store the values in their arrays and // set in_table to true and clear in_use size_t ind = env->ind_dist(gen); if (!env->in_use[ind].test_and_set()) { KType k = env->keys[ind]; ValType v = env->val_dist(gen); ValType2 v2 = env->val_dist2(gen); bool res = env->table.insert(k, v); bool res2 = env->table2.insert(k, v2); EXPECT_NE(res, env->in_table[ind]); EXPECT_NE(res2, env->in_table[ind]); if (res) { EXPECT_EQ(v, env->table.find(k)); EXPECT_EQ(v2, env->table2.find(k)); env->vals[ind] = v; env->vals2[ind] = v2; env->in_table[ind] = true; num_inserts.fetch_add(2, std::memory_order_relaxed); } env->in_use[ind].clear(); } } } template void delete_thread(AllEnvironment *env) { pcg64_fast gen(env->gen_seed); while (!env->finished.load()) { // Run deletes on a random key, check that the deletes // succeeded only if the keys were in the table. If the // deletes succeeded, check that the keys are indeed not in // the tables anymore, and then set in_table to false size_t ind = env->ind_dist(gen); if (!env->in_use[ind].test_and_set()) { KType k = env->keys[ind]; bool res = env->table.erase(k); bool res2 = env->table2.erase(k); EXPECT_EQ(res, env->in_table[ind]); EXPECT_EQ(res2, env->in_table[ind]); if (res) { ValType find_v = 0; ValType2 find_v2 = 0; EXPECT_FALSE(env->table.find(k, find_v)); EXPECT_FALSE(env->table2.find(k, find_v2)); env->in_table[ind] = false; num_deletes.fetch_add(2, std::memory_order_relaxed); } env->in_use[ind].clear(); } } } template void update_thread(AllEnvironment *env) { pcg64_fast gen(env->gen_seed); std::uniform_int_distribution third(0, 2); auto updatefn = [](ValType &v) { v += 3; }; auto updatefn2 = [](ValType2 &v) { v += 10; }; while (!env->finished.load()) { // Run updates, update_fns, or upserts on a random key, check // that the operations succeeded only if the keys were in the // table (or that they succeeded regardless if it's an // upsert). If successful, check that the keys are indeed in // the table with the new value, and then set in_table to true size_t ind = env->ind_dist(gen); if (!env->in_use[ind].test_and_set()) { KType k = env->keys[ind]; ValType v; ValType2 v2; bool res, res2; switch (third(gen)) { case 0: // update v = env->val_dist(gen); v2 = env->val_dist2(gen); res = env->table.update(k, v); res2 = env->table2.update(k, v2); EXPECT_EQ(res, env->in_table[ind]); EXPECT_EQ(res2, env->in_table[ind]); break; case 1: // update_fn v = env->vals[ind]; v2 = env->vals2[ind]; updatefn(v); updatefn2(v2); res = env->table.update_fn(k, updatefn); res2 = env->table2.update_fn(k, updatefn2); EXPECT_EQ(res, env->in_table[ind]); EXPECT_EQ(res2, env->in_table[ind]); break; case 2: // upsert if (env->in_table[ind]) { // Then it should run updatefn v = env->vals[ind]; v2 = env->vals2[ind]; updatefn(v); updatefn2(v2); } else { // Then it should run an insert v = env->val_dist(gen); v2 = env->val_dist2(gen); } // These upserts should always succeed, so set res and res2 to // true. env->table.upsert(k, updatefn, v); env->table2.upsert(k, updatefn2, v2); res = res2 = true; env->in_table[ind] = true; break; default: throw std::logic_error("Impossible"); } if (res) { EXPECT_EQ(v, env->table.find(k)); EXPECT_EQ(v2, env->table2.find(k)); env->vals[ind] = v; env->vals2[ind] = v2; num_updates.fetch_add(2, std::memory_order_relaxed); } env->in_use[ind].clear(); } } } template void find_thread(AllEnvironment *env) { pcg64_fast gen(env->gen_seed); while (!env->finished.load()) { // Run finds on a random key and check that the presence of // the keys matches in_table size_t ind = env->ind_dist(gen); if (!env->in_use[ind].test_and_set()) { KType k = env->keys[ind]; try { EXPECT_EQ(env->vals[ind], env->table.find(k)); EXPECT_TRUE(env->in_table[ind]); } catch (const std::out_of_range &) { EXPECT_FALSE(env->in_table[ind]); } try { EXPECT_EQ(env->vals2[ind], env->table2.find(k)); EXPECT_TRUE(env->in_table[ind]); } catch (const std::out_of_range &) { EXPECT_FALSE(env->in_table[ind]); } num_finds.fetch_add(2, std::memory_order_relaxed); env->in_use[ind].clear(); } } } // Spawns g_thread_num insert, delete, update, and find threads template void StressTest(AllEnvironment *env) { std::vector threads; for (size_t i = 0; i < g_thread_num; i++) { if (!g_disable_inserts) { threads.emplace_back(stress_insert_thread, env); } if (!g_disable_deletes) { threads.emplace_back(delete_thread, env); } if (!g_disable_updates) { threads.emplace_back(update_thread, env); } if (!g_disable_finds) { threads.emplace_back(find_thread, env); } } // Sleeps before ending the threads std::this_thread::sleep_for(std::chrono::seconds(g_test_len)); env->finished.store(true); for (size_t i = 0; i < threads.size(); i++) { threads[i].join(); } // Finds the number of slots that are filled size_t numfilled = 0; for (size_t i = 0; i < g_numkeys; i++) { if (env->in_table[i]) { numfilled++; } } EXPECT_EQ(numfilled, env->table.size()); std::cout << "----------Results----------" << std::endl; std::cout << "Number of inserts:\t" << num_inserts.load() << std::endl; std::cout << "Number of deletes:\t" << num_deletes.load() << std::endl; std::cout << "Number of updates:\t" << num_updates.load() << std::endl; std::cout << "Number of finds:\t" << num_finds.load() << std::endl; } int main(int argc, char **argv) { const char *args[] = {"--power", "--thread-num", "--time", "--seed"}; size_t *arg_vars[] = {&g_power, &g_thread_num, &g_test_len, &g_seed}; const char *arg_help[] = { "The number of keys to size the table with, expressed as a power of 2", "The number of threads to spawn for each type of operation", "The number of seconds to run the test for", "The seed for the random number generator"}; const char *flags[] = {"--disable-inserts", "--disable-deletes", "--disable-updates", "--disable-finds", "--use-strings"}; bool *flag_vars[] = {&g_disable_inserts, &g_disable_deletes, &g_disable_updates, &g_disable_finds, &g_use_strings}; const char *flag_help[] = { "If set, no inserts will be run", "If set, no deletes will be run", "If set, no updates will be run", "If set, no finds will be run", "If set, the key type of the map will be std::string"}; parse_flags(argc, argv, "Runs a stress test on inserts, deletes, and finds", args, arg_vars, arg_help, sizeof(args) / sizeof(const char *), flags, flag_vars, flag_help, sizeof(flags) / sizeof(const char *)); g_numkeys = 1U << g_power; if (g_use_strings) { auto *env = new AllEnvironment; StressTest(env); delete env; } else { auto *env = new AllEnvironment; StressTest(env); delete env; } return main_return_value; } libcuckoo-0.3.1/tests/stress-tests/stress_unchecked.cc000066400000000000000000000250271426042121400231570ustar00rootroot00000000000000// Tests all operations and iterators concurrently. It doesn't check any // operation for correctness, only making sure that everything completes without // crashing. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef uint32_t KeyType; typedef std::string KeyType2; typedef uint32_t ValType; typedef int32_t ValType2; // The number of keys to size the table with, expressed as a power of // 2. This can be set with the command line flag --power size_t g_power = 24; size_t g_numkeys; // Holds 2^power // The number of threads spawned for each type of operation. This can // be set with the command line flag --thread-num size_t g_thread_num = 4; // Whether to disable inserts or not. This can be set with the command // line flag --disable-inserts bool g_disable_inserts = false; // Whether to disable deletes or not. This can be set with the command // line flag --disable-deletes bool g_disable_deletes = false; // Whether to disable updates or not. This can be set with the command // line flag --disable-updates bool g_disable_updates = false; // Whether to disable finds or not. This can be set with the command // line flag --disable-finds bool g_disable_finds = false; // Whether to disable resizes operations or not. This can be set with // the command line flag --disable-resizes bool g_disable_resizes = false; // Whether to disable iterator operations or not. This can be set with // the command line flag --disable-iterators bool g_disable_iterators = false; // Whether to disable statistic operations or not. This can be set with // the command line flag --disable-misc bool g_disable_misc = false; // Whether to disable clear operations or not. This can be set with // the command line flag --disable-clears bool g_disable_clears = false; // How many seconds to run the test for. This can be set with the // command line flag --time size_t g_test_len = 10; // The seed for the random number generator. If this isn't set to a // nonzero value with the --seed flag, the current time is used size_t g_seed = 0; // Whether to use strings as the key bool g_use_strings = false; template class AllEnvironment { public: AllEnvironment() : table(g_numkeys), table2(g_numkeys), finished(false) { // Sets up the random number generator if (g_seed == 0) { g_seed = std::chrono::system_clock::now().time_since_epoch().count(); } std::cout << "seed = " << g_seed << std::endl; gen_seed = g_seed; // Set minimum load factor and maximum hashpower to unbounded, so weird // operations don't throw exceptions. table.minimum_load_factor(0.0); table2.minimum_load_factor(0.0); table.maximum_hashpower(libcuckoo::NO_MAXIMUM_HASHPOWER); table2.maximum_hashpower(libcuckoo::NO_MAXIMUM_HASHPOWER); } libcuckoo::cuckoohash_map table; libcuckoo::cuckoohash_map table2; size_t gen_seed; std::atomic finished; }; template void stress_insert_thread(AllEnvironment *env, size_t thread_seed) { std::uniform_int_distribution ind_dist; std::uniform_int_distribution val_dist; std::uniform_int_distribution val_dist2; pcg64_fast gen(thread_seed); while (!env->finished.load()) { // Insert a random key into the table KType k = generateKey(ind_dist(gen)); env->table.insert(k, val_dist(gen)); env->table2.insert(k, val_dist2(gen)); env->table.insert_or_assign(k, val_dist(gen)); env->table2.insert_or_assign(k, val_dist2(gen)); } } template void delete_thread(AllEnvironment *env, size_t thread_seed) { std::uniform_int_distribution ind_dist; pcg64_fast gen(thread_seed); while (!env->finished.load()) { // Run deletes on a random key. const KType k = generateKey(ind_dist(gen)); env->table.erase(k); env->table2.erase(k); } } template void update_thread(AllEnvironment *env, size_t thread_seed) { std::uniform_int_distribution ind_dist; std::uniform_int_distribution val_dist; std::uniform_int_distribution val_dist2; std::uniform_int_distribution third(0, 2); pcg64_fast gen(thread_seed); auto updatefn = [](ValType &v) { v += 3; }; while (!env->finished.load()) { // Run updates, update_funcs, or upserts on a random key. const KType k = generateKey(ind_dist(gen)); switch (third(gen)) { case 0: // update env->table.update(k, val_dist(gen)); env->table2.update(k, val_dist2(gen)); break; case 1: // update_fn env->table.update_fn(k, updatefn); env->table2.update_fn(k, [](ValType2 &v) { v += 10; }); break; case 2: env->table.upsert(k, updatefn, val_dist(gen)); env->table2.upsert(k, [](ValType2 &v) { v -= 50; }, val_dist2(gen)); } } } template void find_thread(AllEnvironment *env, size_t thread_seed) { std::uniform_int_distribution ind_dist; pcg64_fast gen(thread_seed); ValType v; while (!env->finished.load()) { // Run finds on a random key. const KType k = generateKey(ind_dist(gen)); env->table.find(k, v); try { env->table2.find(k); } catch (...) { } } } template void resize_thread(AllEnvironment *env, size_t thread_seed) { pcg64_fast gen(thread_seed); // Resizes at a random time const size_t sleep_time = gen() % g_test_len; std::this_thread::sleep_for(std::chrono::seconds(sleep_time)); if (env->finished.load()) { return; } const size_t hashpower = env->table2.hashpower(); if (gen() & 1) { env->table.rehash(hashpower + 1); env->table.rehash(hashpower / 2); } else { env->table2.reserve((1U << (hashpower + 1)) * libcuckoo::DEFAULT_SLOT_PER_BUCKET); env->table2.reserve((1U << hashpower) * libcuckoo::DEFAULT_SLOT_PER_BUCKET); } } template void iterator_thread(AllEnvironment *env, size_t thread_seed) { pcg64_fast gen(thread_seed); // Runs an iteration operation at a random time const size_t sleep_time = gen() % g_test_len; std::this_thread::sleep_for(std::chrono::seconds(sleep_time)); if (env->finished.load()) { return; } auto lt = env->table2.lock_table(); for (auto &item : lt) { if (gen() & 1) { item.second++; } } } template void misc_thread(AllEnvironment *env) { // Runs all the misc functions pcg64_fast gen(g_seed); while (!env->finished.load()) { env->table.slot_per_bucket(); env->table.size(); env->table.empty(); env->table.bucket_count(); env->table.load_factor(); env->table.hash_function(); env->table.key_eq(); } } template void clear_thread(AllEnvironment *env, size_t thread_seed) { pcg64_fast gen(thread_seed); // Runs a clear operation at a random time const size_t sleep_time = gen() % g_test_len; std::this_thread::sleep_for(std::chrono::seconds(sleep_time)); if (env->finished.load()) { return; } env->table.clear(); } // Spawns thread_num threads for each type of operation template void StressTest(AllEnvironment *env) { std::vector threads; for (size_t i = 0; i < g_thread_num; i++) { if (!g_disable_inserts) { threads.emplace_back(stress_insert_thread, env, env->gen_seed++); } if (!g_disable_deletes) { threads.emplace_back(delete_thread, env, env->gen_seed++); } if (!g_disable_updates) { threads.emplace_back(update_thread, env, env->gen_seed++); } if (!g_disable_finds) { threads.emplace_back(find_thread, env, env->gen_seed++); } if (!g_disable_resizes) { threads.emplace_back(resize_thread, env, env->gen_seed++); } if (!g_disable_iterators) { threads.emplace_back(iterator_thread, env, env->gen_seed++); } if (!g_disable_misc) { threads.emplace_back(misc_thread, env); } if (!g_disable_clears) { threads.emplace_back(clear_thread, env, env->gen_seed++); } } // Sleeps before ending the threads std::this_thread::sleep_for(std::chrono::seconds(g_test_len)); env->finished.store(true); for (size_t i = 0; i < threads.size(); i++) { threads[i].join(); } std::cout << "----------Results----------" << std::endl; std::cout << "Final size:\t" << env->table.size() << std::endl; std::cout << "Final load factor:\t" << env->table.load_factor() << std::endl; } int main(int argc, char **argv) { const char *args[] = {"--power", "--thread-num", "--time", "--seed"}; size_t *arg_vars[] = {&g_power, &g_thread_num, &g_test_len, &g_seed}; const char *arg_help[] = { "The number of keys to size the table with, expressed as a power of 2", "The number of threads to spawn for each type of operation", "The number of seconds to run the test for", "The seed for the random number generator"}; const char *flags[] = { "--disable-inserts", "--disable-deletes", "--disable-updates", "--disable-finds", "--disable-resizes", "--disable-iterators", "--disable-misc", "--disable-clears", "--use-strings"}; bool *flag_vars[] = { &g_disable_inserts, &g_disable_deletes, &g_disable_updates, &g_disable_finds, &g_disable_resizes, &g_disable_iterators, &g_disable_misc, &g_disable_clears, &g_use_strings}; const char *flag_help[] = { "If set, no inserts will be run", "If set, no deletes will be run", "If set, no updates will be run", "If set, no finds will be run", "If set, no resize operations will be run", "If set, no iterator operations will be run", "If set, no misc functions will be run", "If set, no clears will be run", "If set, the key type of the map will be std::string"}; parse_flags(argc, argv, "Runs a stress test on inserts, deletes, and finds", args, arg_vars, arg_help, sizeof(args) / sizeof(const char *), flags, flag_vars, flag_help, sizeof(flags) / sizeof(const char *)); g_numkeys = 1U << g_power; if (g_use_strings) { auto *env = new AllEnvironment; StressTest(env); delete env; } else { auto *env = new AllEnvironment; StressTest(env); delete env; } return main_return_value; } libcuckoo-0.3.1/tests/test_util.hh000066400000000000000000000175071426042121400171720ustar00rootroot00000000000000#ifndef _TEST_UTIL_HH #define _TEST_UTIL_HH // Utilities for running stress tests and benchmarks #include #include #include #include #include #include #include #include #include std::mutex print_lock; int main_return_value = EXIT_SUCCESS; typedef std::lock_guard mutex_guard; // Prints a message if the two items aren't equal template inline void do_expect_equal(T x, const char *xname, U y, const char *yname, size_t line) { if (x != y) { mutex_guard m(print_lock); main_return_value = EXIT_FAILURE; std::cout << "ERROR:\t" << xname << "(" << x << ") does not equal " << yname << "(" << y << ") on line " << line << std::endl; } } #define EXPECT_EQ(x, y) do_expect_equal(x, #x, y, #y, __LINE__) // Prints a message if the two items are equal template inline void do_expect_not_equal(T x, const char *xname, U y, const char *yname, size_t line) { if (x == y) { mutex_guard m(print_lock); main_return_value = EXIT_FAILURE; std::cout << "ERROR:\t" << xname << "(" << x << ") equals " << yname << "(" << y << ") on line " << line << std::endl; } } #define EXPECT_NE(x, y) do_expect_not_equal(x, #x, y, #y, __LINE__) // Prints a message if the item is false inline void do_expect_true(bool x, const char *xname, size_t line) { if (!x) { mutex_guard m(print_lock); main_return_value = EXIT_FAILURE; std::cout << "ERROR:\t" << xname << "(" << x << ") is false on line " << line << std::endl; } } #define EXPECT_TRUE(x) do_expect_true(x, #x, __LINE__) // Prints a message if the item is true inline void do_expect_false(bool x, const char *xname, size_t line) { if (x) { mutex_guard m(print_lock); main_return_value = EXIT_FAILURE; std::cout << "ERROR:\t" << xname << "(" << x << ") is true on line " << line << std::endl; } } #define EXPECT_FALSE(x) do_expect_false(x, #x, __LINE__) // Prints a message and exists if the two items aren't equal template inline void do_assert_equal(T x, const char *xname, U y, const char *yname, size_t line) { if (x != y) { mutex_guard m(print_lock); std::cout << "FATAL ERROR:\t" << xname << "(" << x << ") does not equal " << yname << "(" << y << ") on line " << line << std::endl; exit(EXIT_FAILURE); } } #define ASSERT_EQ(x, y) do_assert_equal(x, #x, y, #y, __LINE__) // Prints a message and exists if the item is false inline void do_assert_true(bool x, const char *xname, size_t line) { if (!x) { mutex_guard m(print_lock); std::cout << "FATAL ERROR:\t" << xname << "(" << x << ") is false on line " << line << std::endl; exit(EXIT_FAILURE); } } #define ASSERT_TRUE(x) do_assert_true(x, #x, __LINE__) // Parses boolean flags and flags with positive integer arguments void parse_flags(int argc, char **argv, const char *description, const char *args[], size_t *arg_vars[], const char *arg_help[], size_t arg_num, const char *flags[], bool *flag_vars[], const char *flag_help[], size_t flag_num) { errno = 0; for (int i = 0; i < argc; i++) { for (size_t j = 0; j < arg_num; j++) { if (strcmp(argv[i], args[j]) == 0) { if (i == argc - 1) { std::cerr << "You must provide a positive integer argument" << " after the " << args[j] << " argument" << std::endl; exit(EXIT_FAILURE); } else { size_t argval = strtoull(argv[i + 1], NULL, 10); if (errno != 0) { std::cerr << "The argument to " << args[j] << " must be a valid size_t" << std::endl; exit(EXIT_FAILURE); } else { *(arg_vars[j]) = argval; } } } } for (size_t j = 0; j < flag_num; j++) { if (strcmp(argv[i], flags[j]) == 0) { *(flag_vars[j]) = true; } } if (strcmp(argv[i], "--help") == 0) { std::cerr << description << std::endl; std::cerr << "Arguments:" << std::endl; for (size_t j = 0; j < arg_num; j++) { std::cerr << args[j] << " (default " << *arg_vars[j] << "):\t" << arg_help[j] << std::endl; } for (size_t j = 0; j < flag_num; j++) { std::cerr << flags[j] << " (default " << (*flag_vars[j] ? "true" : "false") << "):\t" << flag_help[j] << std::endl; } exit(0); } } } // generateKey is a function from a number to another given type, used to // generate keys for insertion. template T generateKey(size_t i) { return (T)i; } // This specialization returns a stringified representation of the given // integer, where the number is copied to the end of a long string of 'a's, in // order to make comparisons and hashing take time. template <> std::string generateKey(size_t n) { const size_t min_length = 100; const std::string num(std::to_string(n)); if (num.size() >= min_length) { return num; } std::string ret(min_length, 'a'); const size_t startret = min_length - num.size(); for (size_t i = 0; i < num.size(); i++) { ret[i + startret] = num[i]; } return ret; } // An overloaded class that does the inserts for different table types. Inserts // with a value of 0. template class insert_thread { public: typedef typename std::vector::iterator it_t; static void func(Table &table, it_t begin, it_t end) { for (; begin != end; begin++) { ASSERT_TRUE(table.insert(*begin, 0)); } } }; // An overloaded class that does the reads for different table types. It // repeatedly searches for the keys in the given range until the time is up. All // the keys we're searching for should either be in the table or not in the // table, so we assert that. template class read_thread { public: typedef typename std::vector::iterator it_t; static void func(Table &table, it_t begin, it_t end, std::atomic &counter, bool in_table, std::atomic &finished) { typename Table::mapped_type v; // We keep track of our own local counter for reads, to avoid // over-burdening the shared atomic counter size_t reads = 0; while (!finished.load(std::memory_order_acquire)) { for (auto it = begin; it != end; it++) { if (finished.load(std::memory_order_acquire)) { counter.fetch_add(reads); return; } ASSERT_EQ(in_table, table.find(*it, v)); reads++; } } } }; // An overloaded class that does a mixture of reads and inserts for different // table types. It repeatedly searches for the keys in the given range until // everything has been inserted. template class read_insert_thread { public: typedef typename std::vector::iterator it_t; static void func(Table &table, it_t begin, it_t end, std::atomic &counter, const double insert_prob, const size_t start_seed) { typename Table::mapped_type v; pcg64_fast gen(start_seed); std::uniform_real_distribution dist(0.0, 1.0); auto inserter_it = begin; auto reader_it = begin; size_t ops = 0; while (inserter_it != end) { if (dist(gen) < insert_prob) { // Do an insert ASSERT_TRUE(table.insert(*inserter_it, 0)); ++inserter_it; } else { // Do a read ASSERT_EQ(table.find(*reader_it, v), (reader_it < inserter_it)); ++reader_it; if (reader_it == end) { reader_it = begin; } } ++ops; } counter.fetch_add(ops); } }; #endif // _TEST_UTIL_HH libcuckoo-0.3.1/tests/unit-tests/000077500000000000000000000000001426042121400167425ustar00rootroot00000000000000libcuckoo-0.3.1/tests/unit-tests/CMakeLists.txt000066400000000000000000000012261426042121400215030ustar00rootroot00000000000000add_library(int_int_table STATIC int_int_table.cc) target_link_libraries(int_int_table libcuckoo) add_executable(unit_tests test_constructor.cc test_hash_properties.cc test_heterogeneous_compare.cc test_iterator.cc test_maximum_hashpower.cc test_minimum_load_factor.cc test_noncopyable_types.cc test_resize.cc test_runner.cc test_user_exceptions.cc test_locked_table.cc test_c_interface.cc test_bucket_container.cc unit_test_util.cc unit_test_util.hh ) target_link_libraries(unit_tests PRIVATE catch PRIVATE libcuckoo PRIVATE int_int_table ) add_test(NAME unit_tests COMMAND unit_tests) libcuckoo-0.3.1/tests/unit-tests/int_int_table.cc000066400000000000000000000001331426042121400220610ustar00rootroot00000000000000extern "C" { #include "int_int_table.h" } #include libcuckoo-0.3.1/tests/unit-tests/int_int_table.h000066400000000000000000000003371426042121400217310ustar00rootroot00000000000000#ifndef INT_INT_TABLE_H #define INT_INT_TABLE_H #define CUCKOO_TABLE_NAME int_int_table #define CUCKOO_KEY_TYPE int #define CUCKOO_MAPPED_TYPE int #include #endif // INT_INT_TABLE_H libcuckoo-0.3.1/tests/unit-tests/test_bucket_container.cc000066400000000000000000000252331426042121400236340ustar00rootroot00000000000000#include #include #include #include #include #include #include template struct allocator_wrapper { template class stateful_allocator { public: using value_type = T; using propagate_on_container_copy_assignment = std::integral_constant; using propagate_on_container_move_assignment = std::integral_constant; using propagate_on_container_swap = std::integral_constant; stateful_allocator() : id(0) {} stateful_allocator(const size_t &id_) : id(id_) {} stateful_allocator(const stateful_allocator &other) : id(other.id) {} template stateful_allocator(const stateful_allocator &other) : id(other.id) {} stateful_allocator &operator=(const stateful_allocator &a) { id = a.id + 1; return *this; } stateful_allocator &operator=(stateful_allocator &&a) { id = a.id + 2; return *this; } T *allocate(size_t n) { return std::allocator().allocate(n); } void deallocate(T *ptr, size_t n) { std::allocator().deallocate(ptr, n); } stateful_allocator select_on_container_copy_construction() const { stateful_allocator copy(*this); ++copy.id; return copy; } bool operator==(const stateful_allocator &other) { return id == other.id; } bool operator!=(const stateful_allocator &other) { return id != other.id; } size_t id; }; }; template using stateful_allocator = typename allocator_wrapper::template stateful_allocator; const size_t SLOT_PER_BUCKET = 4; template using TestingContainer = libcuckoo::bucket_container, int, Alloc, uint8_t, SLOT_PER_BUCKET>; using value_type = std::pair, int>; TEST_CASE("bucket container default constructor", "[bucket container]") { allocator_wrapper<>::stateful_allocator a; TestingContainer tc(2, a); REQUIRE(tc.hashpower() == 2); REQUIRE(tc.size() == 4); REQUIRE(tc.get_allocator().id == 0); for (size_t i = 0; i < tc.size(); ++i) { for (size_t j = 0; j < SLOT_PER_BUCKET; ++j) { REQUIRE_FALSE(tc[i].occupied(j)); } } } TEST_CASE("bucket container simple stateful allocator", "[bucket container]") { allocator_wrapper<>::stateful_allocator a(10); TestingContainer tc(2, a); REQUIRE(tc.hashpower() == 2); REQUIRE(tc.size() == 4); REQUIRE(tc.get_allocator().id == 10); } TEST_CASE("bucket container copy construction", "[bucket container]") { allocator_wrapper<>::stateful_allocator a(5); TestingContainer tc(2, a); tc.setKV(0, 0, 2, std::make_shared(10), 5); TestingContainer tc2(tc); REQUIRE(tc[0].occupied(0)); REQUIRE(tc[0].partial(0) == 2); REQUIRE(*tc[0].key(0) == 10); REQUIRE(tc[0].mapped(0) == 5); REQUIRE(tc.get_allocator().id == 5); REQUIRE(tc2[0].occupied(0)); REQUIRE(tc2[0].partial(0) == 2); REQUIRE(*tc2[0].key(0) == 10); REQUIRE(tc2[0].mapped(0) == 5); REQUIRE(tc2.get_allocator().id == 6); } TEST_CASE("bucket container move construction", "[bucket container]") { allocator_wrapper<>::stateful_allocator a(5); TestingContainer tc(2, a); tc.setKV(0, 0, 2, std::make_shared(10), 5); TestingContainer tc2(std::move(tc)); REQUIRE(tc2[0].occupied(0)); REQUIRE(tc2[0].partial(0) == 2); REQUIRE(*tc2[0].key(0) == 10); REQUIRE(tc2[0].mapped(0) == 5); REQUIRE(tc2.get_allocator().id == 5); } TEST_CASE("bucket container copy assignment with propagate", "[bucket container]") { allocator_wrapper::stateful_allocator a(5); TestingContainer tc(2, a); tc.setKV(0, 0, 2, std::make_shared(10), 5); TestingContainer tc2(2, a); tc2.setKV(1, 0, 2, std::make_shared(10), 5); tc2 = tc; REQUIRE(tc2[0].occupied(0)); REQUIRE(tc2[0].partial(0) == 2); REQUIRE(*tc2[0].key(0) == 10); REQUIRE(tc2[0].key(0).use_count() == 2); REQUIRE(tc2[0].mapped(0) == 5); REQUIRE_FALSE(tc2[1].occupied(0)); REQUIRE(tc.get_allocator().id == 5); REQUIRE(tc2.get_allocator().id == 6); } TEST_CASE("bucket container copy assignment no propagate", "[bucket container]") { allocator_wrapper::stateful_allocator a(5); TestingContainer tc(2, a); tc.setKV(0, 0, 2, std::make_shared(10), 5); TestingContainer tc2(2, a); tc2.setKV(1, 0, 2, std::make_shared(10), 5); tc2 = tc; REQUIRE(tc2[0].occupied(0)); REQUIRE(tc2[0].partial(0) == 2); REQUIRE(*tc2[0].key(0) == 10); REQUIRE(tc2[0].key(0).use_count() == 2); REQUIRE(tc2[0].mapped(0) == 5); REQUIRE_FALSE(tc2[1].occupied(0)); REQUIRE(tc.get_allocator().id == 5); REQUIRE(tc2.get_allocator().id == 5); } TEST_CASE("bucket container move assignment with propagate", "[bucket container]") { allocator_wrapper<>::stateful_allocator a(5); TestingContainer tc(2, a); tc.setKV(0, 0, 2, std::make_shared(10), 5); TestingContainer tc2(2, a); tc2.setKV(1, 0, 2, std::make_shared(10), 5); tc2 = std::move(tc); REQUIRE(tc2[0].occupied(0)); REQUIRE(tc2[0].partial(0) == 2); REQUIRE(*tc2[0].key(0) == 10); REQUIRE(tc2[0].mapped(0) == 5); REQUIRE_FALSE(tc2[1].occupied(0)); REQUIRE(tc2.get_allocator().id == 7); } TEST_CASE("bucket container move assignment no propagate equal", "[bucket container]") { allocator_wrapper::stateful_allocator a(5); TestingContainer tc(2, a); tc.setKV(0, 0, 2, std::make_shared(10), 5); TestingContainer tc2(2, a); tc2.setKV(1, 0, 2, std::make_shared(10), 5); tc2 = std::move(tc); REQUIRE(tc2[0].occupied(0)); REQUIRE(tc2[0].partial(0) == 2); REQUIRE(*tc2[0].key(0) == 10); REQUIRE(tc2[0].key(0).use_count() == 1); REQUIRE(tc2[0].mapped(0) == 5); REQUIRE_FALSE(tc2[1].occupied(0)); REQUIRE(tc2.get_allocator().id == 5); } TEST_CASE("bucket container move assignment no propagate unequal", "[bucket container]") { allocator_wrapper::stateful_allocator a(5); TestingContainer tc(2, a); tc.setKV(0, 0, 2, std::make_shared(10), 5); allocator_wrapper::stateful_allocator a2(4); TestingContainer tc2(2, a2); tc2.setKV(1, 0, 2, std::make_shared(10), 5); tc2 = std::move(tc); REQUIRE(!tc2[1].occupied(0)); REQUIRE(tc2[0].occupied(0)); REQUIRE(tc2[0].partial(0) == 2); REQUIRE(*tc2[0].key(0) == 10); REQUIRE(tc2[0].key(0).use_count() == 1); REQUIRE(tc2[0].mapped(0) == 5); REQUIRE_FALSE(tc2[1].occupied(0)); REQUIRE(tc2.get_allocator().id == 4); REQUIRE(tc[0].occupied(0)); REQUIRE(tc[0].partial(0) == 2); REQUIRE_FALSE(tc[0].key(0)); } TEST_CASE("bucket container swap no propagate", "[bucket container]") { allocator_wrapper::stateful_allocator a(5); TestingContainer tc(2, a); tc.setKV(0, 0, 2, std::make_shared(10), 5); TestingContainer tc2(2, a); tc2.setKV(1, 0, 2, std::make_shared(10), 5); tc.swap(tc2); REQUIRE(tc[1].occupied(0)); REQUIRE(tc[1].partial(0) == 2); REQUIRE(*tc[1].key(0) == 10); REQUIRE(tc[1].key(0).use_count() == 1); REQUIRE(tc[1].mapped(0) == 5); REQUIRE(tc.get_allocator().id == 5); REQUIRE(tc2[0].occupied(0)); REQUIRE(tc2[0].partial(0) == 2); REQUIRE(*tc2[0].key(0) == 10); REQUIRE(tc2[0].key(0).use_count() == 1); REQUIRE(tc2[0].mapped(0) == 5); REQUIRE(tc2.get_allocator().id == 5); } TEST_CASE("bucket container swap propagate", "[bucket container]") { allocator_wrapper::stateful_allocator a(5); TestingContainer tc(2, a); tc.setKV(0, 0, 2, std::make_shared(10), 5); TestingContainer tc2(2, a); tc2.setKV(1, 0, 2, std::make_shared(10), 5); tc.swap(tc2); REQUIRE(tc[1].occupied(0)); REQUIRE(tc[1].partial(0) == 2); REQUIRE(*tc[1].key(0) == 10); REQUIRE(tc[1].key(0).use_count() == 1); REQUIRE(tc[1].mapped(0) == 5); REQUIRE(tc.get_allocator().id == 7); REQUIRE(tc2[0].occupied(0)); REQUIRE(tc2[0].partial(0) == 2); REQUIRE(*tc2[0].key(0) == 10); REQUIRE(tc2[0].key(0).use_count() == 1); REQUIRE(tc2[0].mapped(0) == 5); REQUIRE(tc2.get_allocator().id == 7); } struct ExceptionInt { int x; static bool do_throw; ExceptionInt(int x_) : x(x_) { maybeThrow(); } ExceptionInt(const ExceptionInt &other) : x(other.x) { maybeThrow(); } ExceptionInt &operator=(const ExceptionInt &other) { x = other.x; maybeThrow(); return *this; } ~ExceptionInt() { maybeThrow(); } private: void maybeThrow() { if (do_throw) { throw std::runtime_error("thrown"); } } }; bool ExceptionInt::do_throw = false; using ExceptionContainer = libcuckoo::bucket_container>, uint8_t, SLOT_PER_BUCKET>; TEST_CASE("setKV with throwing type maintains strong guarantee", "[bucket container]") { ExceptionContainer container(0, ExceptionContainer::allocator_type()); container.setKV(0, 0, 0, ExceptionInt(10), 20); ExceptionInt::do_throw = true; REQUIRE_THROWS_AS(container.setKV(0, 1, 0, 0, 0), std::runtime_error); ExceptionInt::do_throw = false; REQUIRE(container[0].occupied(0)); REQUIRE(container[0].key(0).x == 10); REQUIRE(container[0].mapped(0) == 20); REQUIRE_FALSE(container[0].occupied(1)); } TEST_CASE("copy assignment with throwing type is destroyed properly", "[bucket container]") { ExceptionContainer container(0, ExceptionContainer::allocator_type()); container.setKV(0, 0, 0, ExceptionInt(10), 20); ExceptionContainer other(0, ExceptionContainer::allocator_type()); ExceptionInt::do_throw = true; REQUIRE_THROWS_AS(other = container, std::runtime_error); ExceptionInt::do_throw = false; } TEST_CASE("copy destroyed buckets container", "[bucket container]") { std::allocator a; TestingContainer bc(2, a); REQUIRE(!bc.is_deallocated()); bc.clear_and_deallocate(); REQUIRE(bc.is_deallocated()); auto bc2 = bc; REQUIRE(bc.is_deallocated()); REQUIRE(bc2.is_deallocated()); REQUIRE(bc.size() == bc2.size()); REQUIRE(bc.get_allocator() == bc2.get_allocator()); } libcuckoo-0.3.1/tests/unit-tests/test_c_interface.cc000066400000000000000000000235541426042121400225630ustar00rootroot00000000000000#include #include #include extern "C" { #include "int_int_table.h" } int cuckoo_find_fn_value; void cuckoo_find_fn(const int *value) { cuckoo_find_fn_value = *value; } void cuckoo_increment_fn(int *value) { ++(*value); } bool cuckoo_erase_fn(int *value) { return (*value) & 1; } TEST_CASE("c interface", "[c interface]") { int_int_table *tbl = int_int_table_init(0); SECTION("empty table statistics") { REQUIRE(int_int_table_hashpower(tbl) == 0); REQUIRE(int_int_table_bucket_count(tbl) == 1); REQUIRE(int_int_table_empty(tbl)); REQUIRE(int_int_table_capacity(tbl) == 4); REQUIRE(int_int_table_load_factor(tbl) == 0); } for (int i = 0; i < 10; i++) { int_int_table_insert(tbl, &i, &i); } SECTION("find_fn") { for (int i = 0; i < 10; ++i) { REQUIRE(int_int_table_find_fn(tbl, &i, cuckoo_find_fn)); REQUIRE(cuckoo_find_fn_value == i); } for (int i = 10; i < 20; ++i) { REQUIRE_FALSE(int_int_table_find_fn(tbl, &i, cuckoo_find_fn)); } } SECTION("update_fn") { for (int i = 0; i < 10; ++i) { REQUIRE(int_int_table_update_fn(tbl, &i, cuckoo_increment_fn)); } for (int i = 0; i < 10; ++i) { REQUIRE(int_int_table_find_fn(tbl, &i, cuckoo_find_fn)); REQUIRE(cuckoo_find_fn_value == i + 1); } } SECTION("upsert") { for (int i = 0; i < 10; ++i) { REQUIRE_FALSE(int_int_table_upsert(tbl, &i, cuckoo_increment_fn, &i)); REQUIRE(int_int_table_find_fn(tbl, &i, cuckoo_find_fn)); REQUIRE(cuckoo_find_fn_value == i + 1); } for (int i = 10; i < 20; ++i) { REQUIRE(int_int_table_upsert(tbl, &i, cuckoo_increment_fn, &i)); REQUIRE(int_int_table_find_fn(tbl, &i, cuckoo_find_fn)); REQUIRE(cuckoo_find_fn_value == i); } } SECTION("erase_fn") { for (int i = 0; i < 10; ++i) { if (i & 1) { REQUIRE(int_int_table_erase_fn(tbl, &i, cuckoo_erase_fn)); REQUIRE_FALSE(int_int_table_find_fn(tbl, &i, cuckoo_find_fn)); } else { REQUIRE(int_int_table_erase_fn(tbl, &i, cuckoo_erase_fn)); REQUIRE(int_int_table_find_fn(tbl, &i, cuckoo_find_fn)); } } } SECTION("find") { int value; for (int i = 0; i < 10; ++i) { REQUIRE(int_int_table_find(tbl, &i, &value)); REQUIRE(value == i); } for (int i = 10; i < 20; ++i) { REQUIRE_FALSE(int_int_table_find(tbl, &i, &value)); } } SECTION("contains") { for (int i = 0; i < 10; ++i) { REQUIRE(int_int_table_contains(tbl, &i)); } for (int i = 10; i < 20; ++i) { REQUIRE_FALSE(int_int_table_contains(tbl, &i)); } } SECTION("update") { int value; for (int i = 0; i < 10; ++i) { int new_value = i + 1; REQUIRE(int_int_table_update(tbl, &i, &new_value)); REQUIRE(int_int_table_find(tbl, &i, &value)); REQUIRE(value == i + 1); } for (int i = 10; i < 20; ++i) { REQUIRE_FALSE(int_int_table_update(tbl, &i, &value)); } } SECTION("insert_or_assign") { for (int i = 0; i < 10; ++i) { REQUIRE_FALSE(int_int_table_insert_or_assign(tbl, &i, &i)); } for (int i = 10; i < 20; ++i) { REQUIRE(int_int_table_insert_or_assign(tbl, &i, &i)); } for (int i = 0; i < 20; ++i) { int value; REQUIRE(int_int_table_find(tbl, &i, &value)); REQUIRE(value == i); } } SECTION("erase") { for (int i = 1; i < 10; i += 2) { REQUIRE(int_int_table_erase(tbl, &i)); } for (int i = 0; i < 10; ++i) { REQUIRE(int_int_table_contains(tbl, &i) != (i & 1)); } } SECTION("rehash") { REQUIRE(int_int_table_rehash(tbl, 15)); REQUIRE(int_int_table_hashpower(tbl) == 15); } SECTION("reserve") { REQUIRE(int_int_table_reserve(tbl, 30)); REQUIRE(int_int_table_hashpower(tbl) == 3); } SECTION("clear") { int_int_table_clear(tbl); REQUIRE(int_int_table_empty(tbl)); } SECTION("read/write") { FILE *fp = tmpfile(); int_int_table_locked_table *ltbl = int_int_table_lock_table(tbl); REQUIRE(int_int_table_locked_table_write(ltbl, fp)); rewind(fp); int_int_table *tbl2 = int_int_table_read(fp); REQUIRE(int_int_table_size(tbl2) == 10); for (int i = 0; i < 10; ++i) { int value; REQUIRE(int_int_table_find(tbl2, &i, &value)); REQUIRE(i == value); } int_int_table_free(tbl2); int_int_table_locked_table_free(ltbl); fclose(fp); } int_int_table_free(tbl); } TEST_CASE("c interface locked table", "[c interface]") { int_int_table *tbl = int_int_table_init(0); int_int_table_locked_table *ltbl = int_int_table_lock_table(tbl); SECTION("is_active/unlock") { REQUIRE(int_int_table_locked_table_is_active(ltbl)); int_int_table_locked_table_unlock(ltbl); REQUIRE_FALSE(int_int_table_locked_table_is_active(ltbl)); } SECTION("statistics") { REQUIRE(int_int_table_locked_table_hashpower(ltbl) == 0); REQUIRE(int_int_table_locked_table_bucket_count(ltbl) == 1); REQUIRE(int_int_table_locked_table_empty(ltbl)); REQUIRE(int_int_table_locked_table_size(ltbl) == 0); REQUIRE(int_int_table_locked_table_capacity(ltbl) == 4); REQUIRE(int_int_table_locked_table_load_factor(ltbl) == 0); } for (int i = 0; i < 10; ++i) { int_int_table_locked_table_insert(ltbl, &i, &i, NULL); } SECTION("constant iteration") { int occurrences[10] = {}; int_int_table_const_iterator *begin = int_int_table_locked_table_cbegin(ltbl); int_int_table_const_iterator *end = int_int_table_locked_table_cend(ltbl); for (; !int_int_table_const_iterator_equal(begin, end); int_int_table_const_iterator_increment(begin)) { ++occurrences[*int_int_table_const_iterator_key(begin)]; REQUIRE(*int_int_table_const_iterator_key(begin) == *int_int_table_const_iterator_mapped(begin)); } for (int i = 0; i < 10; ++i) { REQUIRE(occurrences[i] == 1); } int_int_table_const_iterator_decrement(end); int_int_table_const_iterator_set(begin, end); REQUIRE(int_int_table_const_iterator_equal(begin, end)); int_int_table_locked_table_set_cbegin(ltbl, begin); for (; !int_int_table_const_iterator_equal(end, begin); int_int_table_const_iterator_decrement(end)) { ++occurrences[*int_int_table_const_iterator_key(end)]; } ++occurrences[*int_int_table_const_iterator_key(end)]; for (int i = 0; i < 10; ++i) { REQUIRE(occurrences[i] == 2); } int_int_table_const_iterator_free(end); int_int_table_const_iterator_free(begin); } SECTION("iteration") { int_int_table_iterator *begin = int_int_table_locked_table_begin(ltbl); int_int_table_iterator *end = int_int_table_locked_table_end(ltbl); for (; !int_int_table_iterator_equal(begin, end); int_int_table_iterator_increment(begin)) { ++(*int_int_table_iterator_mapped(begin)); } int_int_table_iterator_set(begin, end); REQUIRE(int_int_table_iterator_equal(begin, end)); int_int_table_locked_table_set_begin(ltbl, begin); for (; !int_int_table_iterator_equal(begin, end); int_int_table_iterator_increment(begin)) { REQUIRE(*int_int_table_iterator_key(begin) + 1 == *int_int_table_iterator_mapped(begin)); } int_int_table_iterator_free(end); int_int_table_iterator_free(begin); } SECTION("clear") { int_int_table_locked_table_clear(ltbl); REQUIRE(int_int_table_locked_table_size(ltbl) == 0); } SECTION("insert with iterator") { int_int_table_iterator *it = int_int_table_locked_table_begin(ltbl); int item = 11; REQUIRE(int_int_table_locked_table_insert(ltbl, &item, &item, it)); REQUIRE(*int_int_table_iterator_key(it) == 11); REQUIRE(*int_int_table_iterator_mapped(it) == 11); item = 5; REQUIRE_FALSE(int_int_table_locked_table_insert(ltbl, &item, &item, it)); REQUIRE(*int_int_table_iterator_key(it) == 5); ++(*int_int_table_iterator_mapped(it)); REQUIRE(*int_int_table_iterator_mapped(it) == 6); int_int_table_iterator_free(it); } SECTION("erase") { int_int_table_iterator *it1 = int_int_table_locked_table_begin(ltbl); int_int_table_iterator *it2 = int_int_table_locked_table_begin(ltbl); int_int_table_iterator_increment(it2); int_int_table_locked_table_erase_it(ltbl, it1, it1); REQUIRE(int_int_table_iterator_equal(it1, it2)); int_int_table_const_iterator *cbegin = int_int_table_locked_table_cbegin(ltbl); int_int_table_iterator_increment(it2); int_int_table_locked_table_erase_const_it(ltbl, cbegin, it1); REQUIRE(int_int_table_iterator_equal(it1, it2)); int_int_table_const_iterator_free(cbegin); int_int_table_iterator_free(it2); int_int_table_iterator_free(it1); int successes = 0; for (int i = 0; i < 10; ++i) { successes += int_int_table_locked_table_erase(ltbl, &i); } REQUIRE(successes == 8); REQUIRE(int_int_table_locked_table_empty(ltbl)); } SECTION("find") { int_int_table_iterator *it = int_int_table_locked_table_begin(ltbl); int_int_table_const_iterator *cit = int_int_table_locked_table_cbegin(ltbl); int item = 0; int_int_table_locked_table_find(ltbl, &item, it); REQUIRE(*int_int_table_iterator_key(it) == 0); REQUIRE(*int_int_table_iterator_mapped(it) == 0); item = 10; int_int_table_locked_table_find_const(ltbl, &item, cit); int_int_table_const_iterator *cend = int_int_table_locked_table_cend(ltbl); REQUIRE(int_int_table_const_iterator_equal(cit, cend)); int_int_table_const_iterator_free(cend); int_int_table_const_iterator_free(cit); int_int_table_iterator_free(it); } SECTION("rehash") { int_int_table_locked_table_rehash(ltbl, 15); REQUIRE(int_int_table_locked_table_hashpower(ltbl) == 15); } SECTION("reserve") { int_int_table_locked_table_reserve(ltbl, 30); REQUIRE(int_int_table_locked_table_hashpower(ltbl) == 3); } int_int_table_locked_table_free(ltbl); int_int_table_free(tbl); } libcuckoo-0.3.1/tests/unit-tests/test_constructor.cc000066400000000000000000000210761426042121400227030ustar00rootroot00000000000000#include #include #include #include #include "unit_test_util.hh" #include TEST_CASE("default size", "[constructor]") { IntIntTable tbl; REQUIRE(tbl.size() == 0); REQUIRE(tbl.empty()); if (libcuckoo::DEFAULT_SIZE < 4) { REQUIRE(tbl.hashpower() == 0); } else { REQUIRE(tbl.hashpower() == (size_t)log2(libcuckoo::DEFAULT_SIZE / 4)); } REQUIRE(tbl.bucket_count() == 1UL << tbl.hashpower()); REQUIRE(tbl.load_factor() == 0); } TEST_CASE("given size", "[constructor]") { IntIntTable tbl(1); REQUIRE(tbl.size() == 0); REQUIRE(tbl.empty()); REQUIRE(tbl.hashpower() == 0); REQUIRE(tbl.bucket_count() == 1); REQUIRE(tbl.load_factor() == 0); } TEST_CASE("frees even with exceptions", "[constructor]") { typedef IntIntTableWithAlloc> no_space_table; // Should throw when allocating anything REQUIRE_THROWS_AS(no_space_table(1), std::bad_alloc); REQUIRE(get_unfreed_bytes() == 0); typedef IntIntTableWithAlloc< TrackingAllocator> some_space_table; // Should throw when allocating things after the bucket REQUIRE_THROWS_AS(some_space_table(1), std::bad_alloc); REQUIRE(get_unfreed_bytes() == 0); } struct StatefulHash { StatefulHash(int state_) : state(state_) {} size_t operator()(int x) const { return x; } int state; }; struct StatefulKeyEqual { StatefulKeyEqual(int state_) : state(state_) {} bool operator()(int x, int y) const { return x == y; } int state; }; template struct StatefulAllocator { using value_type = T; using pointer = T *; using const_pointer = const T *; using reference = T &; using const_reference = const T &; using size_type = size_t; using difference_type = ptrdiff_t; template struct rebind { using other = StatefulAllocator; }; StatefulAllocator() : state(0) {} StatefulAllocator(int state_) : state(state_) {} template StatefulAllocator(const StatefulAllocator &other) : state(other.state) {} T *allocate(size_t n) { return std::allocator().allocate(n); } void deallocate(T *p, size_t n) { std::allocator().deallocate(p, n); } template void construct(U *p, Args &&... args) { new ((void *)p) U(std::forward(args)...); } template void destroy(U *p) { p->~U(); } StatefulAllocator select_on_container_copy_construction() const { return StatefulAllocator(); } using propagate_on_container_swap = std::integral_constant; int state; }; template bool operator==(const StatefulAllocator &a1, const StatefulAllocator &a2) { return a1.state == a2.state; } template bool operator!=(const StatefulAllocator &a1, const StatefulAllocator &a2) { return a1.state != a2.state; } using alloc_t = StatefulAllocator>; using tbl_t = libcuckoo::cuckoohash_map; TEST_CASE("stateful components", "[constructor]") { tbl_t map(8, StatefulHash(10), StatefulKeyEqual(20), alloc_t(30)); REQUIRE(map.hash_function().state == 10); for (int i = 0; i < 100; ++i) { REQUIRE(map.hash_function()(i) == i); } REQUIRE(map.key_eq().state == 20); for (int i = 0; i < 100; ++i) { REQUIRE(map.key_eq()(i, i)); REQUIRE_FALSE(map.key_eq()(i, i + 1)); } REQUIRE(map.get_allocator().state == 30); } TEST_CASE("range constructor", "[constructor]") { std::array elems{{{1, 2}, {3, 4}, {5, 6}}}; tbl_t map(elems.begin(), elems.end(), 3, StatefulHash(10), StatefulKeyEqual(20), alloc_t(30)); REQUIRE(map.hash_function().state == 10); REQUIRE(map.key_eq().state == 20); REQUIRE(map.get_allocator().state == 30); for (int i = 1; i <= 5; i += 2) { REQUIRE(map.find(i) == i + 1); } } TEST_CASE("copy constructor", "[constructor]") { tbl_t map(0, StatefulHash(10), StatefulKeyEqual(20), alloc_t(30)); REQUIRE(map.get_allocator().state == 30); tbl_t map2(map); REQUIRE(map2.hash_function().state == 10); REQUIRE(map2.key_eq().state == 20); REQUIRE(map2.get_allocator().state == 0); } TEST_CASE("copy constructor other allocator", "[constructor]") { tbl_t map(0, StatefulHash(10), StatefulKeyEqual(20), alloc_t(30)); tbl_t map2(map, map.get_allocator()); REQUIRE(map2.hash_function().state == 10); REQUIRE(map2.key_eq().state == 20); REQUIRE(map2.get_allocator().state == 30); } TEST_CASE("move constructor", "[constructor]") { tbl_t map(10, StatefulHash(10), StatefulKeyEqual(20), alloc_t(30)); map.insert(10, 10); tbl_t map2(std::move(map)); REQUIRE(map.size() == 0); REQUIRE(map2.size() == 1); REQUIRE(map2.hash_function().state == 10); REQUIRE(map2.key_eq().state == 20); REQUIRE(map2.get_allocator().state == 30); } TEST_CASE("move constructor different allocator", "[constructor]") { tbl_t map(10, StatefulHash(10), StatefulKeyEqual(20), alloc_t(30)); map.insert(10, 10); tbl_t map2(std::move(map), alloc_t(40)); REQUIRE(map.size() == 1); REQUIRE(map.hash_function().state == 10); REQUIRE(map.key_eq().state == 20); REQUIRE(map.get_allocator().state == 30); REQUIRE(map2.size() == 1); REQUIRE(map2.hash_function().state == 10); REQUIRE(map2.key_eq().state == 20); REQUIRE(map2.get_allocator().state == 40); } TEST_CASE("initializer list constructor", "[constructor]") { tbl_t map({{1, 2}, {3, 4}, {5, 6}}, 3, StatefulHash(10), StatefulKeyEqual(20), alloc_t(30)); REQUIRE(map.hash_function().state == 10); REQUIRE(map.key_eq().state == 20); REQUIRE(map.get_allocator().state == 30); for (int i = 1; i <= 5; i += 2) { REQUIRE(map.find(i) == i + 1); } } TEST_CASE("swap maps", "[constructor]") { tbl_t map({{1, 2}}, 1, StatefulHash(10), StatefulKeyEqual(20), alloc_t(30)); tbl_t map2({{3, 4}}, 1, StatefulHash(40), StatefulKeyEqual(50), alloc_t(60)); map.swap(map2); REQUIRE(map.size() == 1); REQUIRE(map.hash_function().state == 40); REQUIRE(map.key_eq().state == 50); REQUIRE(map.get_allocator().state == 60); REQUIRE(map2.size() == 1); REQUIRE(map2.hash_function().state == 10); REQUIRE(map2.key_eq().state == 20); REQUIRE(map2.get_allocator().state == 30); // Uses ADL to find the specialized swap. swap(map, map2); REQUIRE(map.size() == 1); REQUIRE(map.hash_function().state == 10); REQUIRE(map.key_eq().state == 20); REQUIRE(map.get_allocator().state == 30); REQUIRE(map2.size() == 1); REQUIRE(map2.hash_function().state == 40); REQUIRE(map2.key_eq().state == 50); REQUIRE(map2.get_allocator().state == 60); } TEST_CASE("copy assign different allocators", "[constructor]") { tbl_t map({{1, 2}}, 1, StatefulHash(10), StatefulKeyEqual(20), alloc_t(30)); tbl_t map2({{3, 4}}, 1, StatefulHash(40), StatefulKeyEqual(50), alloc_t(60)); map = map2; REQUIRE(map.size() == 1); REQUIRE(map.find(3) == 4); REQUIRE(map.hash_function().state == 40); REQUIRE(map.key_eq().state == 50); REQUIRE(map.get_allocator().state == 30); REQUIRE(map2.size() == 1); REQUIRE(map2.hash_function().state == 40); REQUIRE(map2.key_eq().state == 50); REQUIRE(map2.get_allocator().state == 60); } TEST_CASE("move assign different allocators", "[constructor]") { tbl_t map({{1, 2}}, 1, StatefulHash(10), StatefulKeyEqual(20), alloc_t(30)); tbl_t map2({{3, 4}}, 1, StatefulHash(40), StatefulKeyEqual(50), alloc_t(60)); map = std::move(map2); REQUIRE(map.size() == 1); REQUIRE(map.find(3) == 4); REQUIRE(map.hash_function().state == 40); REQUIRE(map.key_eq().state == 50); REQUIRE(map.get_allocator().state == 30); REQUIRE(map2.hash_function().state == 40); REQUIRE(map2.key_eq().state == 50); REQUIRE(map2.get_allocator().state == 60); } TEST_CASE("move assign same allocators", "[constructor]") { tbl_t map({{1, 2}}, 1, StatefulHash(10), StatefulKeyEqual(20), alloc_t(30)); tbl_t map2({{3, 4}}, 1, StatefulHash(40), StatefulKeyEqual(50), alloc_t(30)); map = std::move(map2); REQUIRE(map.size() == 1); REQUIRE(map.find(3) == 4); REQUIRE(map.hash_function().state == 40); REQUIRE(map.key_eq().state == 50); REQUIRE(map.get_allocator().state == 30); REQUIRE(map2.size() == 0); REQUIRE(map2.hash_function().state == 40); REQUIRE(map2.key_eq().state == 50); REQUIRE(map2.get_allocator().state == 30); } TEST_CASE("initializer list assignment", "[constructor]") { tbl_t map({{1, 2}}, 1, StatefulHash(10), StatefulKeyEqual(20), alloc_t(30)); REQUIRE(map.find(1) == 2); map = {{3, 4}}; REQUIRE(map.find(3) == 4); } libcuckoo-0.3.1/tests/unit-tests/test_hash_properties.cc000066400000000000000000000043641426042121400235160ustar00rootroot00000000000000#include #include "unit_test_util.hh" #include using libcuckoo::UnitTestInternalAccess; // Checks that the alt index function returns a different bucket, and can // recover the old bucket when called with the alternate bucket as the index. template void check_key(size_t hashpower, const typename CuckoohashMap::key_type &key) { auto hashfn = typename CuckoohashMap::hasher(); size_t hv = hashfn(key); auto partial = UnitTestInternalAccess::partial_key(hv); size_t bucket = UnitTestInternalAccess::index_hash(hashpower, hv); size_t alt_bucket = UnitTestInternalAccess::alt_index( hashpower, partial, bucket); size_t orig_bucket = UnitTestInternalAccess::alt_index( hashpower, partial, alt_bucket); REQUIRE(bucket != alt_bucket); REQUIRE(bucket == orig_bucket); } TEST_CASE("int alt index works correctly", "[hash properties]") { for (size_t hashpower = 10; hashpower < 15; ++hashpower) { for (int key = 0; key < 10000; ++key) { check_key(hashpower, key); } } } TEST_CASE("string alt index works correctly", "[hash properties]") { for (size_t hashpower = 10; hashpower < 15; ++hashpower) { for (int key = 0; key < 10000; ++key) { check_key(hashpower, std::to_string(key)); } } } TEST_CASE("hash with larger hashpower only adds top bits", "[hash properties]") { std::string key = "abc"; size_t hv = StringIntTable::hasher()(key); for (size_t hashpower = 1; hashpower < 30; ++hashpower) { auto partial = UnitTestInternalAccess::partial_key(hv); size_t index_bucket1 = UnitTestInternalAccess::index_hash(hashpower, hv); size_t index_bucket2 = UnitTestInternalAccess::index_hash(hashpower + 1, hv); CHECK((index_bucket2 & ~(1L << hashpower)) == index_bucket1); size_t alt_bucket1 = UnitTestInternalAccess::alt_index( hashpower, partial, index_bucket1); size_t alt_bucket2 = UnitTestInternalAccess::alt_index( hashpower, partial, index_bucket2); CHECK((alt_bucket2 & ~(1L << hashpower)) == alt_bucket1); } } libcuckoo-0.3.1/tests/unit-tests/test_heterogeneous_compare.cc000066400000000000000000000150731426042121400247000ustar00rootroot00000000000000#include #include size_t int_constructions; size_t copy_constructions; size_t destructions; size_t foo_comparisons; size_t int_comparisons; size_t foo_hashes; size_t int_hashes; class Foo { public: int val; Foo(int v) { ++int_constructions; val = v; } Foo(const Foo &x) { ++copy_constructions; val = x.val; } ~Foo() { ++destructions; } }; class foo_eq { public: bool operator()(const Foo &left, const Foo &right) const { ++foo_comparisons; return left.val == right.val; } bool operator()(const Foo &left, const int right) const { ++int_comparisons; return left.val == right; } }; class foo_hasher { public: size_t operator()(const Foo &x) const { ++foo_hashes; return static_cast(x.val); } size_t operator()(const int x) const { ++int_hashes; return static_cast(x); } }; typedef libcuckoo::cuckoohash_map foo_map; TEST_CASE("heterogeneous compare", "[heterogeneous compare]") { // setup code int_constructions = 0; copy_constructions = 0; destructions = 0; foo_comparisons = 0; int_comparisons = 0; foo_hashes = 0; int_hashes = 0; SECTION("insert") { { foo_map map; map.insert(0, true); } REQUIRE(int_constructions == 1); REQUIRE(copy_constructions == 0); REQUIRE(destructions == 1); REQUIRE(foo_comparisons == 0); REQUIRE(int_comparisons == 0); REQUIRE(foo_hashes == 0); REQUIRE(int_hashes == 1); } SECTION("foo insert") { { foo_map map; map.insert(Foo(0), true); } REQUIRE(int_constructions == 1); REQUIRE(copy_constructions == 1); // One destruction of passed-in and moved argument, and one after the // table is destroyed. REQUIRE(destructions == 2); REQUIRE(foo_comparisons == 0); REQUIRE(int_comparisons == 0); REQUIRE(foo_hashes == 1); REQUIRE(int_hashes == 0); } SECTION("insert_or_assign") { { foo_map map; map.insert_or_assign(0, true); map.insert_or_assign(0, false); REQUIRE_FALSE(map.find(0)); } REQUIRE(int_constructions == 1); REQUIRE(copy_constructions == 0); REQUIRE(destructions == 1); REQUIRE(foo_comparisons == 0); REQUIRE(int_comparisons == 2); REQUIRE(foo_hashes == 0); REQUIRE(int_hashes == 3); } SECTION("foo insert_or_assign") { { foo_map map; map.insert_or_assign(Foo(0), true); map.insert_or_assign(Foo(0), false); REQUIRE_FALSE(map.find(Foo(0))); } REQUIRE(int_constructions == 3); REQUIRE(copy_constructions == 1); // Three destructions of Foo arguments, and one in table destruction REQUIRE(destructions == 4); REQUIRE(foo_comparisons == 2); REQUIRE(int_comparisons == 0); REQUIRE(foo_hashes == 3); REQUIRE(int_hashes == 0); } SECTION("find") { { foo_map map; map.insert(0, true); bool val; map.find(0, val); REQUIRE(val); REQUIRE(map.find(0, val) == true); REQUIRE(map.find(1, val) == false); } REQUIRE(int_constructions == 1); REQUIRE(copy_constructions == 0); REQUIRE(destructions == 1); REQUIRE(foo_comparisons == 0); REQUIRE(int_comparisons == 2); REQUIRE(foo_hashes == 0); REQUIRE(int_hashes == 4); } SECTION("foo find") { { foo_map map; map.insert(0, true); bool val; map.find(Foo(0), val); REQUIRE(val); REQUIRE(map.find(Foo(0), val) == true); REQUIRE(map.find(Foo(1), val) == false); } REQUIRE(int_constructions == 4); REQUIRE(copy_constructions == 0); REQUIRE(destructions == 4); REQUIRE(foo_comparisons == 2); REQUIRE(int_comparisons == 0); REQUIRE(foo_hashes == 3); REQUIRE(int_hashes == 1); } SECTION("contains") { { foo_map map(0); map.rehash(2); map.insert(0, true); REQUIRE(map.contains(0)); // Shouldn't do comparison because of different partial key REQUIRE(!map.contains(4)); } REQUIRE(int_constructions == 1); REQUIRE(copy_constructions == 0); REQUIRE(destructions == 1); REQUIRE(foo_comparisons == 0); REQUIRE(int_comparisons == 1); REQUIRE(foo_hashes == 0); REQUIRE(int_hashes == 3); } SECTION("erase") { { foo_map map; map.insert(0, true); REQUIRE(map.erase(0)); REQUIRE(!map.contains(0)); } REQUIRE(int_constructions == 1); REQUIRE(copy_constructions == 0); REQUIRE(destructions == 1); REQUIRE(foo_comparisons == 0); REQUIRE(int_comparisons == 1); REQUIRE(foo_hashes == 0); REQUIRE(int_hashes == 3); } SECTION("update") { { foo_map map; map.insert(0, true); REQUIRE(map.update(0, false)); REQUIRE(!map.find(0)); } REQUIRE(int_constructions == 1); REQUIRE(copy_constructions == 0); REQUIRE(destructions == 1); REQUIRE(foo_comparisons == 0); REQUIRE(int_comparisons == 2); REQUIRE(foo_hashes == 0); REQUIRE(int_hashes == 3); } SECTION("update_fn") { { foo_map map; map.insert(0, true); REQUIRE(map.update_fn(0, [](bool &val) { val = !val; })); REQUIRE(!map.find(0)); } REQUIRE(int_constructions == 1); REQUIRE(copy_constructions == 0); REQUIRE(destructions == 1); REQUIRE(foo_comparisons == 0); REQUIRE(int_comparisons == 2); REQUIRE(foo_hashes == 0); REQUIRE(int_hashes == 3); } SECTION("upsert") { { foo_map map(0); map.rehash(2); auto neg = [](bool &val) { val = !val; }; map.upsert(0, neg, true); map.upsert(0, neg, true); // Shouldn't do comparison because of different partial key map.upsert(4, neg, false); REQUIRE(!map.find(0)); REQUIRE(!map.find(4)); } REQUIRE(int_constructions == 2); REQUIRE(copy_constructions == 0); REQUIRE(destructions == 2); REQUIRE(foo_comparisons == 0); REQUIRE(int_comparisons == 3); REQUIRE(foo_hashes == 0); REQUIRE(int_hashes == 5); } SECTION("uprase_fn") { { foo_map map(0); map.rehash(2); auto fn = [](bool &val) { val = !val; return val; }; REQUIRE(map.uprase_fn(0, fn, true)); REQUIRE_FALSE(map.uprase_fn(0, fn, true)); REQUIRE(map.contains(0)); REQUIRE_FALSE(map.uprase_fn(0, fn, true)); REQUIRE_FALSE(map.contains(0)); } REQUIRE(int_constructions == 1); REQUIRE(copy_constructions == 0); REQUIRE(destructions == 1); REQUIRE(foo_comparisons == 0); REQUIRE(int_comparisons == 3); REQUIRE(foo_hashes == 0); REQUIRE(int_hashes == 5); } } libcuckoo-0.3.1/tests/unit-tests/test_iterator.cc000066400000000000000000000116321426042121400221440ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "unit_test_util.hh" #include TEST_CASE("iterator types", "[iterator]") { using Ltbl = IntIntTable::locked_table; using It = Ltbl::iterator; using ConstIt = Ltbl::const_iterator; const bool it_difference_type = std::is_same::value; const bool it_value_type = std::is_same::value; const bool it_pointer = std::is_same::value; const bool it_reference = std::is_same::value; const bool it_iterator_category = std::is_same::value; const bool const_it_difference_type = std::is_same::value; const bool const_it_value_type = std::is_same::value; const bool const_it_reference = std::is_same::value; const bool const_it_pointer = std::is_same::value; const bool const_it_iterator_category = std::is_same::value; REQUIRE(it_difference_type); REQUIRE(it_value_type); REQUIRE(it_pointer); REQUIRE(it_reference); REQUIRE(it_iterator_category); REQUIRE(const_it_difference_type); REQUIRE(const_it_value_type); REQUIRE(const_it_pointer); REQUIRE(const_it_reference); REQUIRE(const_it_iterator_category); } TEST_CASE("empty table iteration", "[iterator]") { IntIntTable table; { auto lt = table.lock_table(); REQUIRE(lt.begin() == lt.begin()); REQUIRE(lt.begin() == lt.end()); REQUIRE(lt.cbegin() == lt.begin()); REQUIRE(lt.begin() == lt.end()); REQUIRE(lt.cbegin() == lt.begin()); REQUIRE(lt.cend() == lt.end()); } } TEST_CASE("iterator walkthrough", "[iterator]") { IntIntTable table; for (int i = 0; i < 10; ++i) { table.insert(i, i); } SECTION("forward postfix walkthrough") { auto lt = table.lock_table(); auto it = lt.cbegin(); for (size_t i = 0; i < table.size(); ++i) { REQUIRE((*it).first == (*it).second); REQUIRE(it->first == it->second); auto old_it = it; REQUIRE(old_it == it++); } REQUIRE(it == lt.end()); } SECTION("forward prefix walkthrough") { auto lt = table.lock_table(); auto it = lt.cbegin(); for (size_t i = 0; i < table.size(); ++i) { REQUIRE((*it).first == (*it).second); REQUIRE(it->first == it->second); ++it; } REQUIRE(it == lt.end()); } SECTION("backwards postfix walkthrough") { auto lt = table.lock_table(); auto it = lt.cend(); for (size_t i = 0; i < table.size(); ++i) { auto old_it = it; REQUIRE(old_it == it--); REQUIRE((*it).first == (*it).second); REQUIRE(it->first == it->second); } REQUIRE(it == lt.begin()); } SECTION("backwards prefix walkthrough") { auto lt = table.lock_table(); auto it = lt.cend(); for (size_t i = 0; i < table.size(); ++i) { --it; REQUIRE((*it).first == (*it).second); REQUIRE(it->first == it->second); } REQUIRE(it == lt.begin()); } SECTION("walkthrough works after move") { auto lt = table.lock_table(); auto it = lt.cend(); auto lt2 = std::move(lt); for (size_t i = 0; i < table.size(); ++i) { --it; REQUIRE((*it).first == (*it).second); REQUIRE(it->first == it->second); } REQUIRE(it == lt2.begin()); } } TEST_CASE("iterator modification", "[iterator]") { IntIntTable table; for (int i = 0; i < 10; ++i) { table.insert(i, i); } auto lt = table.lock_table(); for (auto it = lt.begin(); it != lt.end(); ++it) { it->second = it->second + 1; } auto it = lt.cbegin(); for (size_t i = 0; i < table.size(); ++i) { REQUIRE(it->first == it->second - 1); ++it; } REQUIRE(it == lt.end()); } TEST_CASE("lock table blocks inserts", "[iterator]") { IntIntTable table; auto lt = table.lock_table(); std::thread thread([&table]() { for (int i = 0; i < 10; ++i) { table.insert(i, i); } }); std::this_thread::sleep_for(std::chrono::milliseconds(100)); REQUIRE(table.size() == 0); lt.unlock(); thread.join(); REQUIRE(table.size() == 10); } TEST_CASE("Cast iterator to const iterator", "[iterator]") { IntIntTable table; for (int i = 0; i < 10; ++i) { table.insert(i, i); } auto lt = table.lock_table(); for (IntIntTable::locked_table::iterator it = lt.begin(); it != lt.end(); ++it) { REQUIRE(it->first == it->second); it->second++; IntIntTable::locked_table::const_iterator const_it = it; REQUIRE(it->first + 1 == it->second); } } libcuckoo-0.3.1/tests/unit-tests/test_locked_table.cc000066400000000000000000000307201426042121400227220ustar00rootroot00000000000000#include #include #include #include #include #include "unit_test_util.hh" #include TEST_CASE("locked_table typedefs", "[locked_table]") { using Tbl = IntIntTable; using Ltbl = Tbl::locked_table; const bool key_type = std::is_same::value; const bool mapped_type = std::is_same::value; const bool value_type = std::is_same::value; const bool size_type = std::is_same::value; const bool difference_type = std::is_same::value; const bool hasher = std::is_same::value; const bool key_equal = std::is_same::value; const bool allocator_type = std::is_same::value; const bool reference = std::is_same::value; const bool const_reference = std::is_same::value; const bool pointer = std::is_same::value; const bool const_pointer = std::is_same::value; REQUIRE(key_type); REQUIRE(mapped_type); REQUIRE(value_type); REQUIRE(size_type); REQUIRE(difference_type); REQUIRE(hasher); REQUIRE(key_equal); REQUIRE(allocator_type); REQUIRE(reference); REQUIRE(const_reference); REQUIRE(pointer); REQUIRE(const_pointer); } TEST_CASE("locked_table move", "[locked_table]") { IntIntTable tbl; SECTION("move constructor") { auto lt = tbl.lock_table(); auto lt2(std::move(lt)); REQUIRE(!lt.is_active()); REQUIRE(lt2.is_active()); } SECTION("move assignment") { auto lt = tbl.lock_table(); auto lt2 = std::move(lt); REQUIRE(!lt.is_active()); REQUIRE(lt2.is_active()); } SECTION("iterators compare after table is moved") { auto lt1 = tbl.lock_table(); auto it1 = lt1.begin(); auto it2 = lt1.begin(); REQUIRE(it1 == it2); auto lt2(std::move(lt1)); REQUIRE(it1 == it2); } } TEST_CASE("locked_table unlock", "[locked_table]") { IntIntTable tbl; tbl.insert(10, 10); auto lt = tbl.lock_table(); lt.unlock(); REQUIRE(!lt.is_active()); } TEST_CASE("locked_table info", "[locked_table]") { IntIntTable tbl; tbl.insert(10, 10); auto lt = tbl.lock_table(); REQUIRE(lt.is_active()); // We should still be able to call table info operations on the // cuckoohash_map instance, because they shouldn't take locks. REQUIRE(lt.slot_per_bucket() == tbl.slot_per_bucket()); REQUIRE(lt.get_allocator() == tbl.get_allocator()); REQUIRE(lt.hashpower() == tbl.hashpower()); REQUIRE(lt.bucket_count() == tbl.bucket_count()); REQUIRE(lt.empty() == tbl.empty()); REQUIRE(lt.size() == tbl.size()); REQUIRE(lt.capacity() == tbl.capacity()); REQUIRE(lt.load_factor() == tbl.load_factor()); REQUIRE_THROWS_AS(lt.minimum_load_factor(1.01), std::invalid_argument); lt.minimum_load_factor(lt.minimum_load_factor() * 2); lt.rehash(5); REQUIRE_THROWS_AS(lt.maximum_hashpower(lt.hashpower() - 1), std::invalid_argument); lt.maximum_hashpower(lt.hashpower() + 1); REQUIRE(lt.maximum_hashpower() == tbl.maximum_hashpower()); } TEST_CASE("locked_table clear", "[locked_table]") { IntIntTable tbl; tbl.insert(10, 10); auto lt = tbl.lock_table(); REQUIRE(lt.size() == 1); lt.clear(); REQUIRE(lt.size() == 0); lt.clear(); REQUIRE(lt.size() == 0); } TEST_CASE("locked_table insert duplicate", "[locked_table]") { IntIntTable tbl; tbl.insert(10, 10); { auto lt = tbl.lock_table(); auto result = lt.insert(10, 20); REQUIRE(result.first->first == 10); REQUIRE(result.first->second == 10); REQUIRE_FALSE(result.second); result.first->second = 50; } REQUIRE(tbl.find(10) == 50); } TEST_CASE("locked_table insert new key", "[locked_table]") { IntIntTable tbl; tbl.insert(10, 10); { auto lt = tbl.lock_table(); auto result = lt.insert(20, 20); REQUIRE(result.first->first == 20); REQUIRE(result.first->second == 20); REQUIRE(result.second); result.first->second = 50; } REQUIRE(tbl.find(10) == 10); REQUIRE(tbl.find(20) == 50); } TEST_CASE("locked_table insert lifetime", "[locked_table]") { UniquePtrTable tbl; SECTION("Successful insert") { auto lt = tbl.lock_table(); std::unique_ptr key(new int(20)); std::unique_ptr value(new int(20)); auto result = lt.insert(std::move(key), std::move(value)); REQUIRE(*result.first->first == 20); REQUIRE(*result.first->second == 20); REQUIRE(result.second); REQUIRE(!static_cast(key)); REQUIRE(!static_cast(value)); } SECTION("Unsuccessful insert") { tbl.insert(new int(20), new int(20)); auto lt = tbl.lock_table(); std::unique_ptr key(new int(20)); std::unique_ptr value(new int(30)); auto result = lt.insert(std::move(key), std::move(value)); REQUIRE(*result.first->first == 20); REQUIRE(*result.first->second == 20); REQUIRE(!result.second); REQUIRE(static_cast(key)); REQUIRE(static_cast(value)); } } TEST_CASE("locked_table erase", "[locked_table]") { IntIntTable tbl; for (int i = 0; i < 5; ++i) { tbl.insert(i, i); } using lt_t = IntIntTable::locked_table; SECTION("simple erase") { auto lt = tbl.lock_table(); lt_t::const_iterator const_it; const_it = lt.find(0); REQUIRE(const_it != lt.end()); lt_t::const_iterator const_next = const_it; ++const_next; REQUIRE(static_cast(lt.erase(const_it)) == const_next); REQUIRE(lt.size() == 4); lt_t::iterator it; it = lt.find(1); lt_t::iterator next = it; ++next; REQUIRE(lt.erase(static_cast(it)) == next); REQUIRE(lt.size() == 3); REQUIRE(lt.erase(2) == 1); REQUIRE(lt.size() == 2); } SECTION("erase doesn't ruin this iterator") { auto lt = tbl.lock_table(); auto it = lt.begin(); auto next = it; ++next; REQUIRE(lt.erase(it) == next); ++it; REQUIRE(it->first > 0); REQUIRE(it->first < 5); REQUIRE(it->second > 0); REQUIRE(it->second < 5); } SECTION("erase doesn't ruin other iterators") { auto lt = tbl.lock_table(); auto it0 = lt.find(0); auto it1 = lt.find(1); auto it2 = lt.find(2); auto it3 = lt.find(3); auto it4 = lt.find(4); auto next = it2; ++next; REQUIRE(lt.erase(it2) == next); REQUIRE(it0->first == 0); REQUIRE(it0->second == 0); REQUIRE(it1->first == 1); REQUIRE(it1->second == 1); REQUIRE(it3->first == 3); REQUIRE(it3->second == 3); REQUIRE(it4->first == 4); REQUIRE(it4->second == 4); } } TEST_CASE("locked_table find", "[locked_table]") { IntIntTable tbl; using lt_t = IntIntTable::locked_table; auto lt = tbl.lock_table(); for (int i = 0; i < 10; ++i) { REQUIRE(lt.insert(i, i).second); } bool found_begin_elem = false; bool found_last_elem = false; for (int i = 0; i < 10; ++i) { lt_t::iterator it = lt.find(i); lt_t::const_iterator const_it = lt.find(i); REQUIRE(it != lt.end()); REQUIRE(it->first == i); REQUIRE(it->second == i); REQUIRE(const_it != lt.end()); REQUIRE(const_it->first == i); REQUIRE(const_it->second == i); it->second++; if (it == lt.begin()) { found_begin_elem = true; } if (++it == lt.end()) { found_last_elem = true; } } REQUIRE(found_begin_elem); REQUIRE(found_last_elem); for (int i = 0; i < 10; ++i) { lt_t::iterator it = lt.find(i); REQUIRE(it->first == i); REQUIRE(it->second == i + 1); } } TEST_CASE("locked_table at", "[locked_table]") { IntIntTable tbl; auto lt = tbl.lock_table(); for (int i = 0; i < 10; ++i) { REQUIRE(lt.insert(i, i).second); } for (int i = 0; i < 10; ++i) { int &val = lt.at(i); const int &const_val = const_cast(lt).at(i); REQUIRE(val == i); REQUIRE(const_val == i); ++val; } for (int i = 0; i < 10; ++i) { REQUIRE(lt.at(i) == i + 1); } REQUIRE_THROWS_AS(lt.at(11), std::out_of_range); } TEST_CASE("locked_table operator[]", "[locked_table]") { IntIntTable tbl; auto lt = tbl.lock_table(); for (int i = 0; i < 10; ++i) { REQUIRE(lt.insert(i, i).second); } for (int i = 0; i < 10; ++i) { int &val = lt[i]; REQUIRE(val == i); ++val; } for (int i = 0; i < 10; ++i) { REQUIRE(lt[i] == i + 1); } REQUIRE(lt[11] == 0); REQUIRE(lt.at(11) == 0); } TEST_CASE("locked_table count", "[locked_table]") { IntIntTable tbl; auto lt = tbl.lock_table(); for (int i = 0; i < 10; ++i) { REQUIRE(lt.insert(i, i).second); } for (int i = 0; i < 10; ++i) { REQUIRE(lt.count(i) == 1); } REQUIRE(lt.count(11) == 0); } TEST_CASE("locked_table equal_range", "[locked_table]") { IntIntTable tbl; using lt_t = IntIntTable::locked_table; auto lt = tbl.lock_table(); for (int i = 0; i < 10; ++i) { REQUIRE(lt.insert(i, i).second); } for (int i = 0; i < 10; ++i) { std::pair it_range = lt.equal_range(i); REQUIRE(it_range.first->first == i); REQUIRE(++it_range.first == it_range.second); std::pair const_it_range = lt.equal_range(i); REQUIRE(const_it_range.first->first == i); REQUIRE(++const_it_range.first == const_it_range.second); } auto it_range = lt.equal_range(11); REQUIRE(it_range.first == lt.end()); REQUIRE(it_range.second == lt.end()); } TEST_CASE("locked_table rehash", "[locked_table]") { IntIntTable tbl(10); auto lt = tbl.lock_table(); REQUIRE(lt.hashpower() == 2); lt.rehash(1); REQUIRE(lt.hashpower() == 1); lt.rehash(10); REQUIRE(lt.hashpower() == 10); } TEST_CASE("locked_table reserve", "[locked_table]") { IntIntTable tbl(10); auto lt = tbl.lock_table(); REQUIRE(lt.hashpower() == 2); lt.reserve(1); REQUIRE(lt.hashpower() == 0); lt.reserve(4096); REQUIRE(lt.hashpower() == 10); } TEST_CASE("locked_table equality", "[locked_table]") { IntIntTable tbl1(40); auto lt1 = tbl1.lock_table(); for (int i = 0; i < 10; ++i) { lt1.insert(i, i); } IntIntTable tbl2(30); auto lt2 = tbl2.lock_table(); for (int i = 0; i < 10; ++i) { lt2.insert(i, i); } IntIntTable tbl3(30); auto lt3 = tbl3.lock_table(); for (int i = 0; i < 10; ++i) { lt3.insert(i, i + 1); } IntIntTable tbl4(40); auto lt4 = tbl4.lock_table(); for (int i = 0; i < 10; ++i) { lt4.insert(i + 1, i); } REQUIRE(lt1 == lt2); REQUIRE_FALSE(lt2 != lt1); REQUIRE(lt1 != lt3); REQUIRE_FALSE(lt3 == lt1); REQUIRE_FALSE(lt2 == lt3); REQUIRE(lt3 != lt2); REQUIRE(lt1 != lt4); REQUIRE(lt4 != lt1); REQUIRE_FALSE(lt3 == lt4); REQUIRE_FALSE(lt4 == lt3); } template void check_all_locks_taken(Table &tbl) { auto &locks = libcuckoo::UnitTestInternalAccess::get_current_locks(tbl); for (auto &lock : locks) { REQUIRE_FALSE(lock.try_lock()); } } TEST_CASE("locked table holds locks after resize", "[locked table]") { IntIntTable tbl(4); auto lt = tbl.lock_table(); check_all_locks_taken(tbl); // After a cuckoo_fast_double, all locks are still taken for (int i = 0; i < 5; ++i) { lt.insert(i, i); } check_all_locks_taken(tbl); // After a cuckoo_simple_expand, all locks are still taken lt.rehash(10); check_all_locks_taken(tbl); } TEST_CASE("locked table IO", "[locked_table]") { IntIntTable tbl(0); auto lt = tbl.lock_table(); for (int i = 0; i < 100; ++i) { lt.insert(i, i); } std::stringstream sstream; sstream << lt; IntIntTable tbl2; auto lt2 = tbl2.lock_table(); sstream.seekg(0); sstream >> lt2; REQUIRE(100 == lt.size()); for (int i = 0; i < 100; ++i) { REQUIRE(i == lt.at(i)); } REQUIRE(100 == lt2.size()); for (int i = 100; i < 1000; ++i) { lt2.insert(i, i); } for (int i = 0; i < 1000; ++i) { REQUIRE(i == lt2.at(i)); } } TEST_CASE("empty locked table IO", "[locked table]") { IntIntTable tbl(0); auto lt = tbl.lock_table(); lt.minimum_load_factor(0.5); lt.maximum_hashpower(10); std::stringstream sstream; sstream << lt; IntIntTable tbl2(0); auto lt2 = tbl2.lock_table(); sstream.seekg(0); sstream >> lt2; REQUIRE(0 == lt.size()); REQUIRE(0 == lt2.size()); REQUIRE(0.5 == lt.minimum_load_factor()); REQUIRE(10 == lt.maximum_hashpower()); } libcuckoo-0.3.1/tests/unit-tests/test_maximum_hashpower.cc000066400000000000000000000027641426042121400240560ustar00rootroot00000000000000#include #include #include "unit_test_util.hh" #include #include TEST_CASE("maximum hashpower initialized to default", "[maximum hash power]") { IntIntTable tbl; REQUIRE(tbl.maximum_hashpower() == libcuckoo::NO_MAXIMUM_HASHPOWER); } TEST_CASE("caps any expansion", "[maximum hash power]") { IntIntTable tbl(1); tbl.maximum_hashpower(1); for (size_t i = 0; i < 2 * tbl.slot_per_bucket(); ++i) { tbl.insert(i, i); } REQUIRE(tbl.hashpower() == 1); REQUIRE_THROWS_AS(tbl.insert(2 * tbl.slot_per_bucket(), 0), libcuckoo::maximum_hashpower_exceeded); REQUIRE_THROWS_AS(tbl.rehash(2), libcuckoo::maximum_hashpower_exceeded); REQUIRE_THROWS_AS(tbl.reserve(4 * tbl.slot_per_bucket()), libcuckoo::maximum_hashpower_exceeded); } TEST_CASE("no maximum hash power", "[maximum hash power]") { // It's difficult to check that we actually don't ever set a maximum hash // power, but if we explicitly unset it, we should be able to expand beyond // the limit that we had previously set. IntIntTable tbl(1); tbl.maximum_hashpower(1); REQUIRE_THROWS_AS(tbl.rehash(2), libcuckoo::maximum_hashpower_exceeded); tbl.maximum_hashpower(2); tbl.rehash(2); REQUIRE(tbl.hashpower() == 2); REQUIRE_THROWS_AS(tbl.rehash(3), libcuckoo::maximum_hashpower_exceeded); tbl.maximum_hashpower(libcuckoo::NO_MAXIMUM_HASHPOWER); tbl.rehash(10); REQUIRE(tbl.hashpower() == 10); } libcuckoo-0.3.1/tests/unit-tests/test_minimum_load_factor.cc000066400000000000000000000022041426042121400243160ustar00rootroot00000000000000#include #include #include "unit_test_util.hh" #include #include TEST_CASE("minimum load factor initialized to default", "[minimum load factor]") { IntIntTable tbl; REQUIRE(tbl.minimum_load_factor() == libcuckoo::DEFAULT_MINIMUM_LOAD_FACTOR); } class BadHashFunction { public: size_t operator()(int) { return 0; } }; TEST_CASE("caps automatic expansion", "[minimum load fator]") { const size_t slot_per_bucket = 4; libcuckoo::cuckoohash_map, std::allocator>, slot_per_bucket> tbl(16); tbl.minimum_load_factor(0.6); for (size_t i = 0; i < 2 * slot_per_bucket; ++i) { tbl.insert(i, i); } REQUIRE_THROWS_AS(tbl.insert(2 * slot_per_bucket, 0), libcuckoo::load_factor_too_low); } TEST_CASE("invalid minimum load factor", "[minimum load factor]") { IntIntTable tbl; REQUIRE_THROWS_AS(tbl.minimum_load_factor(-0.01), std::invalid_argument); REQUIRE_THROWS_AS(tbl.minimum_load_factor(1.01), std::invalid_argument); } libcuckoo-0.3.1/tests/unit-tests/test_noncopyable_types.cc000066400000000000000000000100241426042121400240420ustar00rootroot00000000000000#include #include #include #include "unit_test_util.hh" #include using Tbl = UniquePtrTable; using Uptr = std::unique_ptr; const size_t TBL_INIT = 1; const size_t TBL_SIZE = TBL_INIT * Tbl::slot_per_bucket() * 2; void check_key_eq(Tbl &tbl, int key, int expected_val) { REQUIRE(tbl.contains(Uptr(new int(key)))); tbl.find_fn(Uptr(new int(key)), [expected_val](const Uptr &ptr) { REQUIRE(*ptr == expected_val); }); } TEST_CASE("noncopyable insert and update", "[noncopyable]") { Tbl tbl(TBL_INIT); for (size_t i = 0; i < TBL_SIZE; ++i) { REQUIRE(tbl.insert(Uptr(new int(i)), Uptr(new int(i)))); } for (size_t i = 0; i < TBL_SIZE; ++i) { check_key_eq(tbl, i, i); } for (size_t i = 0; i < TBL_SIZE; ++i) { tbl.update(Uptr(new int(i)), Uptr(new int(i + 1))); } for (size_t i = 0; i < TBL_SIZE; ++i) { check_key_eq(tbl, i, i + 1); } } TEST_CASE("noncopyable upsert", "[noncopyable]") { Tbl tbl(TBL_INIT); auto increment = [](Uptr &ptr) { *ptr += 1; }; for (size_t i = 0; i < TBL_SIZE; ++i) { tbl.upsert(Uptr(new int(i)), increment, Uptr(new int(i))); } for (size_t i = 0; i < TBL_SIZE; ++i) { check_key_eq(tbl, i, i); } for (size_t i = 0; i < TBL_SIZE; ++i) { tbl.upsert(Uptr(new int(i)), increment, Uptr(new int(i))); } for (size_t i = 0; i < TBL_SIZE; ++i) { check_key_eq(tbl, i, i + 1); } } TEST_CASE("noncopyable iteration", "[noncopyable]") { Tbl tbl(TBL_INIT); for (size_t i = 0; i < TBL_SIZE; ++i) { tbl.insert(Uptr(new int(i)), Uptr(new int(i))); } { auto locked_tbl = tbl.lock_table(); for (auto &kv : locked_tbl) { REQUIRE(*kv.first == *kv.second); *kv.second += 1; } } { auto locked_tbl = tbl.lock_table(); for (auto &kv : locked_tbl) { REQUIRE(*kv.first == *kv.second - 1); } } } TEST_CASE("nested table", "[noncopyable]") { typedef libcuckoo::cuckoohash_map inner_tbl; typedef libcuckoo::cuckoohash_map> nested_tbl; nested_tbl tbl; std::string keys[] = {"abc", "def"}; for (std::string &k : keys) { tbl.insert(std::string(k), nested_tbl::mapped_type(new inner_tbl)); tbl.update_fn(k, [&k](nested_tbl::mapped_type &t) { for (char c : k) { t->insert(c, std::string(k)); } }); } for (std::string &k : keys) { REQUIRE(tbl.contains(k)); tbl.update_fn(k, [&k](nested_tbl::mapped_type &t) { for (char c : k) { REQUIRE(t->find(c) == k); } }); } } TEST_CASE("noncopyable insert lifetime") { Tbl tbl; // Successful insert SECTION("Successful insert") { Uptr key(new int(20)); Uptr value(new int(20)); REQUIRE(tbl.insert(std::move(key), std::move(value))); REQUIRE(!static_cast(key)); REQUIRE(!static_cast(value)); } // Unsuccessful insert SECTION("Unsuccessful insert") { tbl.insert(new int(20), new int(20)); Uptr key(new int(20)); Uptr value(new int(30)); REQUIRE_FALSE(tbl.insert(std::move(key), std::move(value))); REQUIRE(static_cast(key)); REQUIRE(static_cast(value)); } } TEST_CASE("noncopyable erase_fn") { Tbl tbl; tbl.insert(new int(10), new int(10)); auto decrement_and_erase = [](Uptr &p) { --(*p); return *p == 0; }; Uptr k(new int(10)); for (int i = 0; i < 9; ++i) { tbl.erase_fn(k, decrement_and_erase); REQUIRE(tbl.contains(k)); } tbl.erase_fn(k, decrement_and_erase); REQUIRE_FALSE(tbl.contains(k)); } TEST_CASE("noncopyable uprase_fn") { Tbl tbl; auto decrement_and_erase = [](Uptr &p) { --(*p); return *p == 0; }; REQUIRE( tbl.uprase_fn(Uptr(new int(10)), decrement_and_erase, Uptr(new int(10)))); Uptr k(new int(10)), v(new int(10)); for (int i = 0; i < 10; ++i) { REQUIRE_FALSE( tbl.uprase_fn(std::move(k), decrement_and_erase, std::move(v))); REQUIRE((k && v)); if (i < 9) { REQUIRE(tbl.contains(k)); } else { REQUIRE_FALSE(tbl.contains(k)); } } } libcuckoo-0.3.1/tests/unit-tests/test_resize.cc000066400000000000000000000116041426042121400216130ustar00rootroot00000000000000#include #include #include #include "unit_test_util.hh" #include using libcuckoo::UnitTestInternalAccess; TEST_CASE("rehash empty table", "[resize]") { IntIntTable table(1); REQUIRE(table.hashpower() == 0); table.rehash(20); REQUIRE(table.hashpower() == 20); table.rehash(1); REQUIRE(table.hashpower() == 1); } TEST_CASE("reserve empty table", "[resize]") { IntIntTable table(1); table.reserve(100); REQUIRE(table.hashpower() == 5); table.reserve(1); REQUIRE(table.hashpower() == 0); table.reserve(2); REQUIRE(table.hashpower() == 0); } TEST_CASE("reserve calc", "[resize]") { const size_t slot_per_bucket = IntIntTable::slot_per_bucket(); REQUIRE(UnitTestInternalAccess::reserve_calc(0) == 0); REQUIRE(UnitTestInternalAccess::reserve_calc( 1 * slot_per_bucket) == 0); REQUIRE(UnitTestInternalAccess::reserve_calc( 2 * slot_per_bucket) == 1); REQUIRE(UnitTestInternalAccess::reserve_calc( 3 * slot_per_bucket) == 2); REQUIRE(UnitTestInternalAccess::reserve_calc( 4 * slot_per_bucket) == 2); REQUIRE(UnitTestInternalAccess::reserve_calc( 2500000 * slot_per_bucket) == 22); // The maximum number of elements we can ask to reserve without incurring // rounding error when computing a number of buckets is // SIZE_T_MAX-slot_per_bucket(), which will come out to int_div(SIZE_T_MAX - // 1, slot_per_bucket()) buckets. const size_t max_buckets = ( std::numeric_limits::max() - 1)/slot_per_bucket; // Since the table is always sized in powers of two, our maximum hashpower // comes out to max_hashpower = floor(log2(max_buckets)). We compute this in // a numerically-stable fashion. size_t max_hashpower = 0; for (; (static_cast(1) << (max_hashpower + 1)) <= max_buckets; ++max_hashpower); // Test the boundary between max_hashpower-1 and max_hashpower. const size_t max_elems_before_max_hashpower = ( static_cast(1) << (max_hashpower - 1)) * slot_per_bucket; REQUIRE(UnitTestInternalAccess::reserve_calc( max_elems_before_max_hashpower) == (max_hashpower - 1)); REQUIRE(UnitTestInternalAccess::reserve_calc( max_elems_before_max_hashpower + 1) == max_hashpower); // Test the maximum number of elements. const size_t max_elems = (static_cast(1) << max_hashpower) * slot_per_bucket; REQUIRE(UnitTestInternalAccess::reserve_calc(max_elems) == max_hashpower); } struct my_type { int x; my_type(int v) : x(v) {} my_type(const my_type& other) { x = other.x; } my_type(my_type&& other) { x = other.x; ++num_moves; } ~my_type() { ++num_deletes; } static size_t num_deletes; static size_t num_moves; }; size_t my_type::num_deletes = 0; size_t my_type::num_moves = 0; TEST_CASE("Resizing number of frees", "[resize]") { my_type val(0); size_t num_deletes_after_resize; { // Should allocate 2 buckets of 4 slots libcuckoo::cuckoohash_map, std::equal_to, std::allocator>, 4> map(8); for (int i = 0; i < 9; ++i) { map.insert(i, val); } // All of the items should be moved during resize to the new region of // memory. They should be deleted from the old container. REQUIRE(my_type::num_deletes == 8); REQUIRE(my_type::num_moves == 8); } REQUIRE(my_type::num_deletes == 17); } // Taken from https://github.com/facebook/folly/blob/master/folly/docs/Traits.md class NonRelocatableType { public: std::array buffer; char *pointerToBuffer; NonRelocatableType() : pointerToBuffer(buffer.data()) {} NonRelocatableType(char c) : pointerToBuffer(buffer.data()) { buffer.fill(c); } NonRelocatableType(const NonRelocatableType &x) noexcept : buffer(x.buffer), pointerToBuffer(buffer.data()) {} NonRelocatableType &operator=(const NonRelocatableType &x) { buffer = x.buffer; return *this; } }; TEST_CASE("Resize on non-relocatable type", "[resize]") { libcuckoo::cuckoohash_map, std::equal_to, std::allocator>, 1> map(0); REQUIRE(map.hashpower() == 0); // Make it resize a few times to ensure the vector capacity has to actually // change when we resize the buckets const size_t num_elems = 16; for (int i = 0; i < num_elems; ++i) { map.insert(i, 'a'); } // Make sure each pointer actually points to its buffer NonRelocatableType value; std::array ref; ref.fill('a'); auto lt = map.lock_table(); for (const auto &kvpair : lt) { REQUIRE(ref == kvpair.second.buffer); REQUIRE(kvpair.second.pointerToBuffer == kvpair.second.buffer.data()); } } libcuckoo-0.3.1/tests/unit-tests/test_runner.cc000066400000000000000000000001501426042121400216150ustar00rootroot00000000000000// This file will be the entry point for the test runner #define CATCH_CONFIG_MAIN #include libcuckoo-0.3.1/tests/unit-tests/test_user_exceptions.cc000066400000000000000000000213721426042121400235340ustar00rootroot00000000000000#include #include #include "unit_test_util.hh" #include using libcuckoo::UnitTestInternalAccess; void maybeThrow(bool throwException) { if (throwException) { throw std::runtime_error("exception"); } } bool constructorThrow, moveThrow, hashThrow, equalityThrow; class ExceptionInt { public: ExceptionInt() { maybeThrow(constructorThrow); val = 0; } ExceptionInt(size_t x) { maybeThrow(constructorThrow); val = x; } ExceptionInt(const ExceptionInt &i) { maybeThrow(constructorThrow); val = static_cast(i); } ExceptionInt(ExceptionInt &&i) { maybeThrow(constructorThrow || moveThrow); val = static_cast(i); } ExceptionInt &operator=(const ExceptionInt &i) { maybeThrow(constructorThrow); val = static_cast(i); return *this; } ExceptionInt &operator=(ExceptionInt &&i) { maybeThrow(constructorThrow || moveThrow); val = static_cast(i); return *this; } operator size_t() const { return val; } private: size_t val; }; namespace std { template <> struct hash { size_t operator()(const ExceptionInt &x) const { maybeThrow(hashThrow); return x; } }; template <> struct equal_to { bool operator()(const ExceptionInt &lhs, const ExceptionInt &rhs) const { maybeThrow(equalityThrow); return static_cast(lhs) == static_cast(rhs); } }; } typedef libcuckoo::cuckoohash_map, std::equal_to> exceptionTable; void checkIterTable(exceptionTable &tbl, size_t expectedSize) { auto lockedTable = tbl.lock_table(); size_t actualSize = 0; for (auto it = lockedTable.begin(); it != lockedTable.end(); ++it) { ++actualSize; } REQUIRE(actualSize == expectedSize); } TEST_CASE("user exceptions", "[user_exceptions]") { constructorThrow = hashThrow = equalityThrow = moveThrow = false; // We don't use sub-sections because CATCH is not exactly thread-safe // "find/contains" { exceptionTable tbl; tbl.insert(1, 1); tbl.insert(2, 2); tbl.insert(3, 3); hashThrow = true; REQUIRE_THROWS_AS(tbl.find(3), std::runtime_error); REQUIRE_THROWS_AS(tbl.contains(3), std::runtime_error); hashThrow = false; equalityThrow = true; REQUIRE_THROWS_AS(tbl.find(3), std::runtime_error); REQUIRE_THROWS_AS(tbl.contains(3), std::runtime_error); equalityThrow = false; REQUIRE(tbl.find(3) == 3); REQUIRE(tbl.contains(3)); checkIterTable(tbl, 3); } // "insert" { exceptionTable tbl; constructorThrow = true; REQUIRE_THROWS_AS(tbl.insert(100, 100), std::runtime_error); constructorThrow = false; REQUIRE(tbl.insert(100, 100)); checkIterTable(tbl, 1); } // "erase" { exceptionTable tbl; for (int i = 0; i < 10; ++i) { tbl.insert(i, i); } hashThrow = true; REQUIRE_THROWS_AS(tbl.erase(5), std::runtime_error); hashThrow = false; equalityThrow = true; REQUIRE_THROWS_AS(tbl.erase(5), std::runtime_error); equalityThrow = false; REQUIRE(tbl.erase(5)); checkIterTable(tbl, 9); } // "update" { exceptionTable tbl; tbl.insert(9, 9); tbl.insert(10, 10); hashThrow = true; REQUIRE_THROWS_AS(tbl.update(9, 10), std::runtime_error); hashThrow = false; equalityThrow = true; REQUIRE_THROWS_AS(tbl.update(9, 10), std::runtime_error); equalityThrow = false; REQUIRE(tbl.update(9, 10)); checkIterTable(tbl, 2); } // "update_fn" { exceptionTable tbl; tbl.insert(9, 9); tbl.insert(10, 10); auto updater = [](size_t &val) { val++; }; hashThrow = true; REQUIRE_THROWS_AS(tbl.update_fn(9, updater), std::runtime_error); hashThrow = false; equalityThrow = true; REQUIRE_THROWS_AS(tbl.update_fn(9, updater), std::runtime_error); equalityThrow = false; REQUIRE(tbl.update_fn(9, updater)); checkIterTable(tbl, 2); } // "upsert" { exceptionTable tbl; tbl.insert(9, 9); auto updater = [](size_t &val) { val++; }; hashThrow = true; REQUIRE_THROWS_AS(tbl.upsert(9, updater, 10), std::runtime_error); hashThrow = false; equalityThrow = true; REQUIRE_THROWS_AS(tbl.upsert(9, updater, 10), std::runtime_error); equalityThrow = false; tbl.upsert(9, updater, 10); constructorThrow = true; REQUIRE_THROWS_AS(tbl.upsert(10, updater, 10), std::runtime_error); constructorThrow = false; tbl.upsert(10, updater, 10); checkIterTable(tbl, 2); } // rehash { exceptionTable tbl; for (int i = 0; i < 10; ++i) { tbl.insert(i, i); } size_t original_hashpower = tbl.hashpower(); size_t next_hashpower = original_hashpower + 1; constructorThrow = true; REQUIRE_THROWS_AS(tbl.rehash(next_hashpower), std::runtime_error); constructorThrow = false; REQUIRE(tbl.hashpower() == original_hashpower); hashThrow = true; REQUIRE_THROWS_AS(tbl.rehash(next_hashpower), std::runtime_error); hashThrow = false; REQUIRE(tbl.hashpower() == original_hashpower); // This shouldn't throw, because the partial keys are different between // the different hash values, which means they shouldn't be compared for // actual equality. equalityThrow = true; REQUIRE(tbl.rehash(next_hashpower)); REQUIRE(tbl.hashpower() == next_hashpower); equalityThrow = false; checkIterTable(tbl, 10); } // "reserve" { exceptionTable tbl; for (int i = 0; i < 10; ++i) { tbl.insert(i, i); } size_t original_hashpower = tbl.hashpower(); size_t next_hashpower = original_hashpower + 1; size_t next_reserve = (1UL << next_hashpower) * tbl.slot_per_bucket(); constructorThrow = true; REQUIRE_THROWS_AS(tbl.reserve(next_reserve), std::runtime_error); constructorThrow = false; REQUIRE(tbl.hashpower() == original_hashpower); hashThrow = true; REQUIRE_THROWS_AS(tbl.reserve(next_reserve), std::runtime_error); hashThrow = false; REQUIRE(tbl.hashpower() == original_hashpower); // This shouldn't throw, because the partial keys are different between // the different hash values, which means they shouldn't be compared for // actual equality. equalityThrow = true; REQUIRE(tbl.reserve(next_reserve)); REQUIRE(tbl.hashpower() == next_hashpower); checkIterTable(tbl, 10); } // "insert resize" { exceptionTable tbl(1000); REQUIRE(tbl.rehash(1)); // Fill up the entire table for (size_t i = 0; i < exceptionTable::slot_per_bucket() * 2; ++i) { tbl.insert(i * 2, 0); } // Only throw on move, which should be triggered when we do a resize. moveThrow = true; REQUIRE_THROWS_AS( tbl.insert((exceptionTable::slot_per_bucket() * 2) * 2, 0), std::runtime_error); moveThrow = false; REQUIRE(tbl.insert((exceptionTable::slot_per_bucket() * 2) * 2, 0)); checkIterTable(tbl, exceptionTable::slot_per_bucket() * 2 + 1); } // "insert cuckoohash" -- broken? // { // exceptionTable tbl(0); // REQUIRE(tbl.rehash(2)); // size_t cuckooKey = 0; // size_t cuckooKeyHash = std::hash()(cuckooKey); // size_t cuckooKeyIndex = UnitTestInternalAccess::index_hash< // exceptionTable>(tbl.hashpower(), cuckooKeyHash); // size_t cuckooKeyPartial = UnitTestInternalAccess::partial_key< // exceptionTable>(tbl.hashpower(), cuckooKeyHash); // size_t cuckooKeyAltIndex = UnitTestInternalAccess::alt_index< // exceptionTable>(tbl.hashpower(), cuckooKeyPartial, cuckooKeyIndex); // if (cuckooKeyIndex == cuckooKeyAltIndex) { // // Fill up one bucket with elements that have the same value as // // cuckooKeyIndex mod tbl.hashpower() // for (size_t i = 0; i < exceptionTable::slot_per_bucket; ++i) { // tbl.insert(tbl.hashpower() * (i + 1) + cuckooKeyIndex, 0); // } // } else { // // Fill up one bucket on index cuckooKeyIndex, and another bucket // on // // cuckooKeyAlt // for (size_t i = 0; i < exceptionTable::slot_per_bucket; ++i) { // tbl.insert(tbl.hashpower() * (i + 1) + cuckooKeyIndex, 0); // tbl.insert(tbl.hashpower() * (i + 1) + cuckooKeyAltIndex, 0); // } // } // // Now inserting cuckooKey should trigger a cuckoo hash, which moves // // elements around // moveThrow = true; // REQUIRE_THROWS_AS(tbl.insert(cuckooKey, 0), std::runtime_error); // moveThrow = false; // REQUIRE(tbl.insert(cuckooKey, 0)); // checkIterTable(tbl, // exceptionTable::slot_per_bucket * ( // cuckooKeyIndex == cuckooKeyAltIndex ? 1 : 2) + 1); // } } libcuckoo-0.3.1/tests/unit-tests/unit_test_util.cc000066400000000000000000000002251426042121400223230ustar00rootroot00000000000000#include "unit_test_util.hh" std::atomic &get_unfreed_bytes() { static std::atomic unfreed_bytes(0L); return unfreed_bytes; } libcuckoo-0.3.1/tests/unit-tests/unit_test_util.hh000066400000000000000000000117621426042121400223450ustar00rootroot00000000000000// Utilities for unit testing #ifndef UNIT_TEST_UTIL_HH_ #define UNIT_TEST_UTIL_HH_ #include #include #include #include #include #include #include #include #include // Returns a statically allocated value used to keep track of how many unfreed // bytes have been allocated. This value is shared across all threads. std::atomic &get_unfreed_bytes(); // We define a a allocator class that keeps track of how many unfreed bytes have // been allocated. Users can specify an optional bound for how many bytes can be // unfreed, and the allocator will fail if asked to allocate above that bound // (note that behavior with this bound with concurrent allocations will be hard // to deal with). A bound below 0 is inactive (the default is -1). template struct TrackingAllocator { using value_type = T; using pointer = T *; using const_pointer = const T *; using reference = T &; using const_reference = const T &; using size_type = size_t; using difference_type = ptrdiff_t; template struct rebind { using other = TrackingAllocator; }; TrackingAllocator() {} template TrackingAllocator(const TrackingAllocator &) {} T *allocate(size_t n) { const size_t bytes_to_allocate = sizeof(T) * n; if (BOUND >= 0 && get_unfreed_bytes() + bytes_to_allocate > BOUND) { throw std::bad_alloc(); } get_unfreed_bytes() += bytes_to_allocate; return std::allocator().allocate(n); } void deallocate(T *p, size_t n) { get_unfreed_bytes() -= (sizeof(T) * n); std::allocator().deallocate(p, n); } template void construct(U *p, Args &&... args) { new ((void *)p) U(std::forward(args)...); } template void destroy(U *p) { p->~U(); } }; template bool operator==(const TrackingAllocator &a1, const TrackingAllocator &a2) { return true; } template bool operator!=(const TrackingAllocator &a1, const TrackingAllocator &a2) { return false; } using IntIntTable = libcuckoo::cuckoohash_map, std::equal_to, std::allocator>, 4>; template using IntIntTableWithAlloc = libcuckoo::cuckoohash_map, std::equal_to, Alloc, 4>; using StringIntTable = libcuckoo::cuckoohash_map, std::equal_to, std::allocator>, 4>; namespace std { template struct hash> { size_t operator()(const unique_ptr &ptr) const { return std::hash()(*ptr); } size_t operator()(const T *ptr) const { return std::hash()(*ptr); } }; template struct equal_to> { bool operator()(const unique_ptr &ptr1, const unique_ptr &ptr2) const { return *ptr1 == *ptr2; } bool operator()(const T *ptr1, const unique_ptr &ptr2) const { return *ptr1 == *ptr2; } bool operator()(const unique_ptr &ptr1, const T *ptr2) const { return *ptr1 == *ptr2; } }; } template using UniquePtrTable = libcuckoo::cuckoohash_map< std::unique_ptr, std::unique_ptr, std::hash>, std::equal_to>, std::allocator, std::unique_ptr>>, 4>; // Some unit tests need access into certain private data members of the table. // This class is a friend of the table, so it can access those. namespace libcuckoo { class UnitTestInternalAccess { public: friend IntIntTable; static const size_t IntIntBucketSize = sizeof(IntIntTable::bucket); template static size_t old_table_info_size(const CuckoohashMap &table) { // This is not thread-safe return table.old_table_infos.size(); } template static typename CuckoohashMap::partial_t partial_key(const size_t hv) { return CuckoohashMap::partial_key(hv); } template static size_t index_hash(const size_t hashpower, const size_t hv) { return CuckoohashMap::index_hash(hashpower, hv); } template static size_t alt_index(const size_t hashpower, const typename CuckoohashMap::partial_t partial, const size_t index) { return CuckoohashMap::alt_index(hashpower, partial, index); } template static size_t reserve_calc(size_t n) { return CuckoohashMap::reserve_calc(n); } template static typename CuckoohashMap::locks_t & get_current_locks(const CuckoohashMap &table) { return table.get_current_locks(); } }; } // namespace libcuckoo #endif // UNIT_TEST_UTIL_HH_ libcuckoo-0.3.1/tests/universal-benchmark/000077500000000000000000000000001426042121400205635ustar00rootroot00000000000000libcuckoo-0.3.1/tests/universal-benchmark/CMakeLists.txt000066400000000000000000000033461426042121400233310ustar00rootroot00000000000000add_executable(universal_benchmark universal_benchmark.cc) # put these in the cache so they show up in ccmake set (UNIVERSAL_KEY uint64_t CACHE STRING "set the key type used by the universal benchmark") set (UNIVERSAL_VALUE uint64_t CACHE STRING "set the value type used by the universal benchmark") set (UNIVERSAL_TABLE LIBCUCKOO CACHE STRING "set the table type used by the universal benchmark") option (UNIVERSAL_TRACKING_ALLOCATOR "if on, also sample memory usage in the universal benchmark") target_link_libraries(universal_benchmark PRIVATE test_util PRIVATE libcuckoo PRIVATE pcg ) target_compile_options(universal_benchmark PRIVATE -DKEY=${UNIVERSAL_KEY} PRIVATE -DVALUE=${UNIVERSAL_VALUE} PRIVATE -D${UNIVERSAL_TABLE} ) if(UNIVERSAL_TRACKING_ALLOCATOR) target_compile_options(universal_benchmark PRIVATE -DTRACKING_ALLOCATOR) endif() add_test(NAME pure_read COMMAND universal_benchmark --reads 100 --prefill 75 --total-ops 500 --initial-capacity 23) add_test(NAME pure_insert COMMAND universal_benchmark --inserts 100 --total-ops 75 --initial-capacity 23) add_test(NAME pure_erase COMMAND universal_benchmark --erases 100 --prefill 75 --total-ops 75 --initial-capacity 23) add_test(NAME pure_update COMMAND universal_benchmark --updates 100 --prefill 75 --total-ops 500 --initial-capacity 23) add_test(NAME pure_upsert COMMAND universal_benchmark --upserts 100 --prefill 25 --total-ops 200 --initial-capacity 23) add_test(NAME insert_expansion COMMAND universal_benchmark --inserts 100 --initial-capacity 4 --total-ops 13107200) add_test(NAME read_insert_expansion COMMAND universal_benchmark --reads 80 --inserts 20 --initial-capacity 10 --total-ops 4096000) libcuckoo-0.3.1/tests/universal-benchmark/README.md000066400000000000000000000064671426042121400220570ustar00rootroot00000000000000# Universal Benchmark This walkthrough explains how the `universal_benchmark` executable operates and what each flag means. ## Step-by-Step Operation 1. Generate the mixture of operations you will be running, which consists of a certain percentage of reads, inserts, erases, updates, and upserts. We pre-generate the mixture to avoid having to compute which operation to run while timing the workload 2. Pre-generate all keys we will be inserting into the table. We can calculate an upper bound on the number of keys being inserted based on the prefill percentage (`--prefill`) and the number of inserts and upserts we’ll be performing. Again, pre-generating the keys avoids doing it while timing the workload. We don’t need to pre-generate the values, since the values are the same for all operations 3. Initialize the table, pre-sized to a specified capacity (`--initial-capacity`) 4. Fill up the table in advance to the specified prefill percentage 5. Run the pre-generated mixture of operations (`--total-ops`) and time how long it takes to complete all of them 6. Report the details of the benchmark configuration and the quantities measured, including time elapsed, throughput, and (optionally) memory usage samples. ## Flags These flags are passed to CMake and set various compile-time parameters for the benchmark: `-DUNIVERSAL_KEY` : sets the type of the table Key (by default, this is `uint64_t`). Support for new keys can be added in `universal_gen.hh` `-DUNIVERSAL_VALUE` : sets the type of the table Value. Support for new values can be added in `universal_gen.hh` `-DUNIVERSAL_TABLE` : sets the type of the hashmap being benchmarked (by default, this is `LIBCUCKOO`). Support for new maps can be added in `universal_table_wrapper.hh` `-DUNIVERSAL_TRACKING_ALLOCATOR` : enables memory usage sampling These flags control the mixture of operations that will be run in the benchmark. They are interpreted as whole number percentages, and must sum to 100: `--reads` : percentage of operations that are reads `--inserts` : percentage of operations that are inserts `--erases` : percentage of operations that are erases `--updates` : percentage of operations that are updates `--upserts` : percentage of operations that are upserts These flags control some parameters about what the table will look like before the operation mixture is run: `--initial-capacity` : sets the initial number of elements the table is pre-sized to hold, as a power of 2. So if you pass 25 as the value of this flag, the table will be pre-sized to hold 2^25 elements `-–prefill` : sets the percentage to fill the pre-sized table to before running the operations. These flags control a few other details of the benchmark: `--total-ops` : the total number of operations to run (in the timed phase), as a percentage of the initial table capacity. So specifying `--initial-capacity 25 --total-ops 90` means run `90%` of `2^25`, or about `30198988`, operations. Specifying it as a percentage makes it easy to specify the final table capacity without having to do too much calculation or write a large number. `--num-threads` : the number of threads to use in all phases of the benchmark `--seed` : the seed to use for the rng, or 0 if you want to use a randomly generated seed. If `--num-threads` is 1 and you specify a specific seed, the test should be repeatable. libcuckoo-0.3.1/tests/universal-benchmark/universal_benchmark.cc000066400000000000000000000322121426042121400251140ustar00rootroot00000000000000/* Benchmarks a mix of operations for a compile-time specified key-value pair */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "universal_gen.hh" #include "universal_table_wrapper.hh" /* Run-time parameters -- operation mix and table configuration */ // The following specify what percentage of operations should be of each type. // They must add up to 100, but by default are all 0. size_t g_read_percentage = 0; size_t g_insert_percentage = 0; size_t g_erase_percentage = 0; size_t g_update_percentage = 0; size_t g_upsert_percentage = 0; // The initial capacity of the table, specified as a power of 2. size_t g_initial_capacity = 25; // The percentage of the initial table capacity should we fill the table to // before running the benchmark. size_t g_prefill_percentage = 0; // Total number of operations we are running, specified as a percentage of the // initial capacity. This can exceed 100. size_t g_total_ops_percentage = 75; // Number of threads to run with size_t g_threads = std::thread::hardware_concurrency(); // Seed for random number generator. If left at the default (0), we'll generate // a random seed. size_t g_seed = 0; const char *args[] = { "--reads", "--inserts", "--erases", "--updates", "--upserts", "--initial-capacity", "--prefill", "--total-ops", "--num-threads", "--seed", }; size_t *arg_vars[] = { &g_read_percentage, &g_insert_percentage, &g_erase_percentage, &g_update_percentage, &g_upsert_percentage, &g_initial_capacity, &g_prefill_percentage, &g_total_ops_percentage, &g_threads, &g_seed, }; const char *arg_descriptions[] = { "Percentage of mix that is reads", "Percentage of mix that is inserts", "Percentage of mix that is erases", "Percentage of mix that is updates", "Percentage of mix that is upserts", "Initial capacity of table, as a power of 2", "Percentage of final size to pre-fill table", "Number of operations, as a percentage of the initial capacity. This can " "exceed 100", "Number of threads", "Seed for random number generator", }; #define XSTR(s) STR(s) #define STR(s) #s const char *description = "A benchmark that can run an arbitrary mixture of " "table operations.\nThe sum of read, insert, erase, update, and upsert " "percentages must be 100.\nMap type is " TABLE_TYPE "<" XSTR(KEY) ", " XSTR(VALUE) ">."; void check_percentage(size_t value, const char *name) { if (value > 100) { std::string msg("Percentage for `"); msg += name; msg += "` cannot exceed 100\n"; throw std::runtime_error(msg.c_str()); } } enum Ops { READ, INSERT, ERASE, UPDATE, UPSERT, }; void gen_nums(std::vector &nums, pcg64_oneseq_once_insecure &rng) { for (uint64_t &num : nums) { num = rng(); } } void gen_keys(std::vector &nums, std::vector::storage_type> &keys) { const size_t n = nums.size(); for (size_t i = 0; i < n; ++i) { keys[i] = Gen::storage_key(nums[i]); } } void prefill(Table &tbl, const std::vector::storage_type> &keys, const size_t prefill_elems) { Gen::storage_type local_value = Gen::storage_value(); for (size_t i = 0; i < prefill_elems; ++i) { ASSERT_TRUE( tbl.insert(Gen::get(keys[i]), Gen::get(local_value))); } } void mix(Table &tbl, const size_t num_ops, const std::array &op_mix, const std::vector::storage_type> &keys, const size_t prefill_elems, std::vector &samples) { Sampler sampler(num_ops); Gen::storage_type local_value = Gen::storage_value(); // Invariant: erase_seq <= insert_seq // Invariant: insert_seq < numkeys const size_t numkeys = keys.size(); size_t erase_seq = 0; size_t insert_seq = prefill_elems; // These variables are initialized out here so we don't create new variables // in the switch statement. size_t n; VALUE v; // Convenience functions for getting the nth key and value auto key = [&keys](size_t n) { assert(n < keys.size()); return Gen::get(keys[n]); }; // The upsert function is just the identity auto upsert_fn = [](VALUE &v) { return; }; // Use an LCG over the keys array to iterate over the keys in a pseudorandom // order, for find operations assert(1UL << static_cast(floor(log2(numkeys))) == numkeys); assert(numkeys > 4); size_t find_seq = 0; const size_t a = numkeys / 2 + 1; const size_t c = numkeys / 4 - 1; const size_t find_seq_mask = numkeys - 1; auto find_seq_update = [&find_seq, &a, &c, &find_seq_mask, &numkeys]() { find_seq = (a * find_seq + c) & find_seq_mask; }; // Run the operation mix for num_ops operations for (size_t i = 0; i < num_ops;) { for (size_t j = 0; j < 100 && i < num_ops; ++i, ++j) { sampler.iter(); switch (op_mix[j]) { case READ: // If `find_seq` is between `erase_seq` and `insert_seq`, then it // should be in the table. ASSERT_EQ(find_seq >= erase_seq && find_seq < insert_seq, tbl.read(key(find_seq), v)); find_seq_update(); break; case INSERT: // Insert sequence number `insert_seq`. This should always // succeed and be inserting a new value. ASSERT_TRUE(tbl.insert(key(insert_seq), Gen::get(local_value))); ++insert_seq; break; case ERASE: // If `erase_seq` == `insert_seq`, the table should be empty, so // we pick a random index to unsuccessfully erase. Otherwise we // erase `erase_seq`. if (erase_seq == insert_seq) { ASSERT_TRUE(!tbl.erase(key(find_seq))); find_seq_update(); } else { ASSERT_TRUE(tbl.erase(key(erase_seq++))); } break; case UPDATE: // Same as find, except we update to the same default value ASSERT_EQ(find_seq >= erase_seq && find_seq < insert_seq, tbl.update(key(find_seq), Gen::get(local_value))); find_seq_update(); break; case UPSERT: // Pick a number from the full distribution, but cap it to the // insert_seq, so we don't insert a number greater than // insert_seq. n = std::min(find_seq, insert_seq); find_seq_update(); tbl.upsert(key(n), upsert_fn, Gen::get(local_value)); if (n == insert_seq) { ++insert_seq; } break; } } } sampler.store(samples); } int main(int argc, char **argv) { try { // Parse parameters and check them. parse_flags(argc, argv, description, args, arg_vars, arg_descriptions, sizeof(args) / sizeof(const char *), nullptr, nullptr, nullptr, 0); check_percentage(g_read_percentage, "reads"); check_percentage(g_insert_percentage, "inserts"); check_percentage(g_erase_percentage, "erases"); check_percentage(g_update_percentage, "updates"); check_percentage(g_upsert_percentage, "upserts"); check_percentage(g_prefill_percentage, "prefill"); if (g_read_percentage + g_insert_percentage + g_erase_percentage + g_update_percentage + g_upsert_percentage != 100) { throw std::runtime_error("Operation mix percentages must sum to 100\n"); } if (g_seed == 0) { g_seed = std::random_device()(); } pcg64_oneseq_once_insecure base_rng(g_seed); const size_t initial_capacity = 1UL << g_initial_capacity; const size_t total_ops = initial_capacity * g_total_ops_percentage / 100; // Pre-generate an operation mix based on our percentages. std::array op_mix; auto *op_mix_p = &op_mix[0]; for (size_t i = 0; i < g_read_percentage; ++i) { *op_mix_p++ = READ; } for (size_t i = 0; i < g_insert_percentage; ++i) { *op_mix_p++ = INSERT; } for (size_t i = 0; i < g_erase_percentage; ++i) { *op_mix_p++ = ERASE; } for (size_t i = 0; i < g_update_percentage; ++i) { *op_mix_p++ = UPDATE; } for (size_t i = 0; i < g_upsert_percentage; ++i) { *op_mix_p++ = UPSERT; } std::shuffle(op_mix.begin(), op_mix.end(), base_rng); // Pre-generate all the keys we'd want to insert. In case the insert + // upsert percentage is too low, lower bound by the table capacity. std::cerr << "Generating keys\n"; const size_t prefill_elems = initial_capacity * g_prefill_percentage / 100; // We won't be running through `op_mix` more than ceil(total_ops / 100), // so calculate that ceiling and multiply by the number of inserts and // upserts to get an upper bound on how many elements we'll be // inserting. const size_t max_insert_ops = (total_ops + 99) / 100 * (g_insert_percentage + g_erase_percentage); const size_t insert_keys = std::max(initial_capacity, max_insert_ops) + prefill_elems; // Round this quantity up to a power of 2, so that we can use an LCG to // cycle over the array "randomly". const size_t insert_keys_per_thread = 1UL << static_cast( ceil(log2((insert_keys + g_threads - 1) / g_threads))); // Can't do this in parallel, because the random number generator is // single-threaded. std::vector> nums(g_threads); for (size_t i = 0; i < g_threads; ++i) { nums[i].resize(insert_keys_per_thread); gen_nums(nums[i], base_rng); } std::vector gen_key_threads(g_threads); std::vector::storage_type>> keys(g_threads); for (size_t i = 0; i < g_threads; ++i) { keys[i].resize(insert_keys_per_thread); gen_key_threads[i] = std::thread(gen_keys, std::ref(nums[i]), std::ref(keys[i])); } for (auto &t : gen_key_threads) { t.join(); } // Create and size the table Table tbl(initial_capacity); std::cerr << "Pre-filling table\n"; std::vector prefill_threads(g_threads); const size_t prefill_elems_per_thread = prefill_elems / g_threads; for (size_t i = 0; i < g_threads; ++i) { prefill_threads[i] = std::thread( prefill, std::ref(tbl), std::ref(keys[i]), prefill_elems_per_thread); } for (auto &t : prefill_threads) { t.join(); } // Run the operation mix, timed std::cerr << "Running operations\n"; std::vector mix_threads(g_threads); std::vector> samples(g_threads); const size_t num_ops_per_thread = total_ops / g_threads; auto start_time = std::chrono::high_resolution_clock::now(); for (size_t i = 0; i < g_threads; ++i) { mix_threads[i] = std::thread( mix, std::ref(tbl), num_ops_per_thread, std::ref(op_mix), std::ref(keys[i]), prefill_elems_per_thread, std::ref(samples[i])); } for (auto &t : mix_threads) { t.join(); } auto end_time = std::chrono::high_resolution_clock::now(); double seconds_elapsed = std::chrono::duration_cast>(end_time - start_time) .count(); // Print out args, preprocessor constants, and results in JSON format std::stringstream argstr; argstr << args[0] << " " << *arg_vars[0]; for (size_t i = 1; i < sizeof(args) / sizeof(args[0]); ++i) { argstr << " " << args[i] << " " << *arg_vars[i]; } // Average together the allocator samples from each thread. If // TRACKING_ALLOCATOR is turned off, the samples should all be empty, // and this list should end up empty. std::stringstream samplestr; samplestr << "["; const size_t total_samples = samples[0].size(); for (size_t i = 0; i < total_samples; ++i) { size_t total = 0; for (size_t j = 0; j < g_threads; ++j) { total += samples.at(j).at(i); } size_t avg = total / g_threads; samplestr << avg; if (i < total_samples - 1) { samplestr << ","; } } samplestr << "]"; const char *json_format = R"({ "args": "%s", "key": "%s", "key_size": "%zu", "value": "%s", "value_size": "%zu", "table": "%s", "output": { "total_ops": { "name": "Total Operations", "units": "count", "value": %zu }, "time_elapsed": { "name": "Time Elapsed", "units": "seconds", "value": %.4f }, "throughput": { "name": "Throughput", "units": "count/seconds", "value": %.4f }, "memory_samples": { "name": "Memory Samples", "units": "[bytes]", "value": %s } } } )"; printf(json_format, argstr.str().c_str(), XSTR(KEY), Gen::key_size, XSTR(VALUE), Gen::value_size, TABLE, total_ops, seconds_elapsed, total_ops / seconds_elapsed, samplestr.str().c_str()); } catch (const std::exception &e) { std::cerr << e.what(); std::exit(1); } } libcuckoo-0.3.1/tests/universal-benchmark/universal_gen.hh000066400000000000000000000057751426042121400237630ustar00rootroot00000000000000#ifndef _UNIVERSAL_GEN_HH #define _UNIVERSAL_GEN_HH #include #include #include #include /* A specialized functor for generating unique keys and values for various * types. Must define one for each type we want to use. These keys and values * are meant to be copied into the table (not moved). */ template class Gen { // using storage_type = ... // static storage_type storage_key(uint64_t num) // static storage_type storage_value() // static T get(storage_type&) // static constexpr size_t key_size // static constexpr size_t value_size }; template <> class Gen { public: using storage_type = uint64_t; static storage_type storage_key(uint64_t num) { return num; } static storage_type storage_value() { return 0; } static uint64_t get(const storage_type &st) { return st; } static constexpr size_t key_size = sizeof(uint64_t); static constexpr size_t value_size = sizeof(uint64_t); }; template <> class Gen { static constexpr size_t STRING_SIZE = 100; public: using storage_type = std::string; static storage_type storage_key(uint64_t num) { return std::string( static_cast(static_cast(&num)), sizeof(num)); } static storage_type storage_value() { return std::string(STRING_SIZE, '0'); } static std::string get(const storage_type &st) { return st; } static constexpr size_t key_size = sizeof(uint64_t); static constexpr size_t value_size = 100; }; // Should be 256B. Bitset is nice since it already has std::hash specialized. using MediumBlob = std::bitset<2048>; template <> class Gen { public: using storage_type = MediumBlob; static storage_type storage_key(uint64_t num) { return MediumBlob(Gen::storage_key(num)); } static storage_type storage_value() { return MediumBlob(); } static MediumBlob get(const storage_type &st) { return st; } static constexpr size_t key_size = sizeof(MediumBlob); static constexpr size_t value_size = sizeof(MediumBlob); }; // Should be 512B using BigBlob = std::bitset<4096>; template <> class Gen { public: using storage_type = BigBlob; static storage_type storage_key(uint64_t num) { return BigBlob(Gen::storage_key(num)); } static storage_type storage_value() { return BigBlob(); } static BigBlob get(const storage_type &st) { return st; } static constexpr size_t key_size = sizeof(BigBlob); static constexpr size_t value_size = sizeof(BigBlob); }; template class Gen { public: using storage_type = std::unique_ptr; static storage_type storage_key(uint64_t num) { return storage_type(new T(Gen::storage_key(num))); } static storage_type storage_value() { return storage_type(new T(Gen::storage_value())); } static T *get(const storage_type &st) { return st.get(); } static constexpr size_t key_size = Gen::key_size; static constexpr size_t value_size = Gen::value_size; }; #endif // _UNIVERSAL_GEN_HH libcuckoo-0.3.1/tests/universal-benchmark/universal_table_wrapper.hh000066400000000000000000000116771426042121400260370ustar00rootroot00000000000000/* For each table to support, we define a wrapper class which holds the table * and implements all of the benchmarked operations. Below we list all the * methods each wrapper must implement. * * constructor(size_t n) // n is the initial capacity * template * bool read(const K& k, V& v) const * template * bool insert(const K& k, const V& v) * template * bool erase(const K& k) * template * bool update(const K& k, const V& v) * template * void upsert(const K& k, Updater fn, const V& v) */ #ifndef _UNIVERSAL_TABLE_WRAPPER_HH #define _UNIVERSAL_TABLE_WRAPPER_HH #include #include #include #ifndef KEY #error Must define KEY symbol as valid key type #endif #ifndef VALUE #error Must define VALUE symbol as valid value type #endif #ifdef TRACKING_ALLOCATOR std::atomic universal_benchmark_current_bytes_allocated = ATOMIC_VAR_INIT(0); template