From efb65cb48def3d2d089e814bd3424c2064bb524d Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 25 Sep 2025 15:34:04 -0500 Subject: [PATCH 01/17] CXX-3320 migrate instance and logger to mongocxx::v1 --- .evergreen/scripts/test.sh | 20 +- .../include/mongocxx/v1/exception.hpp | 81 ++++++- src/mongocxx/include/mongocxx/v1/instance.hpp | 130 ++++++++++- .../include/mongocxx/v1/logger-fwd.hpp | 7 +- src/mongocxx/include/mongocxx/v1/logger.hpp | 76 ++++++- .../mongocxx/v_noabi/mongocxx/instance.hpp | 66 +----- .../mongocxx/v_noabi/mongocxx/logger-fwd.hpp | 17 +- .../mongocxx/v_noabi/mongocxx/logger.hpp | 73 +------ src/mongocxx/lib/mongocxx/v1/exception.cpp | 73 +++++++ src/mongocxx/lib/mongocxx/v1/instance.cpp | 190 ++++++++++++++++ src/mongocxx/lib/mongocxx/v1/logger.cpp | 43 ++++ .../mongocxx/v_noabi/mongocxx/instance.cpp | 156 +++++-------- .../lib/mongocxx/v_noabi/mongocxx/logger.cpp | 30 --- src/mongocxx/test/CMakeLists.txt | 21 +- src/mongocxx/test/subprocess.cpp | 188 ++++++++++++++++ src/mongocxx/test/subprocess.hh | 60 +++++ src/mongocxx/test/v1/exception.cpp | 62 ++++++ src/mongocxx/test/v1/exception.hh | 34 +++ src/mongocxx/test/v1/instance.cpp | 206 ++++++++++++++++++ src/mongocxx/test/v1/instance.hh | 32 +++ src/mongocxx/test/v1/logger.cpp | 53 +++++ src/mongocxx/test/v1/logger.hh | 37 ++++ src/mongocxx/test/v_noabi/instance.cpp | 50 +++-- src/mongocxx/test/v_noabi/logging.cpp | 28 ++- 24 files changed, 1439 insertions(+), 294 deletions(-) create mode 100644 src/mongocxx/test/subprocess.cpp create mode 100644 src/mongocxx/test/subprocess.hh create mode 100644 src/mongocxx/test/v1/exception.cpp create mode 100644 src/mongocxx/test/v1/exception.hh create mode 100644 src/mongocxx/test/v1/instance.cpp create mode 100644 src/mongocxx/test/v1/instance.hh create mode 100644 src/mongocxx/test/v1/logger.cpp create mode 100644 src/mongocxx/test/v1/logger.hh diff --git a/.evergreen/scripts/test.sh b/.evergreen/scripts/test.sh index 9edb512e40..cbaa19cb93 100755 --- a/.evergreen/scripts/test.sh +++ b/.evergreen/scripts/test.sh @@ -289,8 +289,25 @@ else command -V valgrind valgrind --version run_test() { + valgrind_args=( + "--leak-check=full" + "--track-origins=yes" + "--num-callers=50" + "--error-exitcode=1" + "--error-limit=no" + "--read-var-info=yes" + "--suppressions=../etc/memcheck.suppressions" + ) + + # Prevent mongocxx::test::subprocess termination from confusing valgrind. + if [[ "${1:?}" =~ test_instance ]]; then + valgrind_args+=( + "--trace-children=no" + ) + fi + echo "Running ${1:?}..." - valgrind --leak-check=full --track-origins=yes --num-callers=50 --error-exitcode=1 --error-limit=no --read-var-info=yes --suppressions=../etc/memcheck.suppressions "${1:?}" "${test_args[@]:?}" || return + valgrind "${1:?}" "${test_args[@]:?}" || return echo "Running ${1:?}... done." } fi @@ -304,7 +321,6 @@ else run_test ./src/mongocxx/test/test_command_monitoring_specs run_test ./src/mongocxx/test/test_instance run_test ./src/mongocxx/test/test_transactions_specs - run_test ./src/mongocxx/test/test_logging run_test ./src/mongocxx/test/test_retryable_reads_specs run_test ./src/mongocxx/test/test_read_write_concern_specs run_test ./src/mongocxx/test/test_unified_format_specs diff --git a/src/mongocxx/include/mongocxx/v1/exception.hpp b/src/mongocxx/include/mongocxx/v1/exception.hpp index 25c686197e..175d1fa8ac 100644 --- a/src/mongocxx/include/mongocxx/v1/exception.hpp +++ b/src/mongocxx/include/mongocxx/v1/exception.hpp @@ -20,6 +20,13 @@ #include +#include + +#include + +#include +#include + namespace mongocxx { namespace v1 { @@ -28,14 +35,60 @@ namespace v1 { /// /// @attention This feature is experimental! It is not ready for use! /// -enum class source_errc {}; +enum class source_errc { + zero, ///< Zero. + mongocxx, ///< From the mongocxx library. + mongoc, ///< From the mongoc library. + mongocrypt, ///< From the mongocrypt library. + server, ///< From the MongoDB server. +}; + +/// +/// The error category for @ref mongocxx::v1::source_errc. +/// +/// @attention This feature is experimental! It is not ready for use! +/// +MONGOCXX_ABI_EXPORT_CDECL(std::error_category const&) source_error_category(); + +/// +/// Support implicit conversion to `std::error_condition`. +/// +/// @attention This feature is experimental! It is not ready for use! +/// +inline std::error_condition make_error_condition(source_errc code) { + return {static_cast(code), v1::source_error_category()}; +} /// /// Enumeration identifying the type (cause) of a @ref mongocxx::v1 error. /// /// @attention This feature is experimental! It is not ready for use! /// -enum class type_errc {}; +enum class type_errc { + zero, ///< Zero. + invalid_argument, ///< An invalid argument passed to the throwing function. + runtime_error, ///< An erroneous condition was detected at runtime. +}; + +/// +/// The error category for @ref mongocxx::v1::type_errc. +/// +/// @attention This feature is experimental! It is not ready for use! +/// +MONGOCXX_ABI_EXPORT_CDECL(std::error_category const&) type_error_category(); + +/// +/// Support implicit conversion to `std::error_condition`. +/// +/// @attention This feature is experimental! It is not ready for use! +/// +inline std::error_condition make_error_condition(type_errc code) { + return {static_cast(code), v1::type_error_category()}; +} + +BSONCXX_PRIVATE_WARNINGS_PUSH(); +BSONCXX_PRIVATE_WARNINGS_DISABLE(MSVC(4251)); +BSONCXX_PRIVATE_WARNINGS_DISABLE(MSVC(4275)); /// /// Base class for all exceptions thrown by @ref mongocxx::v1. @@ -45,11 +98,33 @@ enum class type_errc {}; /// /// @attention This feature is experimental! It is not ready for use! /// -class exception {}; +class exception : public std::system_error { + public: + ~exception() override; + + exception(exception&&) noexcept = default; + exception& operator=(exception&&) noexcept = default; + exception(exception const&) = default; + exception& operator=(exception const&) = default; + + using std::system_error::system_error; +}; + +BSONCXX_PRIVATE_WARNINGS_POP(); } // namespace v1 } // namespace mongocxx +namespace std { + +template <> +struct is_error_condition_enum : true_type {}; + +template <> +struct is_error_condition_enum : true_type {}; + +} // namespace std + #include /// diff --git a/src/mongocxx/include/mongocxx/v1/instance.hpp b/src/mongocxx/include/mongocxx/v1/instance.hpp index 90f276f9b4..57f4dc20a7 100644 --- a/src/mongocxx/include/mongocxx/v1/instance.hpp +++ b/src/mongocxx/include/mongocxx/v1/instance.hpp @@ -20,6 +20,14 @@ #include +#include + +#include + +#include +#include +#include + namespace mongocxx { namespace v1 { @@ -50,15 +58,135 @@ namespace v1 { /// @par Special exemptions /// Only the following API are permitted to be used outside the lifetime of an instance object: /// - @ref mongocxx::v1::logger +/// - @ref mongocxx::v1::default_logger /// /// @see /// - [Initialization and Cleanup (mongoc)](https://mongoc.org/libmongoc/current/init-cleanup.html) /// -class instance {}; +class instance { + private: + class impl; + std::unique_ptr _impl; + + public: + /// + /// Cleanup the mongocxx (and mongoc) library. + /// + /// Calls [`mongoc_init()`](https://mongoc.org/libmongoc/current/mongoc_cleanup.html). + /// + MONGOCXX_ABI_EXPORT_CDECL() ~instance(); + + /// + /// This class is not moveable. + /// + instance(instance&&) = delete; + + /// + /// This class is not moveable. + /// + instance& operator=(instance&&) = delete; + + /// + /// This class is not copyable. + /// + instance(instance const&) = delete; + + /// + /// This class is not copyable. + /// + instance& operator=(instance const&) = delete; + + /// + /// Initialize the mongoc library with unstructured log messages disabled. + /// + /// Calls [`mongoc_init()`](https://mongoc.org/libmongoc/current/mongoc_init.html) after disabling unstructured + /// log messages by calling `mongoc_log_set_handler(nullptr, nullptr)`. + /// + /// @important To use mongoc's default log message handler, construct this object with + /// @ref instance(v1::default_logger tag) instead. + /// + /// @throws mongocxx::v1::exception with @ref mongocxx::v1::instance::errc::multiple_instances if an `instance` + /// object has already been created. + /// + /// @see + /// - [Custom Log Handlers (mongoc)](https://mongoc.org/libmongoc/current/unstructured_log.html#custom-log-handlers) + /// + MONGOCXX_ABI_EXPORT_CDECL() instance(); + + /// + /// Initialize the mongoc library with the custom unstructured log message handler. + /// + /// Calls [`mongoc_init`](https://mongoc.org/libmongoc/current/mongoc_init.html) after registering the custom + /// unstructured log handler by calling `mongoc_log_set_handler()`. + /// + /// @param handler Disable unstructured logging when null. + /// + /// @throws mongocxx::v1::exception with @ref mongocxx::v1::instance::errc::multiple_instances if an `instance` + /// object has already been created. + /// + /// @see + /// - [Custom Log Handlers (mongoc)](https://mongoc.org/libmongoc/current/unstructured_log.html#custom-log-handlers) + /// + explicit MONGOCXX_ABI_EXPORT_CDECL() instance(std::unique_ptr handler); + + /// + /// Initialize the mongoc library with its default unstructured log handler. + /// + /// Calls [`mongoc_init`](https://mongoc.org/libmongoc/current/mongoc_init.html) without registering any custom + /// unstructured log handler. + /// + /// @param tag Unused: only for overload resolution. + /// + /// @throws mongocxx::v1::exception with @ref mongocxx::v1::instance::errc::multiple_instances if an `instance` + /// object has already been created. + /// + explicit MONGOCXX_ABI_EXPORT_CDECL() instance(v1::default_logger tag); + + /// + /// Equivalent to @ref instance(std::unique_ptr handler) when `handler == nullptr`. + /// + /// @see + /// - [Custom Log Handlers (mongoc)](https://mongoc.org/libmongoc/current/unstructured_log.html#custom-log-handlers) + /// + explicit MONGOCXX_ABI_EXPORT_CDECL() instance(std::nullptr_t); + + /// + /// Errors codes which may be returned by @ref mongocxx::v1::instance. + /// + /// @attention This feature is experimental! It is not ready for use! + /// + enum class errc { + zero, ///< Zero. + multiple_instances, ///< Cannot construct multiple instance objects in a given process. + }; + + /// + /// The error category for @ref mongocxx::v1::instance::errc. + /// + /// @attention This feature is experimental! It is not ready for use! + /// + static MONGOCXX_ABI_EXPORT_CDECL(std::error_category const&) error_category(); + + /// + /// Support implicit conversion to `std::error_code`. + /// + /// @attention This feature is experimental! It is not ready for use! + /// + friend std::error_code make_error_code(errc v) { + return {static_cast(v), error_category()}; + } +}; } // namespace v1 } // namespace mongocxx +namespace std { + +template <> +struct is_error_code_enum : true_type {}; + +} // namespace std + #include /// diff --git a/src/mongocxx/include/mongocxx/v1/logger-fwd.hpp b/src/mongocxx/include/mongocxx/v1/logger-fwd.hpp index c4a155ba23..2b57d2b06a 100644 --- a/src/mongocxx/include/mongocxx/v1/logger-fwd.hpp +++ b/src/mongocxx/include/mongocxx/v1/logger-fwd.hpp @@ -16,12 +16,17 @@ #include +// + +#include + namespace mongocxx { namespace v1 { enum class log_level; -class logger; +class MONGOCXX_ABI_EXPORT logger; + class default_logger; } // namespace v1 diff --git a/src/mongocxx/include/mongocxx/v1/logger.hpp b/src/mongocxx/include/mongocxx/v1/logger.hpp index 8f35665b92..1223114c5c 100644 --- a/src/mongocxx/include/mongocxx/v1/logger.hpp +++ b/src/mongocxx/include/mongocxx/v1/logger.hpp @@ -20,6 +20,11 @@ #include +#include +#include + +#include + namespace mongocxx { namespace v1 { @@ -28,7 +33,21 @@ namespace v1 { /// /// @attention This feature is experimental! It is not ready for use! /// -enum class log_level {}; +enum class log_level { + k_error, ///< MONGOC_LOG_LEVEL_ERROR + k_critical, ///< MONGOC_LOG_LEVEL_CRITICAL + k_warning, ///< MONGOC_LOG_LEVEL_WARNING + k_message, ///< MONGOC_LOG_LEVEL_MESSAGE + k_info, ///< MONGOC_LOG_LEVEL_INFO + k_debug, ///< MONGOC_LOG_LEVEL_DEBUG + k_trace, ///< MONGOC_LOG_LEVEL_TRACE +}; + +MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v1::stdx::string_view) to_string(log_level level); + +BSONCXX_PRIVATE_WARNINGS_PUSH(); +BSONCXX_PRIVATE_WARNINGS_DISABLE(MSVC(4251)); +BSONCXX_PRIVATE_WARNINGS_DISABLE(MSVC(4275)); /// /// The interface for an unstructured log message handler. @@ -37,7 +56,60 @@ enum class log_level {}; /// /// @attention This feature is experimental! It is not ready for use! /// -class logger {}; +class logger { + public: + /// + /// Destructor. + /// + virtual ~logger(); + + /// + /// Move constructor. + /// + logger(logger&&) = default; + + /// + /// Move assignment operator. + /// + logger& operator=(logger&&) = default; + + /// + /// Copy constructor. + /// + logger(logger const&) = default; + + /// + /// Copy assignment operator. + /// + logger& operator=(logger const&) = default; + + /// + /// Default constructor. + /// + logger() = default; + + /// + /// Handle an unstructured log message emitted by mongoc. + /// + /// Users may override this function to implement custom log message behavior such as outputting messages to a file + /// or sending messages to a remote server. + /// + /// + /// + /// @param level The log level for the message being handled. + /// @param domain The domain of the message. + /// @param message The contents of the log message. + /// + /// @see + /// - [Custom Log handlers (mongoc)](https://mongoc.org/libmongoc/current/unstructured_log.html#custom-log-handlers) + /// + virtual void operator()( + log_level level, + bsoncxx::v1::stdx::string_view domain, + bsoncxx::v1::stdx::string_view message) noexcept = 0; +}; + +BSONCXX_PRIVATE_WARNINGS_POP(); /// /// A tag type representing mongoc's default unstructured log handler. diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/instance.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/instance.hpp index bd99b83b86..47d3730623 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/instance.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/instance.hpp @@ -14,9 +14,14 @@ #pragma once +#include + +// + +#include + #include -#include #include #include @@ -24,59 +29,7 @@ namespace mongocxx { namespace v_noabi { -/// -/// An instance of the MongoDB driver. -/// -/// The constructor and destructor initialize and shut down the driver, respectively. Therefore, an -/// instance must be created before using the driver and must remain alive until all other mongocxx -/// objects are destroyed. After the instance destructor runs, the driver may not be used. -/// -/// Exactly one instance must be created in a given program. Not constructing an instance or -/// constructing more than one instance in a program are errors, even if the multiple instances have -/// non-overlapping lifetimes. -/// -/// The following is a correct example of using an instance in a program, as the instance is kept -/// alive for as long as the driver is in use: -/// -/// \code -/// -/// #include -/// #include -/// #include -/// -/// int main() { -/// mongocxx::v_noabi::instance inst{}; -/// mongocxx::v_noabi::client conn{mongocxx::v_noabi::uri{}}; -/// ... -/// } -/// -/// \endcode -/// -/// An example of using instance incorrectly might look as follows: -/// -/// \code -/// -/// #include -/// #include -/// #include -/// -/// client get_client() { -/// mongocxx::v_noabi::instance inst{}; -/// mongocxx::v_noabi::client conn{mongocxx::v_noabi::uri{}}; -/// -/// return client; -/// } // ERROR! The instance is no longer alive after this function returns. -/// -/// int main() { -/// mongocxx::v_noabi::client conn = get_client(); -/// ... -/// } -/// -/// \endcode -/// -/// For examples of more advanced usage of instance, see -/// `examples/mongocxx/instance_management.cpp`. -/// +/// @copydoc mongocxx::v1::instance class instance { public: /// @@ -90,7 +43,7 @@ class instance { /// /// @throws mongocxx::v_noabi::logic_error if an instance already exists. /// - MONGOCXX_ABI_EXPORT_CDECL() instance(std::unique_ptr logger); + MONGOCXX_ABI_EXPORT_CDECL() instance(std::unique_ptr logger); /// /// Move constructs an instance of the driver. @@ -129,3 +82,6 @@ class instance { /// @file /// Provides @ref mongocxx::v_noabi::instance. /// +/// @par Includes +/// - @ref mongocxx/v1/instance.hpp +/// diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger-fwd.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger-fwd.hpp index 78de780424..32c31affe9 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger-fwd.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger-fwd.hpp @@ -14,23 +14,29 @@ #pragma once +#include + #include namespace mongocxx { namespace v_noabi { -enum class log_level; +using v1::log_level; + +using v1::logger; -class MONGOCXX_ABI_EXPORT logger; +using v1::default_logger; } // namespace v_noabi } // namespace mongocxx namespace mongocxx { -using ::mongocxx::v_noabi::log_level; +using v1::log_level; -using ::mongocxx::v_noabi::logger; +using v1::logger; + +using v1::default_logger; } // namespace mongocxx @@ -40,3 +46,6 @@ using ::mongocxx::v_noabi::logger; /// @file /// Declares utilities related to mongocxx logging. /// +/// @par Includes +/// - @ref mongocxx/v1/logger-fwd.hpp +/// diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger.hpp index 90fd27e3fa..4f2f4acb6f 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger.hpp @@ -14,83 +14,31 @@ #pragma once -#include - #include -#include +// + +#include #include namespace mongocxx { namespace v_noabi { -/// -/// The log level of a log message. -/// -enum class log_level { - k_error, ///< Log Level Error. - k_critical, ///< Log Level Critical. - k_warning, ///< Log Level Warning. - k_message, ///< Log Level Message. - k_info, ///< Log Level Info. - k_debug, ///< Log Level Debug. - k_trace, ///< Log Level Trace. -}; +using v1::log_level; -/// -/// Returns a stringification of the given log level. -/// -/// @param level -/// The type to stringify. -/// -/// @return a std::string representation of the type. -/// -MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::stdx::string_view) to_string(log_level level); +using v1::to_string; -/// -/// The interface which user-defined loggers must implement. -/// -/// @see -/// - @ref mongocxx::v_noabi::instance -/// -class logger { - public: - virtual ~logger(); - - logger(logger&&) = default; - logger& operator=(logger&&) = default; - logger(logger const&) = default; - logger& operator=(logger const&) = default; - - /// - /// Handles a log message. User defined logger implementations may do whatever they wish when - /// this is called, such as log the output to a file or send it to a remote server for analysis. - /// - /// @param level - /// The log level of the current log message - /// @param domain - /// The domain of the current log message, such as 'client' - /// @param message - /// The text of the current log message. - virtual void operator()( - log_level level, - bsoncxx::v_noabi::stdx::string_view domain, - bsoncxx::v_noabi::stdx::string_view message) noexcept = 0; - - protected: - /// - /// Default constructor - /// - logger(); -}; +using v1::logger; + +using v1::default_logger; } // namespace v_noabi } // namespace mongocxx namespace mongocxx { -using ::mongocxx::v_noabi::to_string; +using v1::to_string; } // namespace mongocxx @@ -100,3 +48,6 @@ using ::mongocxx::v_noabi::to_string; /// @file /// Provides utilities related to mongocxx logging. /// +/// @par Includes +/// - @ref mongocxx/v1/logger.hpp +/// diff --git a/src/mongocxx/lib/mongocxx/v1/exception.cpp b/src/mongocxx/lib/mongocxx/v1/exception.cpp index 38ad005621..29798beee2 100644 --- a/src/mongocxx/lib/mongocxx/v1/exception.cpp +++ b/src/mongocxx/lib/mongocxx/v1/exception.cpp @@ -13,3 +13,76 @@ // limitations under the License. #include + +// + +#include + +#include +#include + +namespace mongocxx { +namespace v1 { + +std::error_category const& source_error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "mongocxx::v1::source_errc"; + } + + std::string message(int v) const noexcept override { + using code = v1::source_errc; + + switch (static_cast(v)) { + case code::zero: + return "zero"; + case code::mongocxx: + return "mongocxx"; + case code::mongoc: + return "mongoc"; + case code::mongocrypt: + return "mongocrypt"; + case code::server: + return "server"; + default: + return std::string(this->name()) + ':' + std::to_string(v); + } + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + +std::error_category const& type_error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "mongocxx::v1::type_errc"; + } + + std::string message(int v) const noexcept override { + using code = v1::type_errc; + + switch (static_cast(v)) { + case code::zero: + return "zero"; + case code::invalid_argument: + return "invalid argument"; + case code::runtime_error: + return "runtime error"; + default: + return std::string(this->name()) + ':' + std::to_string(v); + } + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + +exception::~exception() = default; + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/instance.cpp b/src/mongocxx/lib/mongocxx/v1/instance.cpp index 82b5800947..b8fe1cc895 100644 --- a/src/mongocxx/lib/mongocxx/v1/instance.cpp +++ b/src/mongocxx/lib/mongocxx/v1/instance.cpp @@ -13,3 +13,193 @@ // limitations under the License. #include + +// + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace mongocxx { +namespace v1 { + +using code = v1::instance::errc; + +static_assert(!std::is_move_constructible::value, "bsoncxx::v1::instance must be non-moveable"); +static_assert(!std::is_copy_constructible::value, "bsoncxx::v1::instance must be non-copyable"); + +namespace { + +// 0: no instance object has been created. +// 1: there is a single instance object. +// 2: the single instance object has been destroyed. +std::atomic_int instance_state{0}; + +void custom_log_handler( + mongoc_log_level_t log_level, + char const* domain, + char const* message, + void* user_data) noexcept { + (*static_cast(user_data))( + static_cast(log_level), + bsoncxx::v1::stdx::string_view(domain), + bsoncxx::v1::stdx::string_view(message)); +} + +} // namespace + +class instance::impl { + public: + std::unique_ptr _handler; + + ~impl() { + if (_handler) { + libmongoc::log_set_handler(nullptr, nullptr); + } + + libmongoc::cleanup(); + + instance_state.fetch_add(1, std::memory_order_release); + } + + impl(impl&&) = delete; + impl& operator=(impl&&) = delete; + impl(impl const&) = delete; + impl& operator=(impl const&) = delete; + + explicit impl(std::unique_ptr handler) : _handler{std::move(handler)} { + this->init(true); + } + + explicit impl(v1::default_logger tag) { + (void)tag; + this->init(false); + } + + private: + void init(bool set_custom_handler) { + { + int expected = 0; + + if (!instance_state.compare_exchange_strong( + expected, 1, std::memory_order_acquire, std::memory_order_relaxed)) { + throw v1::exception{code::multiple_instances}; + } + } + + if (set_custom_handler) { + if (auto ptr = _handler.get()) { + libmongoc::log_set_handler(&custom_log_handler, ptr); + } else { + libmongoc::log_set_handler(nullptr, nullptr); + } + } + + libmongoc::init(); + + { + // Avoid /Zc:__cplusplus problems with MSVC. +#pragma push_macro("STDCXX") +#undef STDCXX +#if defined(_MSVC_LANG) +#define STDCXX BSONCXX_PRIVATE_STRINGIFY(_MSVC_LANG) +#else +#define STDCXX BSONCXX_PRIVATE_STRINGIFY(__cplusplus) +#endif + + // Handshake data can only be appended once (or it will return false): this is that "once". + // Despite the name, mongoc_handshake_data_append() *prepends* the platform string. + // Use " / " to delimit handshake date for mongocxx and mongoc. + (void)libmongoc::handshake_data_append( + "mongocxx", + MONGOCXX_VERSION_STRING, + "CXX=" MONGOCXX_COMPILER_ID " " MONGOCXX_COMPILER_VERSION " stdcxx=" STDCXX " / "); + +#pragma pop_macro("STDCXX") + } + } +}; + +instance::~instance() = default; + +instance::instance() : instance{v1::default_logger{}} {} + +instance::instance(std::unique_ptr handler) : _impl{bsoncxx::make_unique(std::move(handler))} {} + +instance::instance(v1::default_logger tag) : _impl{bsoncxx::make_unique(tag)} { + (void)tag; +} + +instance::instance(std::nullptr_t) : instance{std::unique_ptr{}} {} + +std::error_category const& instance::error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "mongocxx::v1::instance"; + } + + std::string message(int v) const noexcept override { + switch (static_cast(v)) { + case code::zero: + return "zero"; + case code::multiple_instances: + return "cannot construct multiple instance objects in a given process"; + default: + return std::string(this->name()) + ':' + std::to_string(v); + } + } + + bool equivalent(int v, std::error_condition const& ec) const noexcept override { + if (ec.category() == v1::source_error_category()) { + using condition = v1::source_errc; + + auto const source = static_cast(ec.value()); + + switch (static_cast(v)) { + case code::multiple_instances: + return source == condition::mongocxx; + + case code::zero: + default: + return false; + } + } + + if (ec.category() == v1::type_error_category()) { + using condition = v1::type_errc; + + auto const type = static_cast(ec.value()); + + switch (static_cast(v)) { + case code::multiple_instances: + return type == condition::runtime_error; + + case code::zero: + default: + return false; + } + } + + return false; + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v1/logger.cpp b/src/mongocxx/lib/mongocxx/v1/logger.cpp index d1a70367cd..3f297e9abb 100644 --- a/src/mongocxx/lib/mongocxx/v1/logger.cpp +++ b/src/mongocxx/lib/mongocxx/v1/logger.cpp @@ -13,3 +13,46 @@ // limitations under the License. #include + +// + +#include + +#include + +namespace mongocxx { +namespace v1 { + +static_assert(static_cast(log_level::k_error) == static_cast(MONGOC_LOG_LEVEL_ERROR), ""); +static_assert(static_cast(log_level::k_critical) == static_cast(MONGOC_LOG_LEVEL_CRITICAL), ""); +static_assert(static_cast(log_level::k_warning) == static_cast(MONGOC_LOG_LEVEL_WARNING), ""); +static_assert(static_cast(log_level::k_message) == static_cast(MONGOC_LOG_LEVEL_MESSAGE), ""); +static_assert(static_cast(log_level::k_info) == static_cast(MONGOC_LOG_LEVEL_INFO), ""); +static_assert(static_cast(log_level::k_debug) == static_cast(MONGOC_LOG_LEVEL_DEBUG), ""); +static_assert(static_cast(log_level::k_trace) == static_cast(MONGOC_LOG_LEVEL_TRACE), ""); + +bsoncxx::v1::stdx::string_view to_string(log_level level) { + switch (level) { + case log_level::k_error: + return "error"; + case log_level::k_critical: + return "critical"; + case log_level::k_warning: + return "warning"; + case log_level::k_message: + return "message"; + case log_level::k_info: + return "info"; + case log_level::k_debug: + return "debug"; + case log_level::k_trace: + return "trace"; + default: + return "unknown"; + } +} + +logger::~logger() = default; + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp index 37687ae315..fcea33ad15 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp @@ -12,22 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include + +// + +#include +#include +#include #include -#include -#include +#include #include #include #include -#include #include #include -#include #include namespace mongocxx { @@ -35,130 +37,80 @@ namespace v_noabi { namespace { -log_level convert_log_level(::mongoc_log_level_t mongoc_log_level) { - switch (mongoc_log_level) { - case MONGOC_LOG_LEVEL_ERROR: - return log_level::k_error; - case MONGOC_LOG_LEVEL_CRITICAL: - return log_level::k_critical; - case MONGOC_LOG_LEVEL_WARNING: - return log_level::k_warning; - case MONGOC_LOG_LEVEL_MESSAGE: - return log_level::k_message; - case MONGOC_LOG_LEVEL_INFO: - return log_level::k_info; - case MONGOC_LOG_LEVEL_DEBUG: - return log_level::k_debug; - case MONGOC_LOG_LEVEL_TRACE: - return log_level::k_trace; - default: - MONGOCXX_PRIVATE_UNREACHABLE; - } -} +// To support mongocxx::v_noabi::instance::current(). +std::atomic current_instance{nullptr}; -void null_log_handler(::mongoc_log_level_t, char const*, char const*, void*) {} +// Sentinel value denoting the current instance has been destroyed. +instance* sentinel() { + alignas(instance) static unsigned char value[sizeof(instance)]; + return reinterpret_cast(value); +} -void user_log_handler( - ::mongoc_log_level_t mongoc_log_level, - char const* log_domain, +void custom_log_handler( + mongoc_log_level_t log_level, + char const* domain, char const* message, - void* user_data) { - (*static_cast(user_data))( - convert_log_level(mongoc_log_level), - bsoncxx::v_noabi::stdx::string_view{log_domain}, - bsoncxx::v_noabi::stdx::string_view{message}); + void* user_data) noexcept { + (*static_cast(user_data))( + static_cast(log_level), + bsoncxx::v1::stdx::string_view(domain), + bsoncxx::v1::stdx::string_view(message)); } -// A region of memory that acts as a sentintel value indicating that an instance object is being -// destroyed. We only care about the address of this object, never its contents. -alignas(instance) unsigned char sentinel[sizeof(instance)]; - -std::atomic current_instance{nullptr}; -static_assert(std::is_standard_layout::value, "Must be standard layout"); -static_assert(std::is_trivially_destructible::value, "Must be trivially destructible"); - } // namespace class instance::impl { public: - impl(std::unique_ptr logger) : _user_logger(std::move(logger)) { - libmongoc::init(); - if (_user_logger) { - libmongoc::log_set_handler(user_log_handler, _user_logger.get()); - // The libmongoc namespace mocking system doesn't play well with varargs - // functions, so we use a bare mongoc_log call here. - mongoc_log(MONGOC_LOG_LEVEL_INFO, "mongocxx", "libmongoc logging callback enabled"); - } else { - libmongoc::log_set_handler(null_log_handler, nullptr); - } - - // Despite the name, mongoc_handshake_data_append *prepends* the platform string. - // mongoc_handshake_data_append does not add a delimitter, so include the " / " in the - // argument for consistency with the driver_name, and driver_version. - std::stringstream platform; - long stdcxx = __cplusplus; -#ifdef _MSVC_LANG - // Prefer _MSVC_LANG to report the supported C++ standard with MSVC. - // The __cplusplus macro may be incorrect. See: - // https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ - stdcxx = _MSVC_LANG; -#endif - platform << "CXX=" << MONGOCXX_COMPILER_ID << " " << MONGOCXX_COMPILER_VERSION << " " - << "stdcxx=" << stdcxx << " / "; - libmongoc::handshake_data_append("mongocxx", MONGOCXX_VERSION_STRING, platform.str().c_str()); - } - - ~impl() { - // If we had a user logger, remove it so that it can't be used by the driver after it goes - // out of scope - if (_user_logger) { - libmongoc::log_set_handler(null_log_handler, nullptr); - } - - libmongoc::cleanup(); - } - - impl(impl&&) noexcept = delete; - impl& operator=(impl&&) noexcept = delete; + v1::instance _instance; + std::unique_ptr _handler; + impl(impl&&) = delete; + impl& operator=(impl&&) = delete; impl(impl const&) = delete; impl& operator=(impl const&) = delete; - std::unique_ptr const _user_logger; + impl(std::unique_ptr handler) try : _instance{v1::default_logger{}}, _handler{std::move(handler)} { + // For backward compatibility, allow v1::instance to initialize mongoc with the default log handler, then set + // the custom logger AFTER initialization has already completed. + if (_handler) { + libmongoc::log_set_handler(&custom_log_handler, _handler.get()); + + // Inform the user that a custom log handler has been registered. + // Cannot use mocked `libmongoc::log()` due to varargs. + mongoc_log(MONGOC_LOG_LEVEL_INFO, "mongocxx", "libmongoc logging callback enabled"); + } else { + libmongoc::log_set_handler(nullptr, nullptr); + } + } catch (v1::exception const&) { + throw v_noabi::logic_error{error_code::k_cannot_recreate_instance}; + } }; instance::instance() : instance(nullptr) {} -instance::instance(std::unique_ptr logger) { - instance* expected = nullptr; - - if (!current_instance.compare_exchange_strong(expected, this)) { - throw logic_error{error_code::k_cannot_recreate_instance}; - } - - _impl = bsoncxx::make_unique(std::move(logger)); +instance::instance(std::unique_ptr logger) : _impl{bsoncxx::make_unique(std::move(logger))} { + current_instance.store(this, std::memory_order_relaxed); } instance::instance(instance&&) noexcept = default; instance& instance::operator=(instance&&) noexcept = default; instance::~instance() { - current_instance.store(reinterpret_cast(&sentinel)); - _impl.reset(); + current_instance.store(sentinel(), std::memory_order_relaxed); } instance& instance::current() { - if (!current_instance.load()) { - static instance the_instance; - } - - instance* curr = current_instance.load(); + if (auto const p = current_instance.load(std::memory_order_relaxed)) { + if (p == sentinel()) { + throw v_noabi::logic_error{error_code::k_instance_destroyed}; + } else { + return *p; + } - if (curr == reinterpret_cast(&sentinel)) { - throw logic_error{error_code::k_instance_destroyed}; + } else { + static instance the_instance; + return the_instance; } - - return *curr; } } // namespace v_noabi diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/logger.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/logger.cpp index 69fbc72912..ec4c30f0a5 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/logger.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/logger.cpp @@ -13,33 +13,3 @@ // limitations under the License. #include - -namespace mongocxx { -namespace v_noabi { - -bsoncxx::v_noabi::stdx::string_view to_string(log_level level) { - switch (level) { - case log_level::k_error: - return "error"; - case log_level::k_critical: - return "critical"; - case log_level::k_warning: - return "warning"; - case log_level::k_message: - return "message"; - case log_level::k_info: - return "info"; - case log_level::k_debug: - return "debug"; - case log_level::k_trace: - return "trace"; - default: - return "unknown"; - } -} - -logger::logger() = default; -logger::~logger() = default; - -} // namespace v_noabi -} // namespace mongocxx diff --git a/src/mongocxx/test/CMakeLists.txt b/src/mongocxx/test/CMakeLists.txt index 627de5cb43..25f46ffd07 100644 --- a/src/mongocxx/test/CMakeLists.txt +++ b/src/mongocxx/test/CMakeLists.txt @@ -94,6 +94,11 @@ set(mongocxx_test_sources_v_noabi v_noabi/write_concern.cpp ) +set(mongocxx_test_sources_v1 + v1/exception.cpp + v1/logger.cpp +) + set(mongocxx_test_sources_spec spec/monitoring.cpp spec/operation.cpp @@ -113,10 +118,12 @@ set(mongocxx_test_sources_extra spec/transactions.cpp spec/unified_tests/operations.cpp spec/unified_tests/runner.cpp + subprocess.cpp v_noabi/catch_helpers.cpp v_noabi/client_helpers.cpp v_noabi/instance.cpp v_noabi/logging.cpp + v1/instance.cpp ) file(GLOB_RECURSE mongocxx_test_headers @@ -132,14 +139,17 @@ add_library(spec_test_common OBJECT ${mongocxx_test_sources_spec}) add_executable(test_driver ${mongocxx_test_sources_private} ${mongocxx_test_sources_v_noabi} + ${mongocxx_test_sources_v1} v_noabi/catch_helpers.cpp ) target_link_libraries(test_driver PRIVATE spec_test_common client_helpers) -add_executable(test_logging v_noabi/logging.cpp) -target_compile_definitions(test_logging PRIVATE MONGOCXX_TEST_DISABLE_MAIN_INSTANCE) - -add_executable(test_instance v_noabi/instance.cpp) +add_executable(test_instance + subprocess.cpp + v_noabi/instance.cpp + v_noabi/logging.cpp + v1/instance.cpp +) target_compile_definitions(test_instance PRIVATE MONGOCXX_TEST_DISABLE_MAIN_INSTANCE) add_executable(test_crud_specs spec/crud.cpp) @@ -202,7 +212,6 @@ set_property( test_driver test_gridfs_specs test_instance - test_logging test_read_write_concern_specs test_retryable_reads_specs test_transactions_specs @@ -231,7 +240,6 @@ foreach(test_name driver gridfs_specs instance - logging read_write_concern_specs retryable_reads_specs transactions_specs @@ -338,6 +346,7 @@ set_dist_list(src_mongocxx_test_DIST CMakeLists.txt ${mongocxx_test_sources_private} ${mongocxx_test_sources_v_noabi} + ${mongocxx_test_sources_v1} ${mongocxx_test_sources_spec} ${mongocxx_test_sources_extra} ${mongocxx_test_headers} diff --git a/src/mongocxx/test/subprocess.cpp b/src/mongocxx/test/subprocess.cpp new file mode 100644 index 0000000000..eecc18b6bf --- /dev/null +++ b/src/mongocxx/test/subprocess.cpp @@ -0,0 +1,188 @@ +// Copyright 2009-present MongoDB, Inc. +// +// 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. + +#include + +// + +#include +#include +#include + +#include + +#if !defined(_MSC_VER) + +#include // exit(), strsignal(), etc. +#include // strsignal() +#include // fork(), close(), etc. + +#include +#include // waitpid() + +namespace mongocxx { +namespace test { + +int subprocess(std::function fn, bool* is_signal_ptr) { + auto is_signal_local = false; + auto& is_signal = is_signal_ptr ? *is_signal_ptr : is_signal_local; + + pid_t const pid = ::fork(); + + // Child: do nothing more than call `fn`. + if (pid == 0) { + // Use `std::exit()` and `std::terminate()` to prevent continued execution of the Catch2 test suite. + try { + fn(); + std::exit(EXIT_SUCCESS); + } catch (Catch::TestFailureException) { + // Assertion failure already output its diagnostic message. + std::exit(EXIT_FAILURE); + } catch (Catch::TestSkipException) { + // SKIP() already output its diagnostic message. + // Don't try to propagate the "skip", just treat as equivalent to success. + std::exit(EXIT_SUCCESS); + } catch (std::exception const& ex) { + // Trigger output of Catch2 diagnostic messages. + FAIL_CHECK("uncaught exception in subprocess: " << ex.what()); + std::exit(EXIT_FAILURE); + } catch (...) { + // Allow default termination handler to translate the unknown exception type. + // This should also trigger the output of Catch2 diagnostic messages. + std::terminate(); + } + } + + // Parent: wait for child and handle returned status values. + else { + int status; + + int const ret = ::waitpid(pid, &status, 0); + + if (WIFEXITED(status) && WEXITSTATUS(status) != EXIT_SUCCESS) { + UNSCOPED_INFO("subprocess exited with a non-zero exit code: " << WEXITSTATUS(status)); + is_signal = false; + return WEXITSTATUS(status); + } + + // For unexpected signals, stop immediately. + else if (WIFSIGNALED(status)) { + int const signal = WTERMSIG(status); + char const* const sigstr = ::strsignal(signal); + UNSCOPED_INFO("subprocess was killed by signal: " << signal << " (" << (sigstr ? sigstr : "") << ")"); + is_signal = true; + return signal; + } + + // We don't expect any other failure condition. + else { + REQUIRE(ret != -1); + return 0; + } + } +} + +} // namespace test +} // namespace mongocxx + +#else + +namespace mongocxx { +namespace test { + +int subprocess(std::function fn, bool* is_signal_ptr) { + (void)fn; + (void)is_signal_ptr; + + SKIP("mongocxx::test::subprocess() is not supported"); + + return 0; // Unused. +} + +} // namespace test +} // namespace mongocxx + +#endif // !defined(_MSC_VER) + +TEST_CASE("counter", "[mongocxx][test][subprocess]") { + std::atomic_int counter{0}; + + CHECK_SUBPROCESS([&counter] { counter.fetch_add(1); }); + CHECK_SUBPROCESS([&counter] { counter.fetch_add(2); }); + CHECK_SUBPROCESS([&counter] { counter.fetch_add(3); }); + + // State of a subprocess must not be observable by the original process. + REQUIRE(counter.load() == 0); +} + +TEST_CASE("failure", "[mongocxx][test][subprocess]") { + auto is_signal = false; + CHECK_FALSE_SUBPROCESS([] { + // Try to silence noisy Catch2 output. + (void)::close(1); // stdout + (void)::close(2); // stderr + + FAIL("subprocess"); + }); + CHECK_FALSE(is_signal); +} + +TEST_CASE("skip", "[mongocxx][test][subprocess]") { + auto is_signal = false; + CHECK_SUBPROCESS([] { + // Try to silence noisy Catch2 output. + (void)::close(1); // stdout + (void)::close(2); // stderr + + SKIP("subprocess"); + }); + CHECK_FALSE(is_signal); +} + +TEST_CASE("exception", "[mongocxx][test][subprocess]") { + auto is_signal = false; + CHECK_FALSE_SUBPROCESS( + [] { + // Try to silence noisy Catch2 output. + (void)::close(1); // stdout + (void)::close(2); // stderr + + throw std::runtime_error("subprocess"); + }, + &is_signal); + CHECK_FALSE(is_signal); +} + +TEST_CASE("unknown exception", "[mongocxx][test][subprocess]") { +#if !defined(_MSC_VER) + auto is_signal = false; + auto const ret = mongocxx::test::subprocess( + [] { + // Try to silence noisy Catch2 output. + (void)::close(1); // stdout + (void)::close(2); // stderr + + throw "subprocess"; + }, + &is_signal); + CHECK(is_signal); + CHECK(ret != 0); + CHECK(ret != SIGTERM); // std::terminate() + +#else + + CHECK_SUBPROCESS([] {}); + +#endif // !defined(_MSC_VER) +} diff --git a/src/mongocxx/test/subprocess.hh b/src/mongocxx/test/subprocess.hh new file mode 100644 index 0000000000..857eab6cbd --- /dev/null +++ b/src/mongocxx/test/subprocess.hh @@ -0,0 +1,60 @@ +// Copyright 2009-present MongoDB, Inc. +// +// 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. + +#pragma once + +#include + +#include + +namespace mongocxx { +namespace test { + +// Execute `fn` in a forked subprocess. +// +// Should be invoked directly from the body of a TEST_CASE(), not within a SECTION(). Keep it simple! +// +// @param fn The function to be invoked within a subprocess. +// @param is_signal_ptr When not null, `*is_signal_ptr` is set to `true` if the return value is a signal rather than an +// exit code. +// +// @returns The exit code of the subprocess (`*is_signal` is `false`) or the signal used to kill the subprocess +// (`*is_signal` is `true`) . +int subprocess(std::function fn, bool* is_signal_ptr = nullptr); + +#if !defined(_MSC_VER) + +#define CHECK_SUBPROCESS(...) \ + if (1) { \ + int const ret = mongocxx::test::subprocess(__VA_ARGS__); \ + CHECK(ret == 0); \ + } else \ + ((void)0) + +#define CHECK_FALSE_SUBPROCESS(...) \ + if (1) { \ + int const ret = mongocxx::test::subprocess(__VA_ARGS__); \ + CHECK(ret != 0); \ + } else \ + ((void)0) + +#else + +#define CHECK_SUBPROCESS(...) SKIP("mongocxx::test::subprocess() is not supported") +#define CHECK_FALSE_SUBPROCESS(...) SKIP("mongocxx::test::subprocess() is not supported") + +#endif // !defined(_MSC_VER) + +} // namespace test +} // namespace mongocxx diff --git a/src/mongocxx/test/v1/exception.cpp b/src/mongocxx/test/v1/exception.cpp new file mode 100644 index 0000000000..875cc8926a --- /dev/null +++ b/src/mongocxx/test/v1/exception.cpp @@ -0,0 +1,62 @@ +// Copyright 2009-present MongoDB, Inc. +// +// 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. + +#include + +// + +#include + +#include +#include + +namespace mongocxx { +namespace v1 { + +TEST_CASE("source", "[mongocxx][v1][error]") { + auto const& c = source_error_category(); + + SECTION("name") { + CHECK_THAT(c.name(), Catch::Matchers::Equals("mongocxx::v1::source_errc")); + } + + SECTION("message") { + CHECK(c.message(-1) == "mongocxx::v1::source_errc:-1"); + CHECK(c.message(0) == "zero"); + CHECK(c.message(1) == "mongocxx"); + CHECK(c.message(2) == "mongoc"); + CHECK(c.message(3) == "mongocrypt"); + CHECK(c.message(4) == "server"); + CHECK(c.message(5) == "mongocxx::v1::source_errc:5"); + } +} + +TEST_CASE("type", "[mongocxx][v1][error]") { + auto const& c = type_error_category(); + + SECTION("name") { + CHECK_THAT(c.name(), Catch::Matchers::Equals("mongocxx::v1::type_errc")); + } + + SECTION("message") { + CHECK(c.message(-1) == "mongocxx::v1::type_errc:-1"); + CHECK(c.message(0) == "zero"); + CHECK(c.message(1) == "invalid argument"); + CHECK(c.message(2) == "runtime error"); + CHECK(c.message(3) == "mongocxx::v1::type_errc:3"); + } +} + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/test/v1/exception.hh b/src/mongocxx/test/v1/exception.hh new file mode 100644 index 0000000000..402a9bf2bf --- /dev/null +++ b/src/mongocxx/test/v1/exception.hh @@ -0,0 +1,34 @@ +// Copyright 2009-present MongoDB, Inc. +// +// 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. + +#pragma once + +#include + +// + +#include + +CATCH_REGISTER_ENUM( + mongocxx::v1::source_errc, + mongocxx::v1::source_errc::zero, + mongocxx::v1::source_errc::mongocxx, + mongocxx::v1::source_errc::mongoc, + mongocxx::v1::source_errc::mongocrypt) + +CATCH_REGISTER_ENUM( + mongocxx::v1::type_errc, + mongocxx::v1::type_errc::zero, + mongocxx::v1::type_errc::invalid_argument, + mongocxx::v1::type_errc::runtime_error) diff --git a/src/mongocxx/test/v1/instance.cpp b/src/mongocxx/test/v1/instance.cpp new file mode 100644 index 0000000000..7c80662b2c --- /dev/null +++ b/src/mongocxx/test/v1/instance.cpp @@ -0,0 +1,206 @@ +// Copyright 2009-present MongoDB, Inc. +// +// 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. + +#include + +// + +#include + +#include + +#include +#include + +#include + +#include + +#include + +#include + +#include +#include + +#if !defined(_MSC_VER) +#include +#endif + +namespace mongocxx { +namespace v1 { + +namespace { + +class custom_logger : public logger { + private: + using fn_type = std::function; + + fn_type _fn; + + public: + /* explicit(false) */ custom_logger(fn_type fn) : _fn{std::move(fn)} {} + + void operator()( + log_level level, + bsoncxx::v1::stdx::string_view domain, + bsoncxx::v1::stdx::string_view message) noexcept override { + _fn(level, domain, message); + } +}; + +} // namespace + +TEST_CASE("logger", "[mongocxx][v1][instance]") { +#if !defined(_MSC_VER) + class capture_stderr { + private: + int _pipes[2]; + int _stderr; // stderr + + public: + ~capture_stderr() { + ::dup2(_stderr, STDERR_FILENO); // Restore original stderr. + } + + capture_stderr(capture_stderr&&) = delete; + capture_stderr& operator=(capture_stderr&&) = delete; + capture_stderr(capture_stderr const&) = delete; + capture_stderr& operator=(capture_stderr const&) = delete; + + capture_stderr() + : _stderr{::dup(STDERR_FILENO)} // Save original stderr. + { + ::fflush(stderr); + REQUIRE(::pipe(_pipes) == 0); // Open redirection pipes. + REQUIRE(::dup2(_pipes[1], STDERR_FILENO) != -1); // Copy stderr to input pipe. + REQUIRE(::close(_pipes[1]) != -1); // Close original stderr. + + REQUIRE( + ::fcntl(_pipes[0], F_SETFL, ::fcntl(_pipes[0], F_GETFL) | O_NONBLOCK) != -1); // Do not block on read. + } + + std::string read() { + std::string res; + + { + // Capture everything up to this point. + // Avoid recursive macro expansion of `stderr` due to Catch expression decomposition. + auto const res = ::fflush(stderr); + REQUIRE(res == 0); + } + + while (true) { + char buf[BUFSIZ]; + auto const n = ::read(_pipes[0], buf, std::size_t{BUFSIZ - 1}); + + CHECKED_IF(n < 0) { + REQUIRE(errno == EAGAIN); // No data left in stream. + break; + } + else { + res.append(buf, static_cast(n)); + } + } + + return res; + } + }; + +#else + + class capture_stderr { + public: + std::string read() { + return {}; + } + }; + +#endif // !defined(_MSC_VER) + + { + auto const test_default = [] { + instance i; + + auto const output = [&] { + capture_stderr guard; + mongoc_log(MONGOC_LOG_LEVEL_WARNING, "mongocxx::v1::instance", "mongoc_log_default_handler"); + return guard.read(); + }(); + + CHECK_THAT( + output, + Catch::Matchers::ContainsSubstring("mongocxx::v1::instance") && + Catch::Matchers::ContainsSubstring("mongoc_log_default_handler")); + }; + CHECK_SUBPROCESS(test_default); + } + + { + auto const test_noop = [] { + instance i{nullptr}; + + auto const output = [&] { + capture_stderr guard; + mongoc_log(MONGOC_LOG_LEVEL_WARNING, "mongocxx::v1::instance", "SHOULD NOT BE LOGGED"); + return guard.read(); + }(); + + CHECK_THAT( + output, + !Catch::Matchers::ContainsSubstring("mongocxx::v1::instance") && + !Catch::Matchers::ContainsSubstring("SHOULD NOT BE LOGGED")); + }; + CHECK_SUBPROCESS(test_noop); + } + + { + auto const test_custom = [] { + int count = 0; + log_level level; + std::string domain; + std::string message; + + auto const fn = + [&](log_level _level, bsoncxx::v1::stdx::string_view _domain, bsoncxx::v1::stdx::string_view _message) { + ++count; + level = _level; + domain = std::string{_domain}; + message = std::string{_message}; + }; + + instance i{bsoncxx::make_unique(fn)}; + + mongoc_log(MONGOC_LOG_LEVEL_WARNING, "mongocxx::v1::instance", "custom_logger"); + + CHECK(count == 1); + CHECK(level == log_level::k_warning); + CHECK(domain == "mongocxx::v1::instance"); + CHECK(message == "custom_logger"); + }; + CHECK_SUBPROCESS(test_custom); + } +} + +TEST_CASE("stringify", "[mongocxx][test][v1][instance]") { + auto const test = [] { + instance i; + + REQUIRE(bsoncxx::test::stringify(i) == "mongocxx::v1::instance"); + }; + CHECK_SUBPROCESS(test); +} + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/test/v1/instance.hh b/src/mongocxx/test/v1/instance.hh new file mode 100644 index 0000000000..2faac1a5f9 --- /dev/null +++ b/src/mongocxx/test/v1/instance.hh @@ -0,0 +1,32 @@ +// Copyright 2009-present MongoDB, Inc. +// +// 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. + +#pragma once + +#include + +// + +#include + +namespace Catch { + +template <> +struct StringMaker { + static std::string convert(mongocxx::v1::instance const&) { + return "mongocxx::v1::instance"; + } +}; + +} // namespace Catch diff --git a/src/mongocxx/test/v1/logger.cpp b/src/mongocxx/test/v1/logger.cpp new file mode 100644 index 0000000000..6eff3f8b20 --- /dev/null +++ b/src/mongocxx/test/v1/logger.cpp @@ -0,0 +1,53 @@ +// Copyright 2009-present MongoDB, Inc. +// +// 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. + +#include + +// + +#include + +#include + +namespace mongocxx { +namespace v1 { + +namespace { + +struct custom_logger : logger { + void operator()( + log_level level, + bsoncxx::v1::stdx::string_view domain, + bsoncxx::v1::stdx::string_view message) noexcept override { + (void)level; + (void)domain; + (void)message; + } +}; + +} // namespace + +// mongocxx::v1::logger must not unnecessarily mpose special requirements on derived classes. +static_assert(std::is_nothrow_destructible::value, ""); +static_assert(std::is_nothrow_move_constructible::value, ""); +static_assert(std::is_copy_constructible::value, ""); +static_assert(std::is_default_constructible::value, ""); + +TEST_CASE("stringify", "[mongocxx][test][v1][logger]") { + // No point specializing StringMaker for abstract class mongocxx::v1::logger. + CHECK(bsoncxx::test::stringify(custom_logger{}) == "{?}"); +} + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/test/v1/logger.hh b/src/mongocxx/test/v1/logger.hh new file mode 100644 index 0000000000..3c8249d60e --- /dev/null +++ b/src/mongocxx/test/v1/logger.hh @@ -0,0 +1,37 @@ +// Copyright 2009-present MongoDB, Inc. +// +// 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. + +#pragma once + +#include + +// + +#include + +CATCH_REGISTER_ENUM( + mongocxx::v1::log_level, + mongocxx::v1::log_level::k_error, + mongocxx::v1::log_level::k_critical, + mongocxx::v1::log_level::k_warning, + mongocxx::v1::log_level::k_message, + mongocxx::v1::log_level::k_info, + mongocxx::v1::log_level::k_debug, + mongocxx::v1::log_level::k_trace) + +namespace Catch { + +// No point specializing StringMaker for abstract class mongocxx::v1::logger. + +} // namespace Catch diff --git a/src/mongocxx/test/v_noabi/instance.cpp b/src/mongocxx/test/v_noabi/instance.cpp index 1e5d5b9212..77fe2212ff 100644 --- a/src/mongocxx/test/v_noabi/instance.cpp +++ b/src/mongocxx/test/v_noabi/instance.cpp @@ -12,34 +12,50 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include -#include +// + +#include +#include + +#include + +#include namespace { -TEST_CASE("instance", "[mongocxx][v_noabi][instance]") { - using namespace mongocxx; +using mongocxx::v_noabi::instance; + +using code = mongocxx::v_noabi::error_code; + +TEST_CASE("basic", "[mongocxx][v_noabi][instance]") { + auto const ret = mongocxx::test::subprocess([] { + using mongocxx::v_noabi::logic_error; - instance* inst = nullptr; + instance* inst = nullptr; - // instance::current creates instance when one has not already been created - REQUIRE_NOTHROW(inst = &instance::current()); + // instance::current creates instance when one has not already been created + REQUIRE_NOTHROW(inst = &instance::current()); - // multiple instances cannot be created" - REQUIRE_THROWS_AS(instance{}, logic_error); + // multiple instances cannot be created" + REQUIRE_THROWS_WITH_CODE(instance{}, code::k_cannot_recreate_instance); + REQUIRE_THROWS_AS(instance{}, logic_error); - // instance::current works when instance is alive - REQUIRE_NOTHROW(instance::current()); + // instance::current works when instance is alive + REQUIRE_NOTHROW(instance::current()); - // an instance cannot be created after one has been destroyed - inst->~instance(); - inst = nullptr; - REQUIRE_THROWS_AS(instance{}, logic_error); + // an instance cannot be created after one has been destroyed + inst->~instance(); + inst = nullptr; + REQUIRE_THROWS_WITH_CODE(instance{}, code::k_cannot_recreate_instance); + REQUIRE_THROWS_AS(instance{}, logic_error); - // instance::current throws if an instance has already been destroyed - REQUIRE_THROWS_AS(instance::current(), logic_error); + // instance::current throws if an instance has already been destroyed + REQUIRE_THROWS_WITH_CODE(instance::current(), code::k_instance_destroyed); + REQUIRE_THROWS_AS(instance::current(), logic_error); + }); + REQUIRE(ret == 0); } } // namespace diff --git a/src/mongocxx/test/v_noabi/logging.cpp b/src/mongocxx/test/v_noabi/logging.cpp index 4d9fb86a67..fb2938f7ec 100644 --- a/src/mongocxx/test/v_noabi/logging.cpp +++ b/src/mongocxx/test/v_noabi/logging.cpp @@ -12,10 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + +// + #include #include -#include #include @@ -23,6 +26,8 @@ #include +#include + namespace { using namespace mongocxx; @@ -49,18 +54,21 @@ class reset_log_handler_when_done { } }; -TEST_CASE("a user-provided log handler will be used for logging output", "[instance]") { - reset_log_handler_when_done rlhwd; +TEST_CASE("custom", "[mongocxx][v_noabi][logger]") { + auto const ret = mongocxx::test::subprocess([] { + reset_log_handler_when_done rlhwd; - std::vector events; - mongocxx::instance driver{bsoncxx::make_unique(&events)}; + std::vector events; + mongocxx::instance driver{bsoncxx::make_unique(&events)}; - // The libmongoc namespace mocking system doesn't play well with varargs - // functions, so we use a bare mongoc_log call here. - mongoc_log(::MONGOC_LOG_LEVEL_ERROR, "foo", "bar"); + // The libmongoc namespace mocking system doesn't play well with varargs + // functions, so we use a bare mongoc_log call here. + mongoc_log(::MONGOC_LOG_LEVEL_ERROR, "foo", "bar"); - REQUIRE(events.size() == 1); - REQUIRE(events[0] == std::make_tuple(log_level::k_error, "foo", "bar")); + REQUIRE(events.size() == 1); + REQUIRE(events[0] == std::make_tuple(log_level::k_error, "foo", "bar")); + }); + REQUIRE(ret == 0); } } // namespace From 2892b35c95f10c01aeffec3f105f05571534962b Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Fri, 26 Sep 2025 14:50:28 -0500 Subject: [PATCH 02/17] Remove redundant redeclarations --- src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger.hpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger.hpp index 4f2f4acb6f..d341bd0d8a 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/logger.hpp @@ -25,14 +25,8 @@ namespace mongocxx { namespace v_noabi { -using v1::log_level; - using v1::to_string; -using v1::logger; - -using v1::default_logger; - } // namespace v_noabi } // namespace mongocxx From 0370746857674a998e33fe5116feb5f53268f48a Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Fri, 26 Sep 2025 14:50:28 -0500 Subject: [PATCH 03/17] Remove redundant instance ctor overload --- src/mongocxx/include/mongocxx/v1/instance.hpp | 8 -------- src/mongocxx/lib/mongocxx/v1/instance.cpp | 2 -- 2 files changed, 10 deletions(-) diff --git a/src/mongocxx/include/mongocxx/v1/instance.hpp b/src/mongocxx/include/mongocxx/v1/instance.hpp index 57f4dc20a7..bcba491272 100644 --- a/src/mongocxx/include/mongocxx/v1/instance.hpp +++ b/src/mongocxx/include/mongocxx/v1/instance.hpp @@ -142,14 +142,6 @@ class instance { /// explicit MONGOCXX_ABI_EXPORT_CDECL() instance(v1::default_logger tag); - /// - /// Equivalent to @ref instance(std::unique_ptr handler) when `handler == nullptr`. - /// - /// @see - /// - [Custom Log Handlers (mongoc)](https://mongoc.org/libmongoc/current/unstructured_log.html#custom-log-handlers) - /// - explicit MONGOCXX_ABI_EXPORT_CDECL() instance(std::nullptr_t); - /// /// Errors codes which may be returned by @ref mongocxx::v1::instance. /// diff --git a/src/mongocxx/lib/mongocxx/v1/instance.cpp b/src/mongocxx/lib/mongocxx/v1/instance.cpp index b8fe1cc895..81b07710f1 100644 --- a/src/mongocxx/lib/mongocxx/v1/instance.cpp +++ b/src/mongocxx/lib/mongocxx/v1/instance.cpp @@ -142,8 +142,6 @@ instance::instance(v1::default_logger tag) : _impl{bsoncxx::make_unique(ta (void)tag; } -instance::instance(std::nullptr_t) : instance{std::unique_ptr{}} {} - std::error_category const& instance::error_category() { class type final : public std::error_category { char const* name() const noexcept override { From 4508456df8e64f6981e369985208692e4b6bc82b Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Mon, 29 Sep 2025 15:09:19 -0500 Subject: [PATCH 04/17] Use std::_Exit instead of std::exit in subprocess --- src/mongocxx/test/subprocess.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mongocxx/test/subprocess.cpp b/src/mongocxx/test/subprocess.cpp index eecc18b6bf..e796cd4a8b 100644 --- a/src/mongocxx/test/subprocess.cpp +++ b/src/mongocxx/test/subprocess.cpp @@ -42,21 +42,21 @@ int subprocess(std::function fn, bool* is_signal_ptr) { // Child: do nothing more than call `fn`. if (pid == 0) { - // Use `std::exit()` and `std::terminate()` to prevent continued execution of the Catch2 test suite. + // Use `std::_Exit()` and `std::terminate()` to prevent continued execution of the Catch2 test suite. try { fn(); - std::exit(EXIT_SUCCESS); + std::_Exit(EXIT_SUCCESS); } catch (Catch::TestFailureException) { // Assertion failure already output its diagnostic message. - std::exit(EXIT_FAILURE); + std::_Exit(EXIT_FAILURE); } catch (Catch::TestSkipException) { // SKIP() already output its diagnostic message. // Don't try to propagate the "skip", just treat as equivalent to success. - std::exit(EXIT_SUCCESS); + std::_Exit(EXIT_SUCCESS); } catch (std::exception const& ex) { // Trigger output of Catch2 diagnostic messages. FAIL_CHECK("uncaught exception in subprocess: " << ex.what()); - std::exit(EXIT_FAILURE); + std::_Exit(EXIT_FAILURE); } catch (...) { // Allow default termination handler to translate the unknown exception type. // This should also trigger the output of Catch2 diagnostic messages. From 40218afffdda4feb4dfa39b5adbc20b7a2792d46 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Mon, 29 Sep 2025 15:09:19 -0500 Subject: [PATCH 05/17] Tweak rationale for disabling Valgrind leak checks for subprocesses --- .evergreen/scripts/test.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.evergreen/scripts/test.sh b/.evergreen/scripts/test.sh index cbaa19cb93..59d8373795 100755 --- a/.evergreen/scripts/test.sh +++ b/.evergreen/scripts/test.sh @@ -299,11 +299,9 @@ else "--suppressions=../etc/memcheck.suppressions" ) - # Prevent mongocxx::test::subprocess termination from confusing valgrind. + # Avoid noisy diagnostics caused by deliberate subprocess termination. if [[ "${1:?}" =~ test_instance ]]; then - valgrind_args+=( - "--trace-children=no" - ) + valgrind_args+=("--trace-children=no") fi echo "Running ${1:?}..." From 75d5e9eb8fb6f6ea83e1f6ade96982435c15d273 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 2 Oct 2025 09:19:47 -0500 Subject: [PATCH 06/17] Fix typos in comments and strings --- src/mongocxx/include/mongocxx/v1/instance.hpp | 2 +- src/mongocxx/lib/mongocxx/v1/instance.cpp | 4 ++-- src/mongocxx/test/subprocess.hh | 4 ++-- src/mongocxx/test/v1/logger.cpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mongocxx/include/mongocxx/v1/instance.hpp b/src/mongocxx/include/mongocxx/v1/instance.hpp index bcba491272..9d33d8789b 100644 --- a/src/mongocxx/include/mongocxx/v1/instance.hpp +++ b/src/mongocxx/include/mongocxx/v1/instance.hpp @@ -72,7 +72,7 @@ class instance { /// /// Cleanup the mongocxx (and mongoc) library. /// - /// Calls [`mongoc_init()`](https://mongoc.org/libmongoc/current/mongoc_cleanup.html). + /// Calls [`mongoc_cleanup()`](https://mongoc.org/libmongoc/current/mongoc_cleanup.html). /// MONGOCXX_ABI_EXPORT_CDECL() ~instance(); diff --git a/src/mongocxx/lib/mongocxx/v1/instance.cpp b/src/mongocxx/lib/mongocxx/v1/instance.cpp index 81b07710f1..fb0c4a2306 100644 --- a/src/mongocxx/lib/mongocxx/v1/instance.cpp +++ b/src/mongocxx/lib/mongocxx/v1/instance.cpp @@ -37,8 +37,8 @@ namespace v1 { using code = v1::instance::errc; -static_assert(!std::is_move_constructible::value, "bsoncxx::v1::instance must be non-moveable"); -static_assert(!std::is_copy_constructible::value, "bsoncxx::v1::instance must be non-copyable"); +static_assert(!std::is_move_constructible::value, "mongocxx::v1::instance must be non-moveable"); +static_assert(!std::is_copy_constructible::value, "mongocxx::v1::instance must be non-copyable"); namespace { diff --git a/src/mongocxx/test/subprocess.hh b/src/mongocxx/test/subprocess.hh index 857eab6cbd..9c75bf3f41 100644 --- a/src/mongocxx/test/subprocess.hh +++ b/src/mongocxx/test/subprocess.hh @@ -29,8 +29,8 @@ namespace test { // @param is_signal_ptr When not null, `*is_signal_ptr` is set to `true` if the return value is a signal rather than an // exit code. // -// @returns The exit code of the subprocess (`*is_signal` is `false`) or the signal used to kill the subprocess -// (`*is_signal` is `true`) . +// @returns The exit code of the subprocess (`*is_signal_ptr` is `false`) or the signal used to kill the subprocess +// (`*is_signal_ptr` is `true`) . int subprocess(std::function fn, bool* is_signal_ptr = nullptr); #if !defined(_MSC_VER) diff --git a/src/mongocxx/test/v1/logger.cpp b/src/mongocxx/test/v1/logger.cpp index 6eff3f8b20..2adfb74cf5 100644 --- a/src/mongocxx/test/v1/logger.cpp +++ b/src/mongocxx/test/v1/logger.cpp @@ -38,7 +38,7 @@ struct custom_logger : logger { } // namespace -// mongocxx::v1::logger must not unnecessarily mpose special requirements on derived classes. +// mongocxx::v1::logger must not unnecessarily impose special requirements on derived classes. static_assert(std::is_nothrow_destructible::value, ""); static_assert(std::is_nothrow_move_constructible::value, ""); static_assert(std::is_copy_constructible::value, ""); From cc5339f431bdeada655add7a879ee9e7d3721f6e Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 2 Oct 2025 09:19:47 -0500 Subject: [PATCH 07/17] Fix missing `&is_signal` argument to subprocess --- src/mongocxx/test/subprocess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mongocxx/test/subprocess.cpp b/src/mongocxx/test/subprocess.cpp index e796cd4a8b..57996bfa46 100644 --- a/src/mongocxx/test/subprocess.cpp +++ b/src/mongocxx/test/subprocess.cpp @@ -146,7 +146,7 @@ TEST_CASE("skip", "[mongocxx][test][subprocess]") { (void)::close(2); // stderr SKIP("subprocess"); - }); + }, &is_signal); CHECK_FALSE(is_signal); } From ba058a46f5a74451c6ddbe87d0ca0a66173094d4 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 2 Oct 2025 09:19:47 -0500 Subject: [PATCH 08/17] Simplify v_noabi implementation (capture pre-init logs) --- .../mongocxx/v_noabi/mongocxx/instance.cpp | 32 ++----------------- 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp index fcea33ad15..866f1a8edb 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp @@ -46,41 +46,13 @@ instance* sentinel() { return reinterpret_cast(value); } -void custom_log_handler( - mongoc_log_level_t log_level, - char const* domain, - char const* message, - void* user_data) noexcept { - (*static_cast(user_data))( - static_cast(log_level), - bsoncxx::v1::stdx::string_view(domain), - bsoncxx::v1::stdx::string_view(message)); -} - } // namespace class instance::impl { public: v1::instance _instance; - std::unique_ptr _handler; - - impl(impl&&) = delete; - impl& operator=(impl&&) = delete; - impl(impl const&) = delete; - impl& operator=(impl const&) = delete; - - impl(std::unique_ptr handler) try : _instance{v1::default_logger{}}, _handler{std::move(handler)} { - // For backward compatibility, allow v1::instance to initialize mongoc with the default log handler, then set - // the custom logger AFTER initialization has already completed. - if (_handler) { - libmongoc::log_set_handler(&custom_log_handler, _handler.get()); - - // Inform the user that a custom log handler has been registered. - // Cannot use mocked `libmongoc::log()` due to varargs. - mongoc_log(MONGOC_LOG_LEVEL_INFO, "mongocxx", "libmongoc logging callback enabled"); - } else { - libmongoc::log_set_handler(nullptr, nullptr); - } + + impl(std::unique_ptr handler) try : _instance{std::move(handler)} { } catch (v1::exception const&) { throw v_noabi::logic_error{error_code::k_cannot_recreate_instance}; } From 91b3d62255e484cf53ced9233afabc7bfd3c4a88 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 2 Oct 2025 09:19:47 -0500 Subject: [PATCH 09/17] Document post-move state for instance class --- .../include/mongocxx/v_noabi/mongocxx/instance.hpp | 10 ++++++++-- .../lib/mongocxx/v_noabi/mongocxx/instance.cpp | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/instance.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/instance.hpp index 47d3730623..84a6bc5551 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/instance.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/instance.hpp @@ -48,12 +48,18 @@ class instance { /// /// Move constructs an instance of the driver. /// - MONGOCXX_ABI_EXPORT_CDECL() instance(instance&&) noexcept; + /// @par Postconditions: + /// - `other` is in an assign-or-destroy-only state. + /// + MONGOCXX_ABI_EXPORT_CDECL() instance(instance&& other) noexcept; /// /// Move assigns an instance of the driver. /// - MONGOCXX_ABI_EXPORT_CDECL(instance&) operator=(instance&&) noexcept; + /// @par Postconditions: + /// - `other` is in an assign-or-destroy-only state. + /// + MONGOCXX_ABI_EXPORT_CDECL(instance&) operator=(instance&& other) noexcept; /// /// Destroys an instance of the driver. diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp index 866f1a8edb..45a9ffaab0 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp @@ -64,8 +64,8 @@ instance::instance(std::unique_ptr logger) : _impl{bsoncxx::mak current_instance.store(this, std::memory_order_relaxed); } -instance::instance(instance&&) noexcept = default; -instance& instance::operator=(instance&&) noexcept = default; +instance::instance(instance&& other) noexcept = default; +instance& instance::operator=(instance&& other) noexcept = default; instance::~instance() { current_instance.store(sentinel(), std::memory_order_relaxed); From 20b931c0e1668a4efd9b7e9ddaab5e82d8f91900 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 2 Oct 2025 09:19:47 -0500 Subject: [PATCH 10/17] Document non-copyability for instance class --- .../include/mongocxx/v_noabi/mongocxx/instance.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/instance.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/instance.hpp index 84a6bc5551..e52ecd6c4f 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/instance.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/instance.hpp @@ -66,7 +66,14 @@ class instance { /// MONGOCXX_ABI_EXPORT_CDECL() ~instance(); + /// + /// This class is not copyable. + /// instance(instance const&) = delete; + + /// + /// This class is not copyable. + /// instance& operator=(instance const&) = delete; /// From df7bf53655b0105f61e0cfc027369115f5ee71d5 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 2 Oct 2025 10:15:25 -0500 Subject: [PATCH 11/17] Preserve v_noabi log handler enabled message --- src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp index 45a9ffaab0..c8004b8435 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp @@ -53,6 +53,9 @@ class instance::impl { v1::instance _instance; impl(std::unique_ptr handler) try : _instance{std::move(handler)} { + // Inform the user that a custom log handler has been registered. + // Cannot use `libmongoc::*` mock due to varargs. + mongoc_log(MONGOC_LOG_LEVEL_INFO, "mongocxx", "libmongoc logging callback enabled"); } catch (v1::exception const&) { throw v_noabi::logic_error{error_code::k_cannot_recreate_instance}; } From f24f3b6143d5452cd8271a3fe8f487cca8d689d3 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 2 Oct 2025 10:21:56 -0500 Subject: [PATCH 12/17] Correctly condition v_noabi info message --- .../lib/mongocxx/v_noabi/mongocxx/instance.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp index c8004b8435..1156751c2b 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp @@ -52,13 +52,19 @@ class instance::impl { public: v1::instance _instance; - impl(std::unique_ptr handler) try : _instance{std::move(handler)} { - // Inform the user that a custom log handler has been registered. - // Cannot use `libmongoc::*` mock due to varargs. - mongoc_log(MONGOC_LOG_LEVEL_INFO, "mongocxx", "libmongoc logging callback enabled"); + // mongoc does not expose the state of registered custom log handlers. A bit of indirection is needed to + // condition the informational "enabled" message even after `handler` is moved-from. + impl(std::unique_ptr&& handler, bool set_custom_handler) try : _instance{std::move(handler)} { + if (set_custom_handler) { + // Inform the user that a custom log handler has been registered. + // Cannot use `libmongoc::*` mock due to varargs. + mongoc_log(MONGOC_LOG_LEVEL_INFO, "mongocxx", "libmongoc logging callback enabled"); + } } catch (v1::exception const&) { throw v_noabi::logic_error{error_code::k_cannot_recreate_instance}; } + + explicit impl(std::unique_ptr handler) : impl{std::move(handler), static_cast(handler)} {} }; instance::instance() : instance(nullptr) {} From 0be7784058cb934636c7c8c005061771481260a0 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 2 Oct 2025 10:23:39 -0500 Subject: [PATCH 13/17] Formatting --- src/mongocxx/test/subprocess.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mongocxx/test/subprocess.cpp b/src/mongocxx/test/subprocess.cpp index 57996bfa46..7c6f30ed75 100644 --- a/src/mongocxx/test/subprocess.cpp +++ b/src/mongocxx/test/subprocess.cpp @@ -140,13 +140,15 @@ TEST_CASE("failure", "[mongocxx][test][subprocess]") { TEST_CASE("skip", "[mongocxx][test][subprocess]") { auto is_signal = false; - CHECK_SUBPROCESS([] { - // Try to silence noisy Catch2 output. - (void)::close(1); // stdout - (void)::close(2); // stderr + CHECK_SUBPROCESS( + [] { + // Try to silence noisy Catch2 output. + (void)::close(1); // stdout + (void)::close(2); // stderr - SKIP("subprocess"); - }, &is_signal); + SKIP("subprocess"); + }, + &is_signal); CHECK_FALSE(is_signal); } From 3d276c8e463db4e87763daf472abd7e6e727fda9 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Tue, 21 Oct 2025 14:42:10 -0500 Subject: [PATCH 14/17] Use qualified explicit template specializations --- src/mongocxx/include/mongocxx/v1/exception.hpp | 8 ++------ src/mongocxx/include/mongocxx/v1/instance.hpp | 6 +----- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/mongocxx/include/mongocxx/v1/exception.hpp b/src/mongocxx/include/mongocxx/v1/exception.hpp index 175d1fa8ac..77c63dc08b 100644 --- a/src/mongocxx/include/mongocxx/v1/exception.hpp +++ b/src/mongocxx/include/mongocxx/v1/exception.hpp @@ -115,15 +115,11 @@ BSONCXX_PRIVATE_WARNINGS_POP(); } // namespace v1 } // namespace mongocxx -namespace std { - template <> -struct is_error_condition_enum : true_type {}; +struct std::is_error_condition_enum : true_type {}; template <> -struct is_error_condition_enum : true_type {}; - -} // namespace std +struct std::is_error_condition_enum : true_type {}; #include diff --git a/src/mongocxx/include/mongocxx/v1/instance.hpp b/src/mongocxx/include/mongocxx/v1/instance.hpp index 9d33d8789b..cfd2236b25 100644 --- a/src/mongocxx/include/mongocxx/v1/instance.hpp +++ b/src/mongocxx/include/mongocxx/v1/instance.hpp @@ -172,12 +172,8 @@ class instance { } // namespace v1 } // namespace mongocxx -namespace std { - template <> -struct is_error_code_enum : true_type {}; - -} // namespace std +struct std::is_error_code_enum : true_type {}; #include From 42cac6f917e11bfe84be11561ae2198c4f716ccb Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Tue, 21 Oct 2025 14:42:10 -0500 Subject: [PATCH 15/17] Remove extra doclines --- src/mongocxx/include/mongocxx/v1/logger.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mongocxx/include/mongocxx/v1/logger.hpp b/src/mongocxx/include/mongocxx/v1/logger.hpp index 1223114c5c..5aba2e9316 100644 --- a/src/mongocxx/include/mongocxx/v1/logger.hpp +++ b/src/mongocxx/include/mongocxx/v1/logger.hpp @@ -94,8 +94,6 @@ class logger { /// Users may override this function to implement custom log message behavior such as outputting messages to a file /// or sending messages to a remote server. /// - /// - /// /// @param level The log level for the message being handled. /// @param domain The domain of the message. /// @param message The contents of the log message. From 33c1853ce634a6687e6c1f1eef0777188316611b Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Tue, 21 Oct 2025 14:42:10 -0500 Subject: [PATCH 16/17] Apply clangd IWYU fixes --- src/bsoncxx/lib/bsoncxx/v1/exception.cpp | 1 + src/mongocxx/lib/mongocxx/v1/exception.cpp | 1 + src/mongocxx/lib/mongocxx/v1/instance.cpp | 4 ++++ src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp | 4 ++-- src/mongocxx/test/subprocess.cpp | 1 - src/mongocxx/test/v1/logger.cpp | 2 ++ 6 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/bsoncxx/lib/bsoncxx/v1/exception.cpp b/src/bsoncxx/lib/bsoncxx/v1/exception.cpp index a2a601d6bc..2d655243bc 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/exception.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/exception.cpp @@ -17,6 +17,7 @@ // #include +#include #include #include diff --git a/src/mongocxx/lib/mongocxx/v1/exception.cpp b/src/mongocxx/lib/mongocxx/v1/exception.cpp index 29798beee2..ab29a888c3 100644 --- a/src/mongocxx/lib/mongocxx/v1/exception.cpp +++ b/src/mongocxx/lib/mongocxx/v1/exception.cpp @@ -17,6 +17,7 @@ // #include +#include #include #include diff --git a/src/mongocxx/lib/mongocxx/v1/instance.cpp b/src/mongocxx/lib/mongocxx/v1/instance.cpp index fb0c4a2306..3c6c147213 100644 --- a/src/mongocxx/lib/mongocxx/v1/instance.cpp +++ b/src/mongocxx/lib/mongocxx/v1/instance.cpp @@ -16,11 +16,15 @@ // +#include +#include + #include #include #include #include +#include #include #include #include diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp index 1156751c2b..00eec93c24 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/instance.cpp @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include @@ -54,7 +54,7 @@ class instance::impl { // mongoc does not expose the state of registered custom log handlers. A bit of indirection is needed to // condition the informational "enabled" message even after `handler` is moved-from. - impl(std::unique_ptr&& handler, bool set_custom_handler) try : _instance{std::move(handler)} { + impl(std::unique_ptr&& handler, bool set_custom_handler) try : _instance{std::move(handler)} { if (set_custom_handler) { // Inform the user that a custom log handler has been registered. // Cannot use `libmongoc::*` mock due to varargs. diff --git a/src/mongocxx/test/subprocess.cpp b/src/mongocxx/test/subprocess.cpp index 7c6f30ed75..743fe2f82f 100644 --- a/src/mongocxx/test/subprocess.cpp +++ b/src/mongocxx/test/subprocess.cpp @@ -17,7 +17,6 @@ // #include -#include #include #include diff --git a/src/mongocxx/test/v1/logger.cpp b/src/mongocxx/test/v1/logger.cpp index 2adfb74cf5..bd9f615660 100644 --- a/src/mongocxx/test/v1/logger.cpp +++ b/src/mongocxx/test/v1/logger.cpp @@ -16,6 +16,8 @@ // +#include + #include #include From 4ae0cd904b70bff0154b791e68bd43db1a0dbbe5 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Tue, 21 Oct 2025 14:59:59 -0500 Subject: [PATCH 17/17] Add missing source_errc enumerator to CATCH_REGISTER_ENUM --- src/mongocxx/test/v1/exception.hh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mongocxx/test/v1/exception.hh b/src/mongocxx/test/v1/exception.hh index 402a9bf2bf..4d1accb903 100644 --- a/src/mongocxx/test/v1/exception.hh +++ b/src/mongocxx/test/v1/exception.hh @@ -25,7 +25,8 @@ CATCH_REGISTER_ENUM( mongocxx::v1::source_errc::zero, mongocxx::v1::source_errc::mongocxx, mongocxx::v1::source_errc::mongoc, - mongocxx::v1::source_errc::mongocrypt) + mongocxx::v1::source_errc::mongocrypt, + mongocxx::v1::source_errc::server) CATCH_REGISTER_ENUM( mongocxx::v1::type_errc,