Skip to content

Commit

Permalink
Extract common tests in soci_tests_common library
Browse files Browse the repository at this point in the history
This allows to compile them once, instead of doing it for every backend:
while this doesn't matter for the CI builds, recompiling common-tests.h
a dozen times enormously slowed down local builds using all backends.

Now it is compiled only once, as test-common.cpp, and all the other
tests (except for the "empty" one) just link with the resulting library.

Also extract some parts of this file into separate headers, that can be
included only by the tests that actually need them.

Note that the entire test-common.cpp probably ought to be split into
multiple files, to speed up its build too, but this can be done later.
  • Loading branch information
vadz committed Oct 23, 2024
1 parent 55f3988 commit b0ecbca
Show file tree
Hide file tree
Showing 28 changed files with 672 additions and 676 deletions.
2 changes: 2 additions & 0 deletions cmake/SociBackend.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ macro(soci_backend_test)
target_link_libraries(${TEST_TARGET}
${SOCI_CORE_DEPS_LIBS}
${THIS_TEST_DEPENDS_LIBRARIES}
soci_tests_common
soci_core
soci_${BACKENDL})

Expand All @@ -365,6 +366,7 @@ macro(soci_backend_test)
target_link_libraries(${TEST_TARGET_STATIC}
${SOCI_CORE_DEPS_LIBS}
${THIS_TEST_DEPENDS_LIBRARIES}
soci_tests_common
soci_${BACKENDL}_static
soci_core_static)

Expand Down
10 changes: 8 additions & 2 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ include_directories(
${SOCI_SOURCE_DIR}/include/private
${CMAKE_CURRENT_SOURCE_DIR})

set(SOCI_TESTS_COMMON
${CMAKE_CURRENT_SOURCE_DIR}/common-tests.h)
# This library is used by all test executables.
# It's always static for simplicity, even when using shared SOCI libraries.
add_library(soci_tests_common STATIC
common/test-main.cpp
common/test-common.cpp
test-assert.h
test-context.h
test-myint.h)

add_subdirectory(empty)
add_subdirectory(db2)
Expand Down
221 changes: 6 additions & 215 deletions tests/common-tests.h → tests/common/test-common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
// http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef SOCI_COMMON_TESTS_H_INCLUDED
#define SOCI_COMMON_TESTS_H_INCLUDED

#include "soci/soci.h"

#ifdef SOCI_HAVE_BOOST
Expand All @@ -28,7 +25,6 @@

#include "soci/callbacks.h"

#define CATCH_CONFIG_RUNNER
#include <catch.hpp>

#if defined(_MSC_VER) && (_MSC_VER < 1500)
Expand All @@ -37,7 +33,6 @@
#endif

#include <algorithm>
#include <cassert>
#include <clocale>
#include <cstdint>
#include <cstdlib>
Expand All @@ -51,6 +46,12 @@

#include "soci-mktime.h"

#include "test-assert.h"
#include "test-context.h"
#include "test-myint.h"

bool soci_use_common_tests = false;

// Although SQL standard mandates right padding CHAR(N) values to their length
// with spaces, some backends don't confirm to it:
//
Expand Down Expand Up @@ -120,18 +121,6 @@ class PhonebookEntry3
std::string phone_;
};

// user-defined object for test26 and test28
class MyInt
{
public:
MyInt() : i_() {}
MyInt(int i) : i_(i) {}
void set(int i) { i_ = i; }
int get() const { return i_; }
private:
int i_;
};

// user-defined object for the "vector of custom type objects" tests.
class MyOptionalString
{
Expand Down Expand Up @@ -175,26 +164,6 @@ std::ostream& operator<<(std::ostream& ostr, const std::vector<MyOptionalString>
namespace soci
{

// basic type conversion for user-defined type with single base value
template<> struct type_conversion<MyInt>
{
typedef int base_type;

static void from_base(int i, indicator ind, MyInt &mi)
{
if (ind == i_ok)
{
mi.set(i);
}
}

static void to_base(MyInt const &mi, int &i, indicator &ind)
{
i = mi.get();
ind = i_ok;
}
};

// basic type conversion for string based user-defined type which can be null
template<> struct type_conversion<MyOptionalString>
{
Expand Down Expand Up @@ -296,158 +265,6 @@ namespace soci
namespace tests
{

// This is a singleton class, at any given time there is at most one test
// context alive and common_tests fixture class uses it.
class test_context_base
{
public:
test_context_base(backend_factory const &backEnd,
std::string const &connectString)
: backEndFactory_(backEnd),
connectString_(connectString)
{
// This can't be a CHECK() because the test context is constructed
// outside of any test.
assert(!the_test_context_);

the_test_context_ = this;

// To allow running tests in non-default ("C") locale, the following
// environment variable can be set and then the current default locale
// (which can itself be changed by setting LC_ALL environment variable)
// will then be used.
if (std::getenv("SOCI_TEST_USE_LC_ALL"))
std::setlocale(LC_ALL, "");
}

static test_context_base const& get_instance()
{
REQUIRE(the_test_context_);

return *the_test_context_;
}

backend_factory const & get_backend_factory() const
{
return backEndFactory_;
}

std::string get_connect_string() const
{
return connectString_;
}

virtual std::string to_date_time(std::string const &dateTime) const = 0;

virtual table_creator_base* table_creator_1(session&) const = 0;
virtual table_creator_base* table_creator_2(session&) const = 0;
virtual table_creator_base* table_creator_3(session&) const = 0;
virtual table_creator_base* table_creator_4(session&) const = 0;

// Override this to return the table creator for a simple table containing
// an integer "id" column and CLOB "s" one.
//
// Returns null by default to indicate that CLOB is not supported.
virtual table_creator_base* table_creator_clob(session&) const { return NULL; }

// Override this to return the table creator for a simple table containing
// an integer "id" column and BLOB "b" one.
//
// Returns null by default to indicate that BLOB is not supported.
virtual table_creator_base* table_creator_blob(session&) const { return NULL; }

// Override this to return the table creator for a simple table containing
// an integer "id" column and XML "x" one.
//
// Returns null by default to indicate that XML is not supported.
virtual table_creator_base* table_creator_xml(session&) const { return NULL; }

// Override this to return the table creator for a simple table containing
// an identity integer "id" and a simple integer "val" columns.
//
// Returns null by default to indicate that identity is not supported.
virtual table_creator_base* table_creator_get_last_insert_id(session&) const { return NULL; }

// Return the casts that must be used to convert the between the database
// XML type and the query parameters.
//
// By default no special casts are done.
virtual std::string to_xml(std::string const& x) const { return x; }
virtual std::string from_xml(std::string const& x) const { return x; }

// Override this if the backend not only supports working with XML values
// (and so returns a non-null value from table_creator_xml()), but the
// database itself has real XML support instead of just allowing to store
// and retrieve XML as text. "Real" support means at least preventing the
// application from storing malformed XML in the database.
virtual bool has_real_xml_support() const { return false; }

// Override this if the backend doesn't handle floating point values
// correctly, i.e. writing a value and reading it back doesn't return
// *exactly* the same value.
virtual bool has_fp_bug() const { return false; }

// Override this if the backend wrongly returns CR LF when reading a string
// with just LFs from the database to strip the unwanted CRs.
virtual std::string fix_crlf_if_necessary(std::string const& s) const { return s; }

// Override this if the backend doesn't handle multiple active select
// statements at the same time, i.e. a result set must be entirely consumed
// before creating a new one (this is the case of MS SQL without MARS).
virtual bool has_multiple_select_bug() const { return false; }

// Override this if the backend may not have transactions support.
virtual bool has_transactions_support(session&) const { return true; }

// Override this if the backend silently truncates string values too long
// to fit by default.
virtual bool has_silent_truncate_bug(session&) const { return false; }

// Override this if the backend doesn't distinguish between empty and null
// strings (Oracle does this).
virtual bool treats_empty_strings_as_null() const { return false; }

// Override this if the backend does not store values bigger than INT64_MAX
// correctly. This can lead to an unexpected ordering of values as larger
// values might be stored as overflown and therefore negative integer.
virtual bool has_uint64_storage_bug() const { return false; }

// Override this if the backend truncates integer values bigger than INT64_MAX.
virtual bool truncates_uint64_to_int64() const { return false; }

// Override this to call commit() if it's necessary for the DDL statements
// to be taken into account (currently this is only the case for Firebird).
virtual void on_after_ddl(session&) const { }

// Put the database in SQL-complient mode for CHAR(N) values, return false
// if it's impossible, i.e. if the database doesn't behave correctly
// whatever we do.
virtual bool enable_std_char_padding(session&) const { return true; }

// Return the SQL expression giving the length of the specified string,
// i.e. "char_length(s)" in standard SQL but often "len(s)" or "length(s)"
// in practice and sometimes even worse (thanks Oracle).
virtual std::string sql_length(std::string const& s) const = 0;

virtual ~test_context_base()
{
the_test_context_ = NULL;
}

private:
backend_factory const &backEndFactory_;
std::string const connectString_;

static test_context_base* the_test_context_;

SOCI_NOT_COPYABLE(test_context_base)
};

// Currently all tests consist of just a single source file, so we can define
// this member here because this header is included exactly once.
tests::test_context_base* tests::test_context_base::the_test_context_ = NULL;


// Compare doubles for approximate equality. This has to be used everywhere
// where we write "3.14" (or "6.28") to the database as a string and then
// compare the value read back with the literal 3.14 floating point constant
Expand Down Expand Up @@ -483,30 +300,6 @@ inline bool are_doubles_approx_equal(double const a, double const b)
} while ( (void)0, 0 )


// Exact double comparison function. We need one, instead of writing "a == b",
// only in order to have some place to put the pragmas disabling gcc warnings.
inline bool
are_doubles_exactly_equal(double a, double b)
{
// Avoid g++ warnings: we do really want the exact equality here.
SOCI_GCC_WARNING_SUPPRESS(float-equal)

return a == b;

SOCI_GCC_WARNING_RESTORE(float-equal)
}

#define ASSERT_EQUAL_EXACT(a, b) \
do { \
if (!are_doubles_exactly_equal((a), (b))) { \
FAIL( "Exact equality check failed: " \
<< std::fixed \
<< std::setprecision(std::numeric_limits<double>::digits10 + 1) \
<< (a) << " != " << (b) ); \
} \
} while ( (void)0, 0 )


// Compare two floating point numbers either exactly or approximately depending
// on test_context::has_fp_bug() return value.
inline bool
Expand Down Expand Up @@ -6972,5 +6765,3 @@ TEST_CASE_METHOD(common_tests, "Failover", "[keep-alive][.]")
} // namespace tests

} // namespace soci

#endif // SOCI_COMMON_TESTS_H_INCLUDED
68 changes: 68 additions & 0 deletions tests/common/test-main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// Copyright (C) 2024 Vadim Zeitlin
// 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)
//

#define CATCH_CONFIG_RUNNER
#include <catch.hpp>

#include "test-context.h"

using namespace soci;

tests::test_context_base* tests::test_context_base::the_test_context_ = NULL;

int main(int argc, char** argv)
{
auto* const tc = tests::test_context_base::get_instance_pointer();
if (!tc)
{
std::cerr << "Internal error: each test must create its test context.\n";
return EXIT_FAILURE;
}

#ifdef _MSC_VER
// Redirect errors, unrecoverable problems, and assert() failures to STDERR,
// instead of debug message window.
// This hack is required to run assert()-driven tests by Buildbot.
// NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside.
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
#endif //_MSC_VER

std::string argFromCommandLine;
if (argc >= 2 && argv[1][0] != '-')
{
argFromCommandLine = argv[1];

// Replace the connect string with the process name to ensure that
// CATCH uses the correct name in its messages.
argv[1] = argv[0];

argc--;
argv++;
}

if ( !tc->initialize_connect_string(std::move(argFromCommandLine)) )
{
std::cerr << "usage: " << argv[0]
<< " <connection-string> [test-arguments...]\n";

auto const& dsn = tc->get_example_connection_string();
if ( !dsn.empty() )
{
std::cerr << "example: " << argv[0] << " \'" << dsn << "\'\n";
}

return EXIT_FAILURE;
}

if ( !tc->start_testing() )
{
return EXIT_FAILURE;
}

return Catch::Session().run(argc, argv);
}
2 changes: 1 addition & 1 deletion tests/db2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
soci_backend_test(
BACKEND DB2
DEPENDS DB2
SOURCE test-db2.cpp ${SOCI_TESTS_COMMON}
SOURCE test-db2.cpp
CONNSTR "DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;autocommit=off")
Loading

0 comments on commit b0ecbca

Please sign in to comment.