Pull in full sqlite_modern_cpp repo for the license as it is not attached to source files

This commit is contained in:
Saood Karim
2025-08-17 08:25:37 -05:00
parent a3b174b69a
commit ed9504bd92
41 changed files with 2654 additions and 0 deletions

View File

@@ -95,5 +95,6 @@ if (LLAMA_CURL)
endif ()
target_include_directories(${TARGET} PUBLIC .)
target_include_directories(${TARGET} PUBLIC ./sqlite_modern_cpp/hdr)
target_compile_features (${TARGET} PUBLIC cxx_std_11)
target_link_libraries (${TARGET} PRIVATE ${LLAMA_COMMON_EXTRA_LIBS} PUBLIC llama Threads::Threads)

9
common/sqlite_modern_cpp/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.deps
config.log
config.status
compile_commands.json
build/

View File

@@ -0,0 +1,26 @@
language: cpp
sudo: required
dist: trusty
cache:
apt: true
directories:
- /home/travis/.hunter/
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- george-edison55-precise-backports
packages:
- g++-5
- libsqlcipher-dev
- cmake
before_install:
- export CXX="g++-5" CC="gcc-5"
script: mkdir build && cd ./build && cmake .. && cmake --build . && ctest .
# TODO: fix sqlcipher test
# script: mkdir build && cd ./build && cmake -DENABLE_SQLCIPHER_TESTS=ON .. && make && ./tests

View File

@@ -0,0 +1,100 @@
import os
import ycm_core
from clang_helpers import PrepareClangFlags
def DirectoryOfThisScript():
return os.path.dirname(os.path.abspath(__file__))
# This is the single most important line in this script. Everything else is just nice to have but
# not strictly necessary.
compilation_database_folder = DirectoryOfThisScript()
# This provides a safe fall-back if no compilation commands are available. You could also add a
# includes relative to your project directory, for example.
flags = [
'-Wall',
'-std=c++14',
'-x',
'c++',
'-isystem', '/usr/local/include',
'-isystem', '/usr/include',
'-I.',
]
database = ycm_core.CompilationDatabase(compilation_database_folder)
SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ]
def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
if not working_directory:
return list( flags )
new_flags = []
make_next_absolute = False
path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
for flag in flags:
new_flag = flag
if make_next_absolute:
make_next_absolute = False
if not flag.startswith( '/' ):
new_flag = os.path.join( working_directory, flag )
for path_flag in path_flags:
if flag == path_flag:
make_next_absolute = True
break
if flag.startswith( path_flag ):
path = flag[ len( path_flag ): ]
new_flag = path_flag + os.path.join( working_directory, path )
break
if new_flag:
new_flags.append( new_flag )
return new_flags
def IsHeaderFile( filename ):
extension = os.path.splitext( filename )[ 1 ]
return extension in [ '.h', '.hxx', '.hpp', '.hh' ]
def GetCompilationInfoForFile( filename ):
# The compilation_commands.json file generated by CMake does not have entries
# for header files. So we do our best by asking the db for flags for a
# corresponding source file, if any. If one exists, the flags for that file
# should be good enough.
if IsHeaderFile( filename ):
basename = os.path.splitext( filename )[ 0 ]
for extension in SOURCE_EXTENSIONS:
replacement_file = basename + extension
if os.path.exists( replacement_file ):
compilation_info = database.GetCompilationInfoForFile(
replacement_file )
if compilation_info.compiler_flags_:
return compilation_info
return None
return database.GetCompilationInfoForFile( filename )
def FlagsForFile( filename, **kwargs ):
if database:
# Bear in mind that compilation_info.compiler_flags_ does NOT return a
# python list, but a "list-like" StringVec object
compilation_info = GetCompilationInfoForFile( filename )
if not compilation_info:
return None
final_flags = MakeRelativePathsInFlagsAbsolute(
compilation_info.compiler_flags_,
compilation_info.compiler_working_dir_ )
else:
relative_to = DirectoryOfThisScript()
final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )
return {
'flags': final_flags,
'do_cache': True
}

View File

@@ -0,0 +1,56 @@
cmake_minimum_required(VERSION 3.0)
option(ENABLE_SQLCIPHER_TESTS "enable sqlchipher test")
# Creates the file compile_commands.json in the build directory.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
set(HUNTER_TLS_VERIFY ON)
include("cmake/HunterGate.cmake")
include("cmake/Catch.cmake")
HunterGate(
URL "https://github.com/cpp-pm/hunter/archive/v0.24.15.tar.gz"
SHA1 "8010d63d5ae611c564889d5fe12d3cb7a45703ac"
)
project(SqliteModernCpp)
hunter_add_package(Catch)
hunter_add_package(sqlite3)
find_package(Catch2 CONFIG REQUIRED)
find_package(sqlite3 CONFIG REQUIRED)
set(TEST_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests)
file(GLOB TEST_SOURCES ${TEST_SOURCE_DIR}/*.cc)
IF(NOT ENABLE_SQLCIPHER_TESTS)
list(REMOVE_ITEM TEST_SOURCES ${TEST_SOURCE_DIR}/sqlcipher.cc)
ENDIF(NOT ENABLE_SQLCIPHER_TESTS)
enable_testing()
add_library (sqlite_modern_cpp INTERFACE)
target_include_directories(sqlite_modern_cpp INTERFACE hdr/)
add_executable(tests_runner ${TEST_SOURCES})
target_include_directories(tests_runner INTERFACE ${SQLITE3_INCLUDE_DIRS})
if(ENABLE_SQLCIPHER_TESTS)
target_link_libraries(tests_runner Catch2::Catch2 sqlite_modern_cpp sqlite3::sqlite3 -lsqlcipher)
else()
target_link_libraries(tests_runner Catch2::Catch2 sqlite_modern_cpp sqlite3::sqlite3)
endif()
catch_discover_tests(tests_runner)
target_compile_options(tests_runner PUBLIC $<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus> )
# Place the file in the source directory, permitting us to place a single configuration file for YCM there.
# YCM is the code-completion engine for (neo)vim https://github.com/Valloric/YouCompleteMe
IF(EXISTS "${CMAKE_BINARY_DIR}/compile_commands.json")
EXECUTE_PROCESS( COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_BINARY_DIR}/compile_commands.json
${CMAKE_SOURCE_DIR}/compile_commands.json
)
ENDIF()

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 aminroosta
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,482 @@
[![Build Status](https://drone.io/github.com/aminroosta/sqlite_modern_cpp/status.png)](https://drone.io/github.com/aminroosta/sqlite_modern_cpp/latest)
sqlite modern cpp wrapper
====
This library is a lightweight modern wrapper around sqlite C api .
```c++
#include<iostream>
#include <sqlite_modern_cpp.h>
using namespace sqlite;
using namespace std;
int main() {
try {
// creates a database file 'dbfile.db' if it does not exists.
database db("dbfile.db");
// executes the query and creates a 'user' table
db <<
"create table if not exists user ("
" _id integer primary key autoincrement not null,"
" age int,"
" name text,"
" weight real"
");";
// inserts a new user record.
// binds the fields to '?' .
// note that only types allowed for bindings are :
// int ,long, long long, float, double
// string, u16string
// sqlite3 only supports utf8 and utf16 strings, you should use std::string for utf8 and std::u16string for utf16.
// If you're using C++17, `std::string_view` and `std::u16string_view` can be used as string types.
// note that u"my text" is a utf16 string literal of type char16_t * .
db << "insert into user (age,name,weight) values (?,?,?);"
<< 20
<< u"bob"
<< 83.25;
int age = 21;
float weight = 68.5;
string name = "jack";
db << u"insert into user (age,name,weight) values (?,?,?);" // utf16 query string
<< age
<< name
<< weight;
cout << "The new record got assigned id " << db.last_insert_rowid() << endl;
// selects from user table on a condition ( age > 18 ) and executes
// the lambda for each row returned .
db << "select age,name,weight from user where age > ? ;"
<< 18
>> [&](int age, string name, double weight) {
cout << age << ' ' << name << ' ' << weight << endl;
};
// a for loop can be used too:
// with named variables
for(auto &&row : db << "select age,name,weight from user where age > ? ;" << 18) {
int age; string name; double weight;
row >> age >> name >> weight;
cout << age << ' ' << name << ' ' << weight << endl;
}
// or with a tuple
for(tuple<int, string, double> row : db << "select age,name,weight from user where age > ? ;" << 18) {
cout << get<0>(row) << ' ' << get<1>(row) << ' ' << get<2>(row) << endl;
}
// selects the count(*) from user table
// note that you can extract a single column single row result only to : int,long,long long,float,double,string,u16string
int count = 0;
db << "select count(*) from user" >> count;
cout << "cout : " << count << endl;
// you can also extract multiple column rows
db << "select age, name from user where _id=1;" >> tie(age, name);
cout << "Age = " << age << ", name = " << name << endl;
// this also works and the returned value will be automatically converted to string
string str_count;
db << "select count(*) from user" >> str_count;
cout << "scount : " << str_count << endl;
}
catch (const exception& e) {
cout << e.what() << endl;
}
}
```
You can not execute multiple statements separated by semicolons in one go.
Additional flags
----
You can pass additional open flags to SQLite by using a config object:
```c++
sqlite_config config;
config.flags = OpenFlags::READONLY
database db("some_db", config);
int a;
// Now you can only read from db
auto ps = db << "select a from table where something = ? and anotherthing = ?" >> a;
config.flags = OpenFlags::READWRITE | OpenFlags::CREATE; // This is the default
config.encoding = Encoding::UTF16; // The encoding is respected only if you create a new database
database db2("some_db2", config);
// If some_db2 didn't exists before, it will be created with UTF-16 encoding.
```
Prepared Statements
----
It is possible to retain and reuse statments this will keep the query plan and in case of an complex query or many uses might increase the performance significantly.
```c++
database db(":memory:");
// if you use << on a sqlite::database you get a prepared statment back
// this will not be executed till it gets destroyed or you execute it explicitly
auto ps = db << "select a,b from table where something = ? and anotherthing = ?"; // get a prepared parsed and ready statment
// first if needed bind values to it
ps << 5;
int tmp = 8;
ps << tmp;
// now you can execute it with `operator>>` or `execute()`.
// If the statement was executed once it will not be executed again when it goes out of scope.
// But beware that it will execute on destruction if it wasn't executed!
ps >> [&](int a,int b){ ... };
// after a successfull execution the statment can be executed again, but the bound values are resetted.
// If you dont need the returned values you can execute it like this
ps.execute();
// or like this
ps++;
// To disable the execution of a statment when it goes out of scope and wasn't used
ps.used(true); // or false if you want it to execute even if it was used
// Usage Example:
auto ps = db << "insert into complex_table_with_lots_of_indices values (?,?,?)";
int i = 0;
while( i < 100000 ){
ps << long_list[i++] << long_list[i++] << long_list[i++];
ps++;
}
```
Shared Connections
----
If you need the handle to the database connection to execute sqlite3 commands directly you can get a managed shared_ptr to it, so it will not close as long as you have a referenc to it.
Take this example on how to deal with a database backup using SQLITEs own functions in a safe and modern way.
```c++
try {
database backup("backup"); //Open the database file we want to backup to
auto con = db.connection(); // get a handle to the DB we want to backup in our scope
// this way we are sure the DB is open and ok while we backup
// Init Backup and make sure its freed on exit or exceptions!
auto state =
std::unique_ptr<sqlite3_backup,decltype(&sqlite3_backup_finish)>(
sqlite3_backup_init(backup.connection().get(), "main", con.get(), "main"),
sqlite3_backup_finish
);
if(state) {
int rc;
// Each iteration of this loop copies 500 database pages from database db to the backup database.
do {
rc = sqlite3_backup_step(state.get(), 500);
std::cout << "Remaining " << sqlite3_backup_remaining(state.get()) << "/" << sqlite3_backup_pagecount(state.get()) << "\n";
} while(rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED);
}
} // Release allocated resources.
```
Transactions
----
You can use transactions with `begin;`, `commit;` and `rollback;` commands.
```c++
db << "begin;"; // begin a transaction ...
db << "insert into user (age,name,weight) values (?,?,?);"
<< 20
<< u"bob"
<< 83.25f;
db << "insert into user (age,name,weight) values (?,?,?);" // utf16 string
<< 21
<< u"jack"
<< 68.5;
db << "commit;"; // commit all the changes.
db << "begin;"; // begin another transaction ....
db << "insert into user (age,name,weight) values (?,?,?);" // utf16 string
<< 19
<< u"chirs"
<< 82.7;
db << "rollback;"; // cancel this transaction ...
```
Blob
----
Use `std::vector<T>` to store and retrieve blob data.
`T` could be `char,short,int,long,long long, float or double`.
```c++
db << "CREATE TABLE person (name TEXT, numbers BLOB);";
db << "INSERT INTO person VALUES (?, ?)" << "bob" << vector<int> { 1, 2, 3, 4};
db << "INSERT INTO person VALUES (?, ?)" << "sara" << vector<double> { 1.0, 2.0, 3.0, 4.0};
vector<int> numbers_bob;
db << "SELECT numbers from person where name = ?;" << "bob" >> numbers_bob;
db << "SELECT numbers from person where name = ?;" << "sara" >> [](vector<double> numbers_sara){
for(auto e : numbers_sara) cout << e << ' '; cout << endl;
};
```
NULL values
----
If you have databases where some rows may be null, you can use `std::unique_ptr<T>` to retain the NULL values between C++ variables and the database.
```c++
db << "CREATE TABLE tbl (id integer,age integer, name string, img blob);";
db << "INSERT INTO tbl VALUES (?, ?, ?, ?);" << 1 << 24 << "bob" << vector<int> { 1, 2 , 3};
unique_ptr<string> ptr_null; // you can even bind empty unique_ptr<T>
db << "INSERT INTO tbl VALUES (?, ?, ?, ?);" << 2 << nullptr << ptr_null << nullptr;
db << "select age,name,img from tbl where id = 1"
>> [](unique_ptr<int> age_p, unique_ptr<string> name_p, unique_ptr<vector<int>> img_p) {
if(age_p == nullptr || name_p == nullptr || img_p == nullptr) {
cerr << "ERROR: values should not be null" << std::endl;
}
cout << "age:" << *age_p << " name:" << *name_p << " img:";
for(auto i : *img_p) cout << i << ","; cout << endl;
};
db << "select age,name,img from tbl where id = 2"
>> [](unique_ptr<int> age_p, unique_ptr<string> name_p, unique_ptr<vector<int>> img_p) {
if(age_p != nullptr || name_p != nullptr || img_p != nullptr) {
cerr << "ERROR: values should be nullptr" << std::endl;
exit(EXIT_FAILURE);
}
cout << "OK all three values are nullptr" << endl;
};
```
SQLCipher
----
We have native support for [SQLCipher](https://www.zetetic.net/sqlcipher/).
If you want to use encrypted databases, include the `sqlite_moder_cpp/sqlcipher.h` header.
Then create a `sqlcipher_database` instead.
```c++
#include<iostream>
#include <sqlite_modern_cpp/sqlcipher.h>
using namespace sqlite;
using namespace std;
int main() {
try {
// creates a database file 'dbfile.db' if it does not exists with password 'secret'
sqlcipher_config config;
config.key = secret;
sqlcipher_database db("dbfile.db", config);
// executes the query and creates a 'user' table
db <<
"create table if not exists user ("
" _id integer primary key autoincrement not null,"
" age int,"
" name text,"
" weight real"
");";
// More queries ...
db.rekey("new_secret"); // Change the password of the already encrypted database.
// Even more queries ..
}
catch (const exception& e) { cout << e.what() << endl; }
}
```
NULL values (C++17)
----
You can use `std::optional<T>` as an alternative for `std::unique_ptr<T>` to work with NULL values.
```c++
#include <sqlite_modern_cpp.h>
struct User {
long long _id;
std::optional<int> age;
std::optional<string> name;
std::optional<real> weight;
};
int main() {
User user;
user.name = "bob";
// Same database as above
database db("dbfile.db");
// Here, age and weight will be inserted as NULL in the database.
db << "insert into user (age,name,weight) values (?,?,?);"
<< user.age
<< user.name
<< user.weight;
user._id = db.last_insert_rowid();
// Here, the User instance will retain the NULL value(s) from the database.
db << "select _id,age,name,weight from user where age > ? ;"
<< 18
>> [&](long long id,
std::optional<int> age,
std::optional<string> name
std::optional<real> weight) {
cout << "id=" << _id
<< " age = " << (age ? to_string(*age) ? string("NULL"))
<< " name = " << (name ? *name : string("NULL"))
<< " weight = " << (weight ? to_string(*weight) : string(NULL))
<< endl;
};
}
```
If the optional library is not available, the experimental/optional one will be used instead.
Variant type support (C++17)
----
If your columns may have flexible types, you can use C++17's `std::variant` to extract the value.
```c++
db << "CREATE TABLE tbl (id integer, data);";
db << "INSERT INTO tbl VALUES (?, ?);" << 1 << vector<int> { 1, 2, 3};
db << "INSERT INTO tbl VALUES (?, ?);" << 2 << 2.5;
db << "select data from tbl where id = 1"
>> [](std::variant<vector<int>, double> data) {
if(data.index() != 1) {
cerr << "ERROR: we expected a blob" << std::endl;
}
for(auto i : get<vector<int>>(data)) cout << i << ","; cout << endl;
};
db << "select data from tbl where id = 2"
>> [](std::variant<vector<int>, double> data) {
if(data.index() != 2) {
cerr << "ERROR: we expected a real number" << std::endl;
}
cout << get<double>(data) << endl;
};
```
If you read a specific type and this type does not match the actual type in the SQlite database, yor data will be converted.
This does not happen if you use a `variant`.
If the `variant` does an alternative of the same value type, an `mismatch` exception will be thrown.
The value types are NULL, integer, real number, text and BLOB.
To support all possible values, you can use `variant<nullptr_t, sqlite_int64, double, string, vector<char>`.
It is also possible to use a variant with `std::monostate` in order to catch null values.
Errors
----
On error, the library throws an error class indicating the type of error. The error classes are derived from the SQLITE3 error names, so if the error code is SQLITE_CONSTRAINT, the error class thrown is sqlite::errors::constraint. SQLite3 extended error names are supported too. So there is e.g. a class sqlite::errors::constraint_primarykey derived from sqlite::errors::constraint. Note that all errors are derived from sqlite::sqlite_exception and that itself is derived from std::runtime_exception.
sqlite::sqlite_exception has a `get_code()` member function to get the SQLITE3 error code or `get_extended_code()` to get the extended error code.
Additionally you can use `get_sql()` to see the SQL statement leading to the error.
```c++
database db(":memory:");
db << "create table person (id integer primary key not null, name text);";
try {
db << "insert into person (id, name) values (?,?)" << 1 << "jack";
// inserting again to produce error
db << "insert into person (id, name) values (?,?)" << 1 << "jack";
}
/* if you are trying to catch all sqlite related exceptions
* make sure to catch them by reference */
catch (const sqlite_exception& e) {
cerr << e.get_code() << ": " << e.what() << " during "
<< e.get_sql() << endl;
}
/* you can catch specific exceptions as well,
catch(const sqlite::errors::constraint &e) { } */
/* and even more specific exceptions
catch(const sqlite::errors::constraint_primarykey &e) { } */
```
You can also register a error logging function with `sqlite::error_log`.
The `<sqlite_modern_cpp/log.h>` header has to be included to make this function available.
The call to `sqlite::error_log` has to be the first call to any `sqlite_modern_cpp` function by your program.
```c++
error_log(
[&](sqlite_exception& e) {
cerr << e.get_code() << ": " << e.what() << endl;
},
[&](errors::misuse& e) {
/* You can behave differently to specific errors */
}
);
database db(":memory:");
db << "create table person (id integer primary key not null, name text);";
try {
db << "insert into person (id, name) values (?,?)" << 1 << "jack";
// inserting again to produce error
db << "insert into person (id, name) values (?,?)" << 1 << "jack";
}
catch (const sqlite_exception& e) {}
```
Custom SQL functions
----
To extend SQLite with custom functions, you just implement them in C++:
```c++
database db(":memory:");
db.define("tgamma", [](double i) {return std::tgamma(i);});
db << "CREATE TABLE numbers (number INTEGER);";
for(auto i=0; i!=10; ++i)
db << "INSERT INTO numbers VALUES (?);" << i;
db << "SELECT number, tgamma(number+1) FROM numbers;" >> [](double number, double factorial) {
cout << number << "! = " << factorial << '\n';
};
```
NDK support
----
Just Make sure you are using the full path of your database file :
`sqlite::database db("/data/data/com.your.package/dbfile.db")`.
Installation
----
The project is header only.
Simply point your compiler at the hdr/ directory.
Contributing
----
Install cmake and build the project.
Dependencies will be installed automatically (using hunter).
```bash
mkdir build
cd ./build
cmake ..
make
```
Breaking Changes
----
See breaking changes documented in each [Release](https://github.com/aminroosta/sqlite_modern_cpp/releases).
Package managers
----
Pull requests are welcome :wink:
- [AUR](https://aur.archlinux.org/packages/sqlite_modern_cpp/) Arch Linux
- maintainer [Nissar Chababy](https://github.com/funilrys)
- Nuget (TODO [nuget.org](https://www.nuget.org/))
- Conan (TODO [conan.io](https://conan.io/))
- [vcpkg](https://github.com/Microsoft/vcpkg)
## License
MIT license - [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php)

View File

@@ -0,0 +1,175 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
Catch
-----
This module defines a function to help use the Catch test framework.
The :command:`catch_discover_tests` discovers tests by asking the compiled test
executable to enumerate its tests. This does not require CMake to be re-run
when tests change. However, it may not work in a cross-compiling environment,
and setting test properties is less convenient.
This command is intended to replace use of :command:`add_test` to register
tests, and will create a separate CTest test for each Catch test case. Note
that this is in some cases less efficient, as common set-up and tear-down logic
cannot be shared by multiple test cases executing in the same instance.
However, it provides more fine-grained pass/fail information to CTest, which is
usually considered as more beneficial. By default, the CTest test name is the
same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
.. command:: catch_discover_tests
Automatically add tests with CTest by querying the compiled test executable
for available tests::
catch_discover_tests(target
[TEST_SPEC arg1...]
[EXTRA_ARGS arg1...]
[WORKING_DIRECTORY dir]
[TEST_PREFIX prefix]
[TEST_SUFFIX suffix]
[PROPERTIES name1 value1...]
[TEST_LIST var]
)
``catch_discover_tests`` sets up a post-build command on the test executable
that generates the list of tests by parsing the output from running the test
with the ``--list-test-names-only`` argument. This ensures that the full
list of tests is obtained. Since test discovery occurs at build time, it is
not necessary to re-run CMake when the list of tests changes.
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
in order to function in a cross-compiling environment.
Additionally, setting properties on tests is somewhat less convenient, since
the tests are not available at CMake time. Additional test properties may be
assigned to the set of tests as a whole using the ``PROPERTIES`` option. If
more fine-grained test control is needed, custom content may be provided
through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`
directory property. The set of discovered tests is made accessible to such a
script via the ``<target>_TESTS`` variable.
The options are:
``target``
Specifies the Catch executable, which must be a known CMake executable
target. CMake will substitute the location of the built executable when
running the test.
``TEST_SPEC arg1...``
Specifies test cases, wildcarded test cases, tags and tag expressions to
pass to the Catch executable with the ``--list-test-names-only`` argument.
``EXTRA_ARGS arg1...``
Any extra arguments to pass on the command line to each test case.
``WORKING_DIRECTORY dir``
Specifies the directory in which to run the discovered test cases. If this
option is not provided, the current binary directory is used.
``TEST_PREFIX prefix``
Specifies a ``prefix`` to be prepended to the name of each discovered test
case. This can be useful when the same test executable is being used in
multiple calls to ``catch_discover_tests()`` but with different
``TEST_SPEC`` or ``EXTRA_ARGS``.
``TEST_SUFFIX suffix``
Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of
every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may
be specified.
``PROPERTIES name1 value1...``
Specifies additional properties to be set on all tests discovered by this
invocation of ``catch_discover_tests``.
``TEST_LIST var``
Make the list of tests available in the variable ``var``, rather than the
default ``<target>_TESTS``. This can be useful when the same test
executable is being used in multiple calls to ``catch_discover_tests()``.
Note that this variable is only available in CTest.
#]=======================================================================]
#------------------------------------------------------------------------------
function(catch_discover_tests TARGET)
cmake_parse_arguments(
""
""
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST"
"TEST_SPEC;EXTRA_ARGS;PROPERTIES"
${ARGN}
)
if(NOT _WORKING_DIRECTORY)
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
endif()
if(NOT _TEST_LIST)
set(_TEST_LIST ${TARGET}_TESTS)
endif()
## Generate a unique name based on the extra arguments
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}")
string(SUBSTRING ${args_hash} 0 7 args_hash)
# Define rule to generate test list for aforementioned test executable
set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
get_property(crosscompiling_emulator
TARGET ${TARGET}
PROPERTY CROSSCOMPILING_EMULATOR
)
add_custom_command(
TARGET ${TARGET} POST_BUILD
BYPRODUCTS "${ctest_tests_file}"
COMMAND "${CMAKE_COMMAND}"
-D "TEST_TARGET=${TARGET}"
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
-D "TEST_SPEC=${_TEST_SPEC}"
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
-D "TEST_PROPERTIES=${_PROPERTIES}"
-D "TEST_PREFIX=${_TEST_PREFIX}"
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
-D "TEST_LIST=${_TEST_LIST}"
-D "CTEST_FILE=${ctest_tests_file}"
-P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
VERBATIM
)
file(WRITE "${ctest_include_file}"
"if(EXISTS \"${ctest_tests_file}\")\n"
" include(\"${ctest_tests_file}\")\n"
"else()\n"
" add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
"endif()\n"
)
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0")
# Add discovered tests to directory TEST_INCLUDE_FILES
set_property(DIRECTORY
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
)
else()
# Add discovered tests as directory TEST_INCLUDE_FILE if possible
get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
if (NOT ${test_include_file_set})
set_property(DIRECTORY
PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
)
else()
message(FATAL_ERROR
"Cannot set more than one TEST_INCLUDE_FILE"
)
endif()
endif()
endfunction()
###############################################################################
set(_CATCH_DISCOVER_TESTS_SCRIPT
${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake
)

View File

@@ -0,0 +1,77 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
set(prefix "${TEST_PREFIX}")
set(suffix "${TEST_SUFFIX}")
set(spec ${TEST_SPEC})
set(extra_args ${TEST_EXTRA_ARGS})
set(properties ${TEST_PROPERTIES})
set(script)
set(suite)
set(tests)
function(add_command NAME)
set(_args "")
foreach(_arg ${ARGN})
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
else()
set(_args "${_args} ${_arg}")
endif()
endforeach()
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
endfunction()
# Run test executable to get list of available tests
if(NOT EXISTS "${TEST_EXECUTABLE}")
message(FATAL_ERROR
"Specified test executable '${TEST_EXECUTABLE}' does not exist"
)
endif()
execute_process(
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only
OUTPUT_VARIABLE output
RESULT_VARIABLE result
)
# Catch --list-test-names-only reports the number of tests, so 0 is... surprising
if(${result} EQUAL 0)
message(WARNING
"Test executable '${TEST_EXECUTABLE}' contains no tests!\n"
)
elseif(${result} LESS 0)
message(FATAL_ERROR
"Error running test executable '${TEST_EXECUTABLE}':\n"
" Result: ${result}\n"
" Output: ${output}\n"
)
endif()
string(REPLACE "\n" ";" output "${output}")
# Parse output
foreach(line ${output})
# Test name; strip spaces to get just the name...
string(REGEX REPLACE "^ +" "" test "${line}")
# ...and add to script
add_command(add_test
"${prefix}${test}${suffix}"
${TEST_EXECUTOR}
"${TEST_EXECUTABLE}"
"${test}"
${extra_args}
)
add_command(set_tests_properties
"${prefix}${test}${suffix}"
PROPERTIES
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
${properties}
)
list(APPEND tests "${prefix}${test}${suffix}")
endforeach()
# Create a list of all discovered tests, which users may use to e.g. set
# properties on the tests
add_command(set ${TEST_LIST} ${tests})
# Write CTest script
file(WRITE "${CTEST_FILE}" "${script}")

View File

@@ -0,0 +1,542 @@
# Copyright (c) 2013-2017, Ruslan Baratov
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# This is a gate file to Hunter package manager.
# Include this file using `include` command and add package you need, example:
#
# cmake_minimum_required(VERSION 3.0)
#
# include("cmake/HunterGate.cmake")
# HunterGate(
# URL "https://github.com/path/to/hunter/archive.tar.gz"
# SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d"
# )
#
# project(MyProject)
#
# hunter_add_package(Foo)
# hunter_add_package(Boo COMPONENTS Bar Baz)
#
# Projects:
# * https://github.com/hunter-packages/gate/
# * https://github.com/ruslo/hunter
option(HUNTER_ENABLED "Enable Hunter package manager support" ON)
if(HUNTER_ENABLED)
if(CMAKE_VERSION VERSION_LESS "3.0")
message(FATAL_ERROR "At least CMake version 3.0 required for hunter dependency management."
" Update CMake or set HUNTER_ENABLED to OFF.")
endif()
endif()
include(CMakeParseArguments) # cmake_parse_arguments
option(HUNTER_STATUS_PRINT "Print working status" ON)
option(HUNTER_STATUS_DEBUG "Print a lot info" OFF)
set(HUNTER_WIKI "https://github.com/ruslo/hunter/wiki")
function(hunter_gate_status_print)
foreach(print_message ${ARGV})
if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG)
message(STATUS "[hunter] ${print_message}")
endif()
endforeach()
endfunction()
function(hunter_gate_status_debug)
foreach(print_message ${ARGV})
if(HUNTER_STATUS_DEBUG)
string(TIMESTAMP timestamp)
message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}")
endif()
endforeach()
endfunction()
function(hunter_gate_wiki wiki_page)
message("------------------------------ WIKI -------------------------------")
message(" ${HUNTER_WIKI}/${wiki_page}")
message("-------------------------------------------------------------------")
message("")
message(FATAL_ERROR "")
endfunction()
function(hunter_gate_internal_error)
message("")
foreach(print_message ${ARGV})
message("[hunter ** INTERNAL **] ${print_message}")
endforeach()
message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
message("")
hunter_gate_wiki("error.internal")
endfunction()
function(hunter_gate_fatal_error)
cmake_parse_arguments(hunter "" "WIKI" "" "${ARGV}")
string(COMPARE EQUAL "${hunter_WIKI}" "" have_no_wiki)
if(have_no_wiki)
hunter_gate_internal_error("Expected wiki")
endif()
message("")
foreach(x ${hunter_UNPARSED_ARGUMENTS})
message("[hunter ** FATAL ERROR **] ${x}")
endforeach()
message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
message("")
hunter_gate_wiki("${hunter_WIKI}")
endfunction()
function(hunter_gate_user_error)
hunter_gate_fatal_error(${ARGV} WIKI "error.incorrect.input.data")
endfunction()
function(hunter_gate_self root version sha1 result)
string(COMPARE EQUAL "${root}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("root is empty")
endif()
string(COMPARE EQUAL "${version}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("version is empty")
endif()
string(COMPARE EQUAL "${sha1}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("sha1 is empty")
endif()
string(SUBSTRING "${sha1}" 0 7 archive_id)
if(EXISTS "${root}/cmake/Hunter")
set(hunter_self "${root}")
else()
set(
hunter_self
"${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked"
)
endif()
set("${result}" "${hunter_self}" PARENT_SCOPE)
endfunction()
# Set HUNTER_GATE_ROOT cmake variable to suitable value.
function(hunter_gate_detect_root)
# Check CMake variable
string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty)
if(not_empty)
set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE)
hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable")
return()
endif()
# Check environment variable
string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty)
if(not_empty)
set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE)
hunter_gate_status_debug("HUNTER_ROOT detected by environment variable")
return()
endif()
# Check HOME environment variable
string(COMPARE NOTEQUAL "$ENV{HOME}" "" result)
if(result)
set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE)
hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable")
return()
endif()
# Check SYSTEMDRIVE and USERPROFILE environment variable (windows only)
if(WIN32)
string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result)
if(result)
set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE)
hunter_gate_status_debug(
"HUNTER_ROOT set using SYSTEMDRIVE environment variable"
)
return()
endif()
string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result)
if(result)
set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE)
hunter_gate_status_debug(
"HUNTER_ROOT set using USERPROFILE environment variable"
)
return()
endif()
endif()
hunter_gate_fatal_error(
"Can't detect HUNTER_ROOT"
WIKI "error.detect.hunter.root"
)
endfunction()
macro(hunter_gate_lock dir)
if(NOT HUNTER_SKIP_LOCK)
if("${CMAKE_VERSION}" VERSION_LESS "3.2")
hunter_gate_fatal_error(
"Can't lock, upgrade to CMake 3.2 or use HUNTER_SKIP_LOCK"
WIKI "error.can.not.lock"
)
endif()
hunter_gate_status_debug("Locking directory: ${dir}")
file(LOCK "${dir}" DIRECTORY GUARD FUNCTION)
hunter_gate_status_debug("Lock done")
endif()
endmacro()
function(hunter_gate_download dir)
string(
COMPARE
NOTEQUAL
"$ENV{HUNTER_DISABLE_AUTOINSTALL}"
""
disable_autoinstall
)
if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL)
hunter_gate_fatal_error(
"Hunter not found in '${dir}'"
"Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'"
"Settings:"
" HUNTER_ROOT: ${HUNTER_GATE_ROOT}"
" HUNTER_SHA1: ${HUNTER_GATE_SHA1}"
WIKI "error.run.install"
)
endif()
string(COMPARE EQUAL "${dir}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("Empty 'dir' argument")
endif()
string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("HUNTER_GATE_SHA1 empty")
endif()
string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("HUNTER_GATE_URL empty")
endif()
set(done_location "${dir}/DONE")
set(sha1_location "${dir}/SHA1")
set(build_dir "${dir}/Build")
set(cmakelists "${dir}/CMakeLists.txt")
hunter_gate_lock("${dir}")
if(EXISTS "${done_location}")
# while waiting for lock other instance can do all the job
hunter_gate_status_debug("File '${done_location}' found, skip install")
return()
endif()
file(REMOVE_RECURSE "${build_dir}")
file(REMOVE_RECURSE "${cmakelists}")
file(MAKE_DIRECTORY "${build_dir}") # check directory permissions
# Disabling languages speeds up a little bit, reduces noise in the output
# and avoids path too long windows error
file(
WRITE
"${cmakelists}"
"cmake_minimum_required(VERSION 3.0)\n"
"project(HunterDownload LANGUAGES NONE)\n"
"include(ExternalProject)\n"
"ExternalProject_Add(\n"
" Hunter\n"
" URL\n"
" \"${HUNTER_GATE_URL}\"\n"
" URL_HASH\n"
" SHA1=${HUNTER_GATE_SHA1}\n"
" DOWNLOAD_DIR\n"
" \"${dir}\"\n"
" TLS_VERIFY\n"
" ON\n"
" SOURCE_DIR\n"
" \"${dir}/Unpacked\"\n"
" CONFIGURE_COMMAND\n"
" \"\"\n"
" BUILD_COMMAND\n"
" \"\"\n"
" INSTALL_COMMAND\n"
" \"\"\n"
")\n"
)
if(HUNTER_STATUS_DEBUG)
set(logging_params "")
else()
set(logging_params OUTPUT_QUIET)
endif()
hunter_gate_status_debug("Run generate")
# Need to add toolchain file too.
# Otherwise on Visual Studio + MDD this will fail with error:
# "Could not find an appropriate version of the Windows 10 SDK installed on this machine"
if(EXISTS "${CMAKE_TOOLCHAIN_FILE}")
get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE)
set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}")
else()
# 'toolchain_arg' can't be empty
set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=")
endif()
string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make)
if(no_make)
set(make_arg "")
else()
# Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM
set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}")
endif()
execute_process(
COMMAND
"${CMAKE_COMMAND}"
"-H${dir}"
"-B${build_dir}"
"-G${CMAKE_GENERATOR}"
"${toolchain_arg}"
${make_arg}
WORKING_DIRECTORY "${dir}"
RESULT_VARIABLE download_result
${logging_params}
)
if(NOT download_result EQUAL 0)
hunter_gate_internal_error("Configure project failed")
endif()
hunter_gate_status_print(
"Initializing Hunter workspace (${HUNTER_GATE_SHA1})"
" ${HUNTER_GATE_URL}"
" -> ${dir}"
)
execute_process(
COMMAND "${CMAKE_COMMAND}" --build "${build_dir}"
WORKING_DIRECTORY "${dir}"
RESULT_VARIABLE download_result
${logging_params}
)
if(NOT download_result EQUAL 0)
hunter_gate_internal_error("Build project failed")
endif()
file(REMOVE_RECURSE "${build_dir}")
file(REMOVE_RECURSE "${cmakelists}")
file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}")
file(WRITE "${done_location}" "DONE")
hunter_gate_status_debug("Finished")
endfunction()
# Must be a macro so master file 'cmake/Hunter' can
# apply all variables easily just by 'include' command
# (otherwise PARENT_SCOPE magic needed)
macro(HunterGate)
if(HUNTER_GATE_DONE)
# variable HUNTER_GATE_DONE set explicitly for external project
# (see `hunter_download`)
set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
endif()
# First HunterGate command will init Hunter, others will be ignored
get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET)
if(NOT HUNTER_ENABLED)
# Empty function to avoid error "unknown function"
function(hunter_add_package)
endfunction()
set(
_hunter_gate_disabled_mode_dir
"${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode"
)
if(EXISTS "${_hunter_gate_disabled_mode_dir}")
hunter_gate_status_debug(
"Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}"
)
list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}")
endif()
elseif(_hunter_gate_done)
hunter_gate_status_debug("Secondary HunterGate (use old settings)")
hunter_gate_self(
"${HUNTER_CACHED_ROOT}"
"${HUNTER_VERSION}"
"${HUNTER_SHA1}"
_hunter_self
)
include("${_hunter_self}/cmake/Hunter")
else()
set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_LIST_DIR}")
string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name)
if(_have_project_name)
hunter_gate_fatal_error(
"Please set HunterGate *before* 'project' command. "
"Detected project: ${PROJECT_NAME}"
WIKI "error.huntergate.before.project"
)
endif()
cmake_parse_arguments(
HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV}
)
string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1)
string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url)
string(
COMPARE
NOTEQUAL
"${HUNTER_GATE_UNPARSED_ARGUMENTS}"
""
_have_unparsed
)
string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global)
string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath)
if(_have_unparsed)
hunter_gate_user_error(
"HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}"
)
endif()
if(_empty_sha1)
hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory")
endif()
if(_empty_url)
hunter_gate_user_error("URL suboption of HunterGate is mandatory")
endif()
if(_have_global)
if(HUNTER_GATE_LOCAL)
hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)")
endif()
if(_have_filepath)
hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)")
endif()
endif()
if(HUNTER_GATE_LOCAL)
if(_have_global)
hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)")
endif()
if(_have_filepath)
hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)")
endif()
endif()
if(_have_filepath)
if(_have_global)
hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)")
endif()
if(HUNTER_GATE_LOCAL)
hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)")
endif()
endif()
hunter_gate_detect_root() # set HUNTER_GATE_ROOT
# Beautify path, fix probable problems with windows path slashes
get_filename_component(
HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE
)
hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}")
if(NOT HUNTER_ALLOW_SPACES_IN_PATH)
string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces)
if(NOT _contain_spaces EQUAL -1)
hunter_gate_fatal_error(
"HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces."
"Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error"
"(Use at your own risk!)"
WIKI "error.spaces.in.hunter.root"
)
endif()
endif()
string(
REGEX
MATCH
"[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*"
HUNTER_GATE_VERSION
"${HUNTER_GATE_URL}"
)
string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty)
if(_is_empty)
set(HUNTER_GATE_VERSION "unknown")
endif()
hunter_gate_self(
"${HUNTER_GATE_ROOT}"
"${HUNTER_GATE_VERSION}"
"${HUNTER_GATE_SHA1}"
_hunter_self
)
set(_master_location "${_hunter_self}/cmake/Hunter")
if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter")
# Hunter downloaded manually (e.g. by 'git clone')
set(_unused "xxxxxxxxxx")
set(HUNTER_GATE_SHA1 "${_unused}")
set(HUNTER_GATE_VERSION "${_unused}")
else()
get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE)
set(_done_location "${_archive_id_location}/DONE")
set(_sha1_location "${_archive_id_location}/SHA1")
# Check Hunter already downloaded by HunterGate
if(NOT EXISTS "${_done_location}")
hunter_gate_download("${_archive_id_location}")
endif()
if(NOT EXISTS "${_done_location}")
hunter_gate_internal_error("hunter_gate_download failed")
endif()
if(NOT EXISTS "${_sha1_location}")
hunter_gate_internal_error("${_sha1_location} not found")
endif()
file(READ "${_sha1_location}" _sha1_value)
string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal)
if(NOT _is_equal)
hunter_gate_internal_error(
"Short SHA1 collision:"
" ${_sha1_value} (from ${_sha1_location})"
" ${HUNTER_GATE_SHA1} (HunterGate)"
)
endif()
if(NOT EXISTS "${_master_location}")
hunter_gate_user_error(
"Master file not found:"
" ${_master_location}"
"try to update Hunter/HunterGate"
)
endif()
endif()
include("${_master_location}")
set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
endif()
endmacro()

View File

@@ -0,0 +1,185 @@
#==================================================================================================#
# supported macros #
# - TEST_CASE, #
# - SCENARIO, #
# - TEST_CASE_METHOD, #
# - CATCH_TEST_CASE, #
# - CATCH_SCENARIO, #
# - CATCH_TEST_CASE_METHOD. #
# #
# Usage #
# 1. make sure this module is in the path or add this otherwise: #
# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") #
# 2. make sure that you've enabled testing option for the project by the call: #
# enable_testing() #
# 3. add the lines to the script for testing target (sample CMakeLists.txt): #
# project(testing_target) #
# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") #
# enable_testing() #
# #
# find_path(CATCH_INCLUDE_DIR "catch.hpp") #
# include_directories(${INCLUDE_DIRECTORIES} ${CATCH_INCLUDE_DIR}) #
# #
# file(GLOB SOURCE_FILES "*.cpp") #
# add_executable(${PROJECT_NAME} ${SOURCE_FILES}) #
# #
# include(ParseAndAddCatchTests) #
# ParseAndAddCatchTests(${PROJECT_NAME}) #
# #
# The following variables affect the behavior of the script: #
# #
# PARSE_CATCH_TESTS_VERBOSE (Default OFF) #
# -- enables debug messages #
# PARSE_CATCH_TESTS_NO_HIDDEN_TESTS (Default OFF) #
# -- excludes tests marked with [!hide], [.] or [.foo] tags #
# PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME (Default ON) #
# -- adds fixture class name to the test name #
# PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME (Default ON) #
# -- adds cmake target name to the test name #
# PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) #
# -- causes CMake to rerun when file with tests changes so that new tests will be discovered #
# #
#==================================================================================================#
cmake_minimum_required(VERSION 2.8.8)
option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF)
option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF)
option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the test name" ON)
option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON)
option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF)
function(PrintDebugMessage)
if(PARSE_CATCH_TESTS_VERBOSE)
message(STATUS "ParseAndAddCatchTests: ${ARGV}")
endif()
endfunction()
# This removes the contents between
# - block comments (i.e. /* ... */)
# - full line comments (i.e. // ... )
# contents have been read into '${CppCode}'.
# !keep partial line comments
function(RemoveComments CppCode)
string(ASCII 2 CMakeBeginBlockComment)
string(ASCII 3 CMakeEndBlockComment)
string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}")
string(REGEX REPLACE "\\*/" "${CMakeEndBlockComment}" ${CppCode} "${${CppCode}}")
string(REGEX REPLACE "${CMakeBeginBlockComment}[^${CMakeEndBlockComment}]*${CMakeEndBlockComment}" "" ${CppCode} "${${CppCode}}")
string(REGEX REPLACE "\n[ \t]*//+[^\n]+" "\n" ${CppCode} "${${CppCode}}")
set(${CppCode} "${${CppCode}}" PARENT_SCOPE)
endfunction()
# Worker function
function(ParseFile SourceFile TestTarget)
# According to CMake docs EXISTS behavior is well-defined only for full paths.
get_filename_component(SourceFile ${SourceFile} ABSOLUTE)
if(NOT EXISTS ${SourceFile})
message(WARNING "Cannot find source file: ${SourceFile}")
return()
endif()
PrintDebugMessage("parsing ${SourceFile}")
file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME)
# Remove block and fullline comments
RemoveComments(Contents)
# Find definition of test names
string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^\)]+\\)+[ \t\n]*{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}")
if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests)
PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property")
set_property(
DIRECTORY
APPEND
PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile}
)
endif()
foreach(TestName ${Tests})
# Strip newlines
string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}")
# Get test type and fixture if applicable
string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}")
string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}")
string(REPLACE "${TestType}(" "" TestFixture "${TestTypeAndFixture}")
# Get string parts of test definition
string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}")
# Strip wrapping quotation marks
string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}")
string(REPLACE "\";\"" ";" TestStrings "${TestStrings}")
# Validate that a test name and tags have been provided
list(LENGTH TestStrings TestStringsLength)
if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1)
message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}")
endif()
# Assign name and tags
list(GET TestStrings 0 Name)
if("${TestType}" STREQUAL "SCENARIO")
set(Name "Scenario: ${Name}")
endif()
if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND TestFixture)
set(CTestName "${TestFixture}:${Name}")
else()
set(CTestName "${Name}")
endif()
if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME)
set(CTestName "${TestTarget}:${CTestName}")
endif()
# add target to labels to enable running all tests added from this target
set(Labels ${TestTarget})
if(TestStringsLength EQUAL 2)
list(GET TestStrings 1 Tags)
string(TOLOWER "${Tags}" Tags)
# remove target from labels if the test is hidden
if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*")
list(REMOVE_ITEM Labels ${TestTarget})
endif()
string(REPLACE "]" ";" Tags "${Tags}")
string(REPLACE "[" "" Tags "${Tags}")
endif()
list(APPEND Labels ${Tags})
list(FIND Labels "!hide" IndexOfHideLabel)
set(HiddenTagFound OFF)
foreach(label ${Labels})
string(REGEX MATCH "^!hide|^\\." result ${label})
if(result)
set(HiddenTagFound ON)
break()
endif(result)
endforeach(label)
if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound})
PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label")
else()
PrintDebugMessage("Adding test \"${CTestName}\"")
if(Labels)
PrintDebugMessage("Setting labels to ${Labels}")
endif()
# Add the test and set its properties
add_test(NAME "\"${CTestName}\"" COMMAND ${TestTarget} ${Name} ${AdditionalCatchParameters})
set_tests_properties("\"${CTestName}\"" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran"
LABELS "${Labels}")
endif()
endforeach()
endfunction()
# entry point
function(ParseAndAddCatchTests TestTarget)
PrintDebugMessage("Started parsing ${TestTarget}")
get_target_property(SourceFiles ${TestTarget} SOURCES)
PrintDebugMessage("Found the following sources: ${SourceFiles}")
foreach(SourceFile ${SourceFiles})
ParseFile(${SourceFile} ${TestTarget})
endforeach()
PrintDebugMessage("Finished parsing ${TestTarget}")
endfunction()

View File

@@ -0,0 +1,36 @@
#include <iostream>
#include <cstdlib>
#include <vector>
#include <string>
#include <catch2/catch.hpp>
#include <sqlite_modern_cpp.h>
using namespace sqlite;
using namespace std;
TEST_CASE("Blob does work", "[blob]") {
database db(":memory:");
db << "CREATE TABLE person (name TEXT, numbers BLOB);";
db << "INSERT INTO person VALUES (?, ?)" << "bob" << vector<int> { 1, 2, 3, 4};
db << "INSERT INTO person VALUES (?, ?)" << "jack" << vector<char> { '1', '2', '3', '4'};
db << "INSERT INTO person VALUES (?, ?)" << "sara" << vector<double> { 1.0, 2.0, 3.0, 4.0};
vector<int> numbers_bob;
db << "SELECT numbers from person where name = ?;" << "bob" >> numbers_bob;
REQUIRE(numbers_bob.size() == 4);
REQUIRE((numbers_bob[0] == 1 && numbers_bob[1] == 2 && numbers_bob[2] == 3 && numbers_bob[3] == 4));
vector<char> numbers_jack;
db << "SELECT numbers from person where name = ?;" << "jack" >> numbers_jack;
REQUIRE(numbers_jack.size() == 4);
REQUIRE((numbers_jack[0] == '1' && numbers_jack[1] == '2' && numbers_jack[2] == '3' && numbers_jack[3] == '4'));
vector<double> numbers_sara;
db << "SELECT numbers from person where name = ?;" << "sara" >> numbers_sara;
REQUIRE(numbers_sara.size() == 4);
REQUIRE((numbers_sara[0] == 1.0 && numbers_sara[1] == 2.0 && numbers_sara[2] == 3.0 && numbers_sara[3] == 4.0));
}

View File

@@ -0,0 +1,63 @@
#include <iostream>
#include <iomanip>
#include <string>
#include <memory>
#include <stdexcept>
#include <sqlite_modern_cpp.h>
#include <sqlite_modern_cpp/log.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
struct TrackErrors {
TrackErrors()
: constraint_called{false}, primarykey_called{false}
{
error_log(
[this](errors::constraint) {
constraint_called = true;
},
[this](errors::constraint_primarykey e) {
primarykey_called = true;
}
// We are not registering the unique key constraint:
// For a unique key error the first handler (errors::constraint) will be called instead.
);
}
bool constraint_called;
bool primarykey_called;
/* bool unique_called; */
};
// Run before main, before any other sqlite function.
static TrackErrors track;
TEST_CASE("error_log works", "[log]") {
database db(":memory:");
db << "CREATE TABLE person (id integer primary key not null, name TEXT unique);";
SECTION("An extended error code gets called when registered") {
try {
db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack";
// triger primarykey constraint of 'id'
db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "bob";
} catch (const errors::constraint& e) { }
REQUIRE(track.primarykey_called == true);
REQUIRE(track.constraint_called == false);
track.primarykey_called = false;
}
SECTION("Parent gets called when the exact error code is not registered") {
try {
db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack";
// trigger unique constraint of 'name'
db << "INSERT INTO person (id,name) VALUES (?,?)" << 2 << "jack";
} catch (const errors::constraint& e) { }
REQUIRE(track.primarykey_called == false);
REQUIRE(track.constraint_called == true);
track.constraint_called = false;
}
}

View File

@@ -0,0 +1,23 @@
#include <iostream>
#include <string>
#include <memory>
#include <stdexcept>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
TEST_CASE("Prepared statement will not execute on exceptions", "[prepared_statements]") {
database db(":memory:");
db << "CREATE TABLE person (id integer primary key not null, name TEXT not null);";
try {
auto stmt = db << "INSERT INTO person (id,name) VALUES (?,?)";
throw 1;
} catch (int) { }
int count;
db << "select count(*) from person" >> count;
REQUIRE(count == 0);
}

View File

@@ -0,0 +1,31 @@
#include <iostream>
#include <string>
#include <memory>
#include <stdexcept>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
struct A {
~A() {
database db(":memory:");
db << "CREATE TABLE person (id integer primary key not null, name TEXT not null);";
try {
auto stmt = db << "INSERT INTO person (id,name) VALUES (?,?)";
throw 1;
} catch (int) {
}
}
};
TEST_CASE("Nested prepered statements wont execute", "[nested_prepared_statements]") {
#ifdef MODERN_SQLITE_UNCAUGHT_EXCEPTIONS_SUPPORT
try {
A a;
throw 1;
} catch(int) { }
#else
#endif
}

View File

@@ -0,0 +1,51 @@
#include <iostream>
#include <iomanip>
#include <string>
#include <memory>
#include <stdexcept>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
TEST_CASE("exceptions are thrown", "[exceptions]") {
database db(":memory:");
db << "CREATE TABLE person (id integer primary key not null, name TEXT);";
bool expception_thrown = false;
std::string get_sql_result;
#if SQLITE_VERSION_NUMBER >= 3014000
std::string expedted_sql = "INSERT INTO person (id,name) VALUES (1,'jack')";
#else
std::string expedted_sql = "INSERT INTO person (id,name) VALUES (?,?)";
#endif
SECTION("Parent exception works") {
try {
db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack";
// inserting again to produce error
db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack";
} catch (errors::constraint& e) {
expception_thrown = true;
get_sql_result = e.get_sql();
}
REQUIRE(expception_thrown == true);
REQUIRE(get_sql_result == expedted_sql);
}
SECTION("Extended exception works") {
try {
db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack";
// inserting again to produce error
db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack";
} catch (errors::constraint_primarykey& e) {
expception_thrown = true;
get_sql_result = e.get_sql();
}
REQUIRE(expception_thrown == true);
REQUIRE(get_sql_result == expedted_sql);
}
}

View File

@@ -0,0 +1,80 @@
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <sqlite_modern_cpp.h>
#include <sys/types.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
struct TmpFile {
string fname;
TmpFile(): fname("./flags.db") { }
~TmpFile() { remove(fname.c_str()); }
};
#ifdef _WIN32
#define OUR_UTF16 "UTF-16le"
#elif BYTE_ORDER == BIG_ENDIAN
#define OUR_UTF16 "UTF-16be"
#else
#define OUR_UTF16 "UTF-16le"
#endif
TEST_CASE("flags work", "[flags]") {
TmpFile file;
sqlite::sqlite_config cfg;
std::string enc;
SECTION("PRAGMA endcoding is UTF-8 for string literals") {
database db(":memory:", cfg);
db << "PRAGMA encoding;" >> enc;
REQUIRE(enc == "UTF-8");
}
SECTION("encoding is UTF-16 for u"" prefixed string literals") {
database db(u":memory:", cfg);
db << "PRAGMA encoding;" >> enc;
REQUIRE(enc == OUR_UTF16);
}
SECTION("we can set encoding to UTF-8 with flags") {
cfg.encoding = Encoding::UTF8;
database db(u":memory:", cfg);
db << "PRAGMA encoding;" >> enc;
REQUIRE(enc == "UTF-8");
}
SECTION("we can set encoding to UTF-16 with flags") {
cfg.encoding = Encoding::UTF16;
database db(u":memory:", cfg);
db << "PRAGMA encoding;" >> enc;
REQUIRE(enc == OUR_UTF16);
}
SECTION("we can set encoding to UTF-16 with flags for on disk databases") {
cfg.encoding = Encoding::UTF16;
database db(file.fname, cfg);
db << "PRAGMA encoding;" >> enc;
REQUIRE(enc == OUR_UTF16);
}
SECTION("READONLY flag works") {
{
database db(file.fname, cfg);
db << "CREATE TABLE foo (a string);";
db << "INSERT INTO foo VALUES (?)" << "hello";
}
cfg.flags = sqlite::OpenFlags::READONLY;
database db(file.fname, cfg);
string str;
db << "SELECT a FROM foo;" >> str;
REQUIRE(str == "hello");
bool failed = false;
try {
db << "INSERT INTO foo VALUES (?)" << "invalid";
} catch(errors::readonly&) {
failed = true;
}
REQUIRE(failed == true);
}
}

View File

@@ -0,0 +1,51 @@
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
int add_integers(int i, int j) {
return i+j;
}
TEST_CASE("sql functions work", "[functions]") {
database db(":memory:");
db.define("my_new_concat", [](std::string i, std::string j) {return i+j;});
db.define("my_new_concat", [](std::string i, std::string j, std::string k) {return i+j+k;});
db.define("add_integers", &add_integers);
std::string test1, test3;
int test2 = 0;
db << "select my_new_concat('Hello ','world!')" >> test1;
db << "select add_integers(1,1)" >> test2;
db << "select my_new_concat('a','b','c')" >> test3;
REQUIRE(test1 == "Hello world!");
REQUIRE(test2 == 2);
REQUIRE(test3 == "abc");
db.define("my_count", [](int &i, int) {++i;}, [](int &i) {return i;});
db.define("my_concat_aggregate", [](std::string &stored, std::string current) {stored += current;}, [](std::string &stored) {return stored;});
db << "create table countable(i, s)";
db << "insert into countable values(1, 'a')";
db << "insert into countable values(2, 'b')";
db << "insert into countable values(3, 'c')";
db << "select my_count(i) from countable" >> test2;
db << "select my_concat_aggregate(s) from countable order by i" >> test3;
REQUIRE(test2 == 3);
REQUIRE(test3 == "abc");
db.define("tgamma", [](double i) {return std::tgamma(i);});
db << "CREATE TABLE numbers (number INTEGER);";
for(auto i=0; i!=10; ++i)
db << "INSERT INTO numbers VALUES (?);" << i;
db << "SELECT number, tgamma(number+1) FROM numbers;" >> [](double number, double factorial) {
/* cout << number << "! = " << factorial << '\n'; */
};
}

View File

@@ -0,0 +1,39 @@
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
struct tbl_functor {
explicit tbl_functor(vector<pair<int, string> > &vec_) : vec(vec_) { }
void operator() ( int id, string name) {
vec.push_back(make_pair(id, move(name)));
}
vector<pair<int,string> > &vec;
};
TEST_CASE("functors work", "[functors]") {
database db(":memory:");
db << "CREATE TABLE tbl (id integer, name string);";
db << "INSERT INTO tbl VALUES (?, ?);" << 1 << "hello";
db << "INSERT INTO tbl VALUES (?, ?);" << 2 << "world";
vector<pair<int,string> > vec;
db << "select id,name from tbl;" >> tbl_functor(vec);
REQUIRE(vec.size() == 2);
vec.clear();
tbl_functor functor(vec);
db << "select id,name from tbl;" >> functor;
REQUIRE(vec.size() == 2);
REQUIRE(vec[0].first == 1);
REQUIRE(vec[0].second == "hello");
}

View File

@@ -0,0 +1,52 @@
#include<iostream>
#include<sqlite_modern_cpp.h>
#include<string>
#include<vector>
#include<catch2/catch.hpp>
using namespace sqlite;
using namespace std;
template<typename Target, typename... AttrTypes>
struct builder {
vector<Target> results;
void operator()(AttrTypes... args) {
results.emplace_back(std::forward<AttrTypes&&>(args)...);
};
};
struct user {
int age;
string name;
double weight;
user(int age, string name, double weight) : age(age), name(name), weight(weight) { }
static std::vector<user> all(sqlite::database& db) {
builder<user, int, std::string, double> person_builder;
db << "SELECT * FROM user;"
>> person_builder;
return std::move(person_builder.results); // move to avoid copying data ;-)
};
};
TEST_CASE("lvalue functors work", "[lvalue_functor]") {
database db(":memory:");
db <<
"create table if not exists user ("
" age int,"
" name text,"
" weight real"
");";
db << "insert into user (age,name,weight) values (?,?,?);" << 20 << u"chandler" << 83.25;
db << "insert into user (age,name,weight) values (?,?,?);" << 21 << u"monika" << 86.25;
db << "insert into user (age,name,weight) values (?,?,?);" << 22 << u"ross" << 88.25;
auto users = user::all(db);
REQUIRE(users.size() == 3);
}

View File

@@ -0,0 +1,31 @@
// Fixing https://github.com/SqliteModernCpp/sqlite_modern_cpp/issues/63
#include <iostream>
#include <cstdlib>
#include <sqlite_modern_cpp.h>
#include <memory>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
struct dbFront {
std::unique_ptr<database_binder> storedProcedure;
database db;
dbFront(): db(":memory:") {
db << "CREATE TABLE tbl (id integer, name string);";
// the temporary moved object should not run _execute() function on destruction.
storedProcedure = std::make_unique<database_binder>(
db << "INSERT INTO tbl VALUES (?, ?);"
);
}
};
TEST_CASE("database lifecycle", "move_ctor") {
bool failed = false;
try { dbFront dbf; }
catch(const sqlite_exception& e) { failed = true; }
catch(...) { failed = true; }
REQUIRE(failed == false);
}

View File

@@ -0,0 +1,20 @@
#include <iostream>
#include <cstdlib>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
TEST_CASE("binding named parameters works", "[named]") {
database db(":memory:");
db << "CREATE TABLE foo (a,b);";
int a = 1;
db << "INSERT INTO foo VALUES (:first,:second)" << named_parameter(":second", 2) << named_parameter(":first", a);
db << "SELECT b FROM foo WHERE a=?;" << 1 >> a;
REQUIRE(a == 2);
}

View File

@@ -0,0 +1,27 @@
#include <iostream>
#include <string>
#include <vector>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace std;
using namespace sqlite;
TEST_CASE("nullptr & unique_ptr", "[null_ptr_unique_ptr]") {
database db(":memory:");
db << "CREATE TABLE tbl (id integer,age integer, name string, img blob);";
db << "INSERT INTO tbl VALUES (?, ?, ?, ?);" << 1 << 24 << "bob" << vector<int> { 1, 2 , 3};
unique_ptr<string> ptr_null;
db << "INSERT INTO tbl VALUES (?, ?, ?, ?);" << 2 << nullptr << ptr_null << nullptr;
db << "select age,name,img from tbl where id = 1" >> [](unique_ptr<int> age_p, unique_ptr<string> name_p, unique_ptr<vector<int>> img_p) {
REQUIRE(age_p != nullptr);
REQUIRE(name_p != nullptr);
REQUIRE(img_p != nullptr);
};
db << "select age,name,img from tbl where id = 2" >> [](unique_ptr<int> age_p, unique_ptr<string> name_p, unique_ptr<vector<int>> img_p) {
REQUIRE(age_p == nullptr);
REQUIRE(name_p == nullptr);
REQUIRE(img_p == nullptr);
};
}

View File

@@ -0,0 +1,102 @@
#include <iostream>
#include <cstdlib>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
TEST_CASE("prepared statements work", "[prepared_statement]") {
database db(":memory:");
auto pps = db << "select ?"; // get a prepared parsed and ready statment
int test = 4;
pps << test; // set a bound var
pps >> test; // execute statement
REQUIRE(test == 4);
pps << 4; // bind a rvalue
pps++; // and execute
pps << 8 >> test;
REQUIRE(test == 8);
auto pps2 = db << "select 1,2"; // multiple extract test
pps2 >> [](int a, int b) {
REQUIRE(a == 1);
REQUIRE(b == 2);
};
auto pps3 = db << "select ?,?,?";
test = 2;
pps3 << 1 << test << 3 >> [](int a, int b, int c) {
REQUIRE(a == 1);
REQUIRE(b == 2);
REQUIRE(c == 3);
};
test = 1;
db << "select ?,?" << test << 5 >> test; // and mow everything together
REQUIRE(test == 1);
test = 2;
db << "select ?,?,?" << 1 << test << 3 >> [](int a, int b, int c) {
REQUIRE(a == 1);
REQUIRE(b == 2);
REQUIRE(c == 3);
};
db << "select ?" << test; // noVal
db << "select ?,?" << test << 1;
db << "select ?,?" << 1 << test;
db << "select ?,?" << 1 << 1;
db << "select ?,?" << test << test;
db << "select ?" << test >> test; // lVal
db << "select ?,?" << test << 1 >> test;
db << "select ?,?" << 1 << test >> test;
db << "select ?,?" << 1 << 1 >> test;
db << "select ?,?" << test << test >> test;
int q = 0;
test = 1;
db << "select ?" << test >> [&](int t) { q = t; }; // rVal
REQUIRE(q == 1);
db << "select ?,?" << test << 1 >> [&](int t, int p) { q = t + p; };
db << "select ?,?" << 1 << test >> [&](int t, int p) { q = t + p; };
db << "select ?,?" << 1 << 1 >> [&](int t, int p) { q = t + p; };
db << "select ?,?" << test << test >> [&](int t, int p) { q = t + p; };
db << "select ?,?,?" << test << 1 << test; // mix
db << "select ?,?,?" << 1 << test << 1;
db << "select ?,?,?" << 1 << 1 << test;
db << "select ?,?,?" << 1 << 1 << 1;
db << "select ?,?,?" << test << test << test;
{
auto pps4 = db << "select ?,?,?"; // reuse
(pps4 << test << 1 << test)++;
(pps4 << 1 << test << 1)++;
(pps4 << 1 << 1 << test)++;
(pps4 << 1 << 1 << 1)++;
(pps4 << test << test << test)++;
}
{
auto prep = db << "select ?";
prep << 5;
prep.execute();
prep << 6;
prep.execute();
}
}

View File

@@ -0,0 +1,64 @@
#define CATCH_CONFIG_MAIN
#include<iostream>
#include <catch2/catch.hpp>
#include <sqlite_modern_cpp.h>
using namespace sqlite;
using namespace std;
TEST_CASE("README Example Works", "[readme]") {
database db(":memory:");
db <<
"create table if not exists user ("
" _id integer primary key autoincrement not null,"
" age int,"
" name text,"
" weight real"
");";
db << "insert into user (age,name,weight) values (?,?,?);"
<< 20
<< u"bob"
<< 83.25;
int age = 22; float weight = 68.5; string name = "jack";
db << u"insert into user (age,name,weight) values (?,?,?);" // utf16 query string
<< age
<< name
<< weight;
REQUIRE(db.last_insert_rowid() != 0);
db << "select age,name,weight from user where age > ? ;"
<< 21
>> [&](int _age, string _name, double _weight) {
REQUIRE((_age == age && _name == name));
};
for(auto &&row : db << "select age,name,weight from user where age > ? ;" << 21) {
int _age;
string _name;
double _weight;
row >> _age >> _name >> _weight;
REQUIRE((_age == age && _name == name));
}
for(std::tuple<int, string, double> row : db << "select age,name,weight from user where age > ? ;" << 21) {
REQUIRE((std::get<int>(row) == age && std::get<string>(row) == name));
}
// selects the count(*) from user table
// note that you can extract a single culumn single row result only to : int,long,long,float,double,string,u16string
int count = 0;
db << "select count(*) from user" >> count;
REQUIRE(count == 2);
db << "select age, name from user where _id=1;" >> tie(age, name);
// this also works and the returned value will be automatically converted to string
string str_count;
db << "select count(*) from user" >> str_count;
REQUIRE(str_count == string{"2"});
}

View File

@@ -0,0 +1,27 @@
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
TEST_CASE("shared connections work fine", "[shared_connection]") {
database db(":memory:");
{
auto con = db.connection();
{
database db2(con);
int test = 0;
db2 << "select 1" >> test;
REQUIRE(test == 1);
}
int test = 0;
db << "select 1" >> test;
REQUIRE(test == 1);
}
}

View File

@@ -0,0 +1,36 @@
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
TEST_CASE("simple examples", "[examples]") {
database db(":memory:");
db << "CREATE TABLE foo (a integer, b string);\n";
db << "INSERT INTO foo VALUES (?, ?)" << 1 << "hello";
db << "INSERT INTO foo VALUES (?, ?)" << 2 << "world";
string str;
db << "SELECT b from FOO where a=?;" << 2L >> str;
REQUIRE(str == "world");
std::string sql("select 1+1");
long test = 0;
db << sql >> test;
REQUIRE(test == 2);
db << "UPDATE foo SET b=? WHERE a=?;" << "hi" << 1L;
db << "SELECT b FROM foo WHERE a=?;" << 1L >> str;
REQUIRE(str == "hi");
REQUIRE(db.rows_modified() == 1);
db << "UPDATE foo SET b=?;" << "hello world";
REQUIRE(db.rows_modified() == 2);
}

View File

@@ -0,0 +1,56 @@
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <catch.hpp>
#include <sqlite_modern_cpp/sqlcipher.h>
using namespace sqlite;
using namespace std;
struct TmpFile
{
string fname;
TmpFile(): fname("./sqlcipher.db") { }
~TmpFile() { remove(fname.c_str()); }
};
TEST_CASE("sqlcipher works", "[sqlcipher]") {
TmpFile file;
sqlcipher_config config;
{
config.key = "DebugKey";
sqlcipher_database db(file.fname, config);
db << "CREATE TABLE foo (a integer, b string);";
db << "INSERT INTO foo VALUES (?, ?)" << 1 << "hello";
db << "INSERT INTO foo VALUES (?, ?)" << 2 << "world";
string str;
db << "SELECT b from FOO where a=?;" << 2 >> str;
REQUIRE(str == "world");
}
bool failed = false;
try {
config.key = "DebugKey2";
sqlcipher_database db(file.fname, config);
db << "INSERT INTO foo VALUES (?, ?)" << 3 << "fail";
} catch(const errors::notadb&) {
failed = true;
// Expected, wrong key
}
REQUIRE(failed == true);
{
config.key = "DebugKey";
sqlcipher_database db(file.fname, config);
db.rekey("DebugKey2");
}
{
config.key = "DebugKey2";
sqlcipher_database db(file.fname, config);
db << "INSERT INTO foo VALUES (?, ?)" << 3 << "fail";
}
}

View File

@@ -0,0 +1,31 @@
#include <iostream>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
TEST_CASE("std::optional works", "[optional]") {
database db(":memory:");
db << "drop table if exists test";
db <<
"create table if not exists test ("
" id integer primary key,"
" val int"
");";
db << "insert into test(id,val) values(?,?)" << 1 << 5;
db << "select id,val from test" >> [&](long long, sqlite::optional<int> val) {
REQUIRE(val);
};
db << "delete from test where id = 1";
db << "insert into test(id,val) values(?,?)" << 1 << nullptr;
db << "select id,val from test" >> [&](long long, sqlite::optional<int> val) {
REQUIRE(!val);
};
}
#endif

View File

@@ -0,0 +1,23 @@
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
#ifdef MODERN_SQLITE_STRINGVIEW_SUPPORT
#include <string_view>
using namespace sqlite;
TEST_CASE("std::string_view works", "[string_view]") {
database db(":memory:");
db << "CREATE TABLE foo (a integer, b string);\n";
const std::string_view test1 = "null terminated string view";
db << "INSERT INTO foo VALUES (?, ?)" << 1 << test1;
std::string str;
db << "SELECT b from FOO where a=?;" << 1 >> str;
REQUIRE(test1 == str);
const char s[] = "hello world";
std::string_view test2(&s[0], 2);
db << "INSERT INTO foo VALUES (?,?)" << 2 << test2;
db << "SELECT b from FOO where a=?" << 2 >> str;
REQUIRE(str == "he");
}
#endif

View File

@@ -0,0 +1,80 @@
#include <iostream>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using std::string;
struct TmpFile {
string fname;
TmpFile(): fname("./trycatchblocks.db") {}
~TmpFile() { remove(fname.c_str()); }
};
class DBInterface {
database db;
public:
DBInterface( const string& fileName ) : db( fileName ) { }
void LogRequest( const string& username, const string& ip, const string& request )
{
try {
auto timestamp = std::to_string( time( nullptr ) );
db <<
"create table if not exists log_request ("
" _id integer primary key autoincrement not null,"
" username text,"
" timestamp text,"
" ip text,"
" request text"
");";
db << "INSERT INTO log_request (username, timestamp, ip, request) VALUES (?,?,?,?);"
<< username
<< timestamp
<< ip
<< request;
} catch ( const std::exception& e ) {
std::cout << e.what() << std::endl;
}
}
bool TestData( void ) {
try {
string username, timestamp, ip, request;
db << "select username, timestamp, ip, request from log_request where username = ?"
<< "test"
>> std::tie(username, timestamp, ip, request);
if ( username == "test" && ip == "127.0.0.1" && request == "hello world" ) {
return true;
}
} catch ( const std::exception& e ) {
std::cout << e.what() << std::endl;
}
return false;
}
};
TEST_CASE("try catch blocks", "[trycatchblocks]") {
// --------------------------------------------------------------------------
// -- Test if writing to disk works properly from within a catch block.
// --------------------------------------------------------------------------
try {
throw "hello";
}
catch ( ... ) {
TmpFile tmpF;
DBInterface interf(tmpF.fname);
interf.LogRequest( "test", "127.0.0.1", "hello world" );
REQUIRE(interf.TestData() == true);
}
}

View File

@@ -0,0 +1,57 @@
#include <iostream>
#include <cstdlib>
#include <sqlite_modern_cpp.h>
#include <catch2/catch.hpp>
using namespace sqlite;
using namespace std;
#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT
TEST_CASE("std::variant works", "[variant]") {
database db(":memory:");
db << "CREATE TABLE foo (a);";
std::variant<std::string, int, std::optional<float>> v;
v = 1;
db << "INSERT INTO foo VALUES (?)" << v;
v = "a";
db << "INSERT INTO foo VALUES (?)" << v;
db << "SELECT a FROM foo WHERE a=?;" << 1 >> v;
REQUIRE(v.index() == 1);
REQUIRE(std::get<1>(v) == 1);
db << "SELECT NULL" >> v;
REQUIRE(!std::get<2>(v));
db << "SELECT 0.0" >> v;
REQUIRE(std::get<2>(v));
}
TEST_CASE("std::monostate is a nullptr substitute", "[monostate]") {
database db(":memory:");
db << "CREATE TABLE foo (a);";
std::variant<std::monostate,std::string> v;
v=std::monostate();
db << "INSERT INTO foo VALUES (?)" << v;
db << "INSERT INTO foo VALUES (?)" << "This isn't a monostate!";
bool found_null = false,
found_string = false;
db << "SELECT * FROM foo"
>> [&](std::variant<std::monostate, std::string> z) {
if(z.index() == 0) {
found_null = true;
} else {
found_string = true;
}
};
REQUIRE((found_null && found_string));
db << "SELECT NULL" >> v;
REQUIRE(v.index() == 0);
}
#endif