diff --git a/docs/user/netservices/ExclusiveBorrow.dox b/docs/user/netservices/ExclusiveBorrow.dox new file mode 100644 index 0000000000..5fa358080d --- /dev/null +++ b/docs/user/netservices/ExclusiveBorrow.dox @@ -0,0 +1,565 @@ +/* + * Copyright 2021 Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Niels Sascha Reedijk, niels.reedijk@gmail.com + * + * Corresponds to: + * headers/private/netservices2/ExclusiveBorrow.h hrev????? + */ + + +#if __cplusplus >= 201703L + + +/*! + \file ExclusiveBorrow.h + \ingroup netservices + \brief Provides the BExclusiveBorrow smart pointer. +*/ + +namespace BPrivate { + +namespace Network { + + +/*! + \class BBorrowError + \ingroup netservices + \brief Error while handling a BExclusiveBorrow or BBorrow object. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrowError::BBorrowError(const char* origin) + \brief Constructor that sets the \a origin. + + \since Haiku R1 +*/ + + +/*! + \fn virtual const char* BBorrowError::Message() const noexcept override + \brief Get a pointer to the message describing the borrow error. + + \since Haiku R1 +*/ + + +//! \cond INTERNAL + + +/*! + \class BorrowAdmin + \brief Internal class that provides admin block for BExclusiveBorrow and BBorrow. + + This base class provides the admin functions to deal with the underlying objects that are + managed by this smart pointers. It is implemented in such a way that it provides type erasure + for the actual smart objects, to enable them to allow for a BExclusiveBorrow to have a + BBorrow higher up in the class inheritance. + + \since Haiku R1 +*/ + + +/*! + \fn BorrowAdmin::~BorrowAdmin() + \brief Destructor + + \since Haiku R1 +*/ + + +/*! + \fn virtual void BorrowAdmin::Cleanup() noexcept + \brief Hook function that triggers the cleanup of the underlying object. + + Implemented by BorrowPointer + + \since Haiku R1 +*/ + + +/*! + \fn virtual void BorrowAdmin::ReleasePointer() noexcept + \brief Hook function to relinquish ownership of the underlying pointer. + + When BExclusiveBorrow::Release() is called, ownership of the pointer is transferred to the + caller. This hook function allows the BorrowPointer to release the object so that there will + not be a double delete when the BExclusiveBorrow is cleaned up. + + \since Haiku R1 +*/ + + +/*! + \fn BorrowAdmin::BorrowAdmin() + \brief Constructor + + \since Haiku R1 +*/ + + +/*! + \fn void BorrowAdmin::Borrow() + \brief Register that a BBorrow object is created + + \exception BBorrowError In case the object already is borrowed. + + \since Haiku R1 +*/ + + +/*! + \fn void BorrowAdmin::Return() noexcept + \brief Register that the BBorrow object no longer borrows the object. + + This also cleans up the internal object if the corresponding BExclusiveBorrow no longer + exists. + + \since Haiku R1 +*/ + + +/*! + \fn void BorrowAdmin::Forfeit() noexcept + \brief Register that the BExclusiveBorrow object no longer wants to own the object. + + This also cleans up the internal object if there is no BBorrow object. + + \since Haiku R1 +*/ + + +/*! + \fn bool BorrowAdmin::IsBorrowed() noexcept + \brief Check if the current object is borrowed. + + \since Haiku R1 +*/ + + +/*! + \fn void BorrowAdmin::Release() + \brief Cleanup the BorrowAdmin object without cleaning up the underlying object. + + This method is called by BExclusiveBorrow::Release(), where the smart pointer is emptied and + the ownership of the object is transferred to the caller. + + \exception BBorrowError The ownership cannot be released if the object is borrowed. + + \since Haiku R1 +*/ + + +/*! + \class BorrowPointer + \ingroup netservices + \brief Type derived from BorrowAdmin, which administrates the type-specific pointer. + + In order to accomplish type erasure in BExclusiveBorrow and BBorrow, this derived type handles + the actual owned pointer. + + \tparam T The type of the object that the BExclusiveBorrow instance owns. + + \since Haiku R1 +*/ + + +/*! + \fn BorrowPointer::BorrowPointer(T* object) noexcept + \brief Construct a new admin block to manage the \a object. + + \since Haiku R1 +*/ + + +/*! + \fn BorrowPointer::~BorrowPointer() + \brief Destructor that deletes the owned object. + + \since Haiku R1 +*/ + + +/*! + \fn virtual void BorrowPointer::Cleanup() noexcept override + \brief Implementation of the Cleanup() hook function to delete the admin block and free + resources. + + \since Haiku R1 +*/ + + +/*! + \fn virtual void BorrowPointer::ReleasePointer() noexcept override + \brief Implementation of the ReleasePointer() hook function to make sure that the pointer + will not be freed when the object is destroyed. + + \since Haiku R1 +*/ + + +//! \endcond + + +/*! + \class BExclusiveBorrow + \ingroup netservices + \brief Smart pointer that allows shared ownership of an object with exclusive access. + + This smart pointer was designed to support the particular pattern where a non-threadsafe or + non-lockable needs to be shared between two threads, where only one can have access to the + underlying object at the time. + + When creating a new object, the underlying object can be accessed using the dereference + operator overloads as if with any other smart pointer. This ownership can then be borrowed + by creating a \ref BBorrow object. At that stage, the original owner can no longer access the + underlying object, until the borrow is returned. The borrow can access the object as long as + they retain the borrow. The borrow is returned by the borrow object going out of scope, or by + the borrow object being assigned a \c nullptr object. At that stage, the original owner regains + access. + + \code + // Create a newly owned string object. + BExclusiveBorrow owner = make_exclusive_borrow("Initial value"); + + // Access the exclusively accessibe object and set it to a different value + owner->SetTo("New value set by owner"); + + // Create a borrow. + BBorrow borrow = BBorrow(owner); + + try { + owner->SetTo("Another value set by owner"); + } catch (const BorrowError& e) { + // This error will be thrown because the `owner` cannot access the object while borrowed. + } + + try { + BBorrow secondBorrow = BBorrow(owner); + } catch (const BorrowError& e) { + // This error will be thrown because there cannot be more than one borrow at a time. + } + + // The `borrow` has exclusive access to the underlying BString object + borrow->SetTo("A value set by the borrower"); + + // The borrow is returned by explicitly setting it to `nullptr` or by having the object go out + // of scope. + borrow = nullptr; + + // The owner can access the object again + assert(*owner == "A value set by the borrower"); + \endcode + + \par Object Lifetime Management + The BExclusiveBorrow and BBorrow pair manage the lifetime of the underlying object, meaning + the memory will be freed when the object is no longer referenced by either the owner or the + borrower. It is possible to get the ownership of the underlying object through the + \ref BExclusiveBorrow::Release() method. This returns a \c unique_ptr. + + \par Creating New Objects + When creating a BExclusiveBorrow object, you can use the \ref BExclusiveBorrow(T* object) + constructor to create a new smart pointer that takes an \em existing underlying object. Note + that the smart pointer will assume ownership, meaning that you should also have ownership of + that object. If you want to create a BExclusiveBorrow object for a new object, then you can use + the \ref make_exclusive_borrow() function to create a new object. + + \par Move Semantics and Empty Objects + The template class is designed around having an underlying object value, and in most cases will + have an underlying object. However, there may be cases where a BExclusiveOwner or BBorrow + object will not have an internal value. This either happens when it is explicitly assigned an + empty value, or after the object has been moved. You can check whether the object has a value + through the \ref HasValue() method. Trying to access an empty object will throw a + \ref BBorrowError. + + \par Checked Access + The semantics of the exclusive ownership are enforced by this class. The rules are: + - There can only be one owner. The object cannot be copied, only moved. + - There can only be one borrower at a time. The borrow object cannot be copied, only moved. + - If one tries to create an additional borrow, an exception is thrown. + - If an object is borrowed, accessing it through the owner will throw exceptions. + + \par Casting Pointers between Owner and Borrower + For some design patterns, you may want to be able to cast the type of the owner to a related + type for the borrower. For example, the Network Services kit accepts a \c BBorrow type + in order to allow the user to specify where to write the content of a network request to. The + \ref BDataIO itself is an abstract interface to read and write data from an object. A user + will most likely use a \ref BFile or \ref BMallocIO as underlying objects, both of which + have \ref BDataIO as their base class. + + \par + Due to the specialized constructor of \ref BBorrow, it is possible to cast between compatible + pointer types, without loosing the advantages of properly cleaning up the object when the + borrow and the owner go out of scope. In the internals of the template, a type erasure + technique similar to that of \c std::shared_ptr is used. + + \code + // Create a new BFile object, which inherits the BDataIO class. + BExclusiveBorrow owner = make_exclusive_borrow("path/to/file", B_READ_WRITE); + // The following succeeds because a BFile pointer can be assigned to a BDataIO pointer. + BBorrow borrow = BBorrow(owner); + \endcode + + \par Multithread Safety, and Performance Cost + This smart object uses atomics to synchronize the ownership and borrows of the object, and to + enforce all the checks that were mentioned previously. The atomics guarantee that when you + want to access the object in BExclusiveBorrow, that this only succeeds after any outstanding + borrow is completed, otherwise an exception is thrown. While atomics can be used as a method of + synchronization, this templace class is \em not designed for that and it does not have the + tools to help doing that. If you need to synchronize object access between through threads, you + should use semaphores or thread joins instead. + + \tparam T The type of object for this smart pointer. + + \since Haiku R1 +*/ + +/*! + \fn BExclusiveBorrow::BExclusiveBorrow() noexcept + \brief Create a new smart pointer with no value. + + \since Haiku R1 +*/ + + +/*! + \fn BExclusiveBorrow::BExclusiveBorrow(nullptr_t) noexcept + \brief Special constructor that creates a new smart pointer with no value. + + \since Haiku R1 +*/ + + +/*! + \fn BExclusiveBorrow::BExclusiveBorrow(T* object) + \brief Create a new smart pointer that takes ownership of the \a object. + + \param object The object to wrap inside this smart pointer. + + \exception std::bad_alloc In case there are issues allocating memory for the internals of + the smart pointer. + + \since Haiku R1 +*/ + + +/*! + \fn BExclusiveBorrow::~BExclusiveBorrow() + \brief Destructor. + + If the smart pointer is not empty, the underlying object will be deleted if there no longer + is a borrow accessing it. + + \since Haiku R1 +*/ + + +/*! + \fn BExclusiveBorrow::BExclusiveBorrow(BExclusiveBorrow&& other) noexcept + \brief Move constructor. + + \param other The object to move from. It will be left empty after the move. + + \since Haiku R1 +*/ + + +/*! + \fn BExclusiveBorrow& BExclusiveBorrow::operator=(BExclusiveBorrow&& other) noexcept + \brief Move assignment. + + \param other The object to move from. It will be left empty after the move. + + \since Haiku R1 +*/ + + +/*! + \fn bool BExclusiveBorrow::HasValue() const noexcept + \brief Check if the object has a value or is empty. + + \since Haiku R1 +*/ + + +/*! + \fn T& BExclusiveBorrow::operator*() const + \brief Dereferences the pointer. + + \exception BBorrowError This exception is raised if the object is borrowed, or if it is empty. + + \since Haiku R1 +*/ + + +/*! + \fn T* BExclusiveBorrow::operator->() const + \brief Dereferences the pointer. + + \exception BBorrowError This exception is raised if the object is borrowed, or if it is empty. + + \since Haiku R1 +*/ + + +/*! + \fn std::unique_ptr BExclusiveBorrow::Release() + \brief Returns a unique_ptr of the inner object and releases the ownership. + + \exception BBorrowError This exception is raised if the object is borrowed, or if it is empty. + + \since Haiku R1 +*/ + + +/*! + \class BBorrow + \ingroup netservices + \brief Smart pointer that borrows an object from a \ref BExclusiveBorrow owner. + + The BBorrow smart pointer is the accompanyment to the \ref BExclusiveBorrow owner object. See + the documentation on that template class on how to use the smart pointer pairs to express and + enforce exclusive ownership between the owner and the borrower. + + Like a BExclusiveBorrow object, a BBorrow object can either have a borrow or be empty. When it + is empty, it means the current object is not borrowing anything at that moment. Any calls to + access the underlying data will fail in that case. + + \tparam T The type of object that is owned by this smart pointer. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrow::BBorrow() noexcept + \brief Create a new smart pointer with no value. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrow::BBorrow(nullptr_t) noexcept + \brief Special constructor that builds an empty borrow object. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrow::BBorrow(BExclusiveBorrow

& owner) + \brief Construct a borrowed object from the \a owner. + + \param owner The owner to borrow from. + + \exception BBorrowError In case the \a owner already borrowed their object, or in case the + \a owner is an empty object, as you cannot borrow something that is not there. + + \tparam T The type of object for this BBorrow object. + \tparam P The type of objedt for the BExclusiveBorrow object. This allows you to have different + types between the owner and the borrower, with the requirement that a pointer to type P can + be cast to a pointer of type T without issue. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrow::BBorrow(BBorrow&& other) noexcept + \brief Move constructor. + + \param other The object to move from. It will be left empty after the move. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrow& BBorrow::operator=(BBorrow&& other) noexcept + \brief Move assignment. + + \param other The object to move from. It will be left empty after the move. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrow::~BBorrow() + \brief Destructor that returns the object to the original owner. + + If the original owner no longer exists, the underlying object will be deleted. + + \since Haiku R1 +*/ + + +/*! + \fn bool BBorrow::HasValue() const noexcept + \brief Check if the object has a value or is empty. + + \since Haiku R1 +*/ + + +/*! + \fn T& BBorrow::operator*() const + \brief Dereference operator. + + \exception BBorrowError When the smart pointer is empty and there is no object to access. + + \since Haiku R1 +*/ + + +/*! + \fn T* BBorrow::operator->() const + \brief Dereference operator. + + \exception BBorrowError When the smart pointer is empty and there is no object to access. + + \since Haiku R1 +*/ + + +/*! + \fn void BBorrow::Return() noexcept + \brief Return object to the owner. + + The current object will be set to be an empty object after this call. If the object is already + empty, this call will not do anything. If the owner no longer exists, the object will be + disposed off. + + \since Haiku R1 +*/ + + +/*! + \fn BExclusiveBorrow make_exclusive_borrow(_Args&& ...__args) + \ingroup netservices + \brief Create a new object that is managed by a BExclusiveBorrow smart pointer. + + This is a convenience template function to the likes of \c std::make_unique(). It allows you to + directly create the \ref BExclusiveBorrow smart pointer around a newly allocated object. + + \tparam T The type of the object that will be created. + \tparam _Args Arguments to be passed to the constructor of T. + + \exception std::bad_alloc In case there are issues allocating the new object. + \exception ... Any other exception that is thrown by the constructor of the object T. + + \since Haiku R1 +*/ + + +} // namespace Network + +} // namespace BPrivate + +# endif diff --git a/headers/private/netservices2/ExclusiveBorrow.h b/headers/private/netservices2/ExclusiveBorrow.h new file mode 100644 index 0000000000..04aaa63ddb --- /dev/null +++ b/headers/private/netservices2/ExclusiveBorrow.h @@ -0,0 +1,345 @@ +/* + * Copyright 2022 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ + +#ifndef _B_EXCLUSIVE_BORROW_H +#define _B_EXCLUSIVE_BORROW_H + +#include +#include + +#include + +namespace BPrivate { + +namespace Network { + + +class BBorrowError : public BError { +public: + BBorrowError(const char* origin) + : BError(origin) + { + + } + + virtual const char* + Message() const noexcept override + { + return "BBorrowError"; + } +}; + + +class BorrowAdmin { +private: + static constexpr uint8 kOwned = 0x1; + static constexpr uint8 kBorrowed = 0x2; + std::atomic fState = kOwned; + +protected: + virtual ~BorrowAdmin() = default; + + virtual void Cleanup() noexcept {}; + virtual void ReleasePointer() noexcept {}; +public: + BorrowAdmin() noexcept + { + + } + + + void + Borrow() + { + auto alreadyBorrowed = (fState.fetch_or(kBorrowed) & kBorrowed) == kBorrowed; + if (alreadyBorrowed) { + throw BBorrowError(__PRETTY_FUNCTION__); + } + } + + + void + Return() noexcept + { + auto cleanup = (fState.fetch_and(~kBorrowed) & kOwned) != kOwned; + if (cleanup) + this->Cleanup(); + } + + + void + Forfeit() noexcept + { + auto cleanup = (fState.fetch_and(~kOwned) & kBorrowed) != kBorrowed; + if (cleanup) + this->Cleanup(); + } + + + bool + IsBorrowed() noexcept + { + return (fState.load() & kBorrowed) == kBorrowed; + } + + + void + Release() + { + if ((fState.load() & kBorrowed) == kBorrowed) + throw BBorrowError(__PRETTY_FUNCTION__); + this->ReleasePointer(); + this->Cleanup(); + } +}; + + +template +class BorrowPointer : public BorrowAdmin +{ +public: + BorrowPointer(T* object) noexcept + : fPtr(object) + { + + } + + virtual ~BorrowPointer() { + delete fPtr; + } + +protected: + virtual void + Cleanup() noexcept override + { + delete this; + } + + virtual void + ReleasePointer() noexcept override + { + fPtr = nullptr; + } + +private: + T* fPtr; +}; + + +template +class BExclusiveBorrow { + template + friend class BBorrow; + + T* fPtr = nullptr; + BorrowAdmin* fAdminBlock = nullptr; + +public: + BExclusiveBorrow() noexcept + { + + } + + + BExclusiveBorrow(nullptr_t) noexcept + { + + } + + + BExclusiveBorrow(T* object) + { + fAdminBlock = new BorrowPointer(object); + fPtr = object; + } + + ~BExclusiveBorrow() + { + if (fAdminBlock) + fAdminBlock->Forfeit(); + } + + BExclusiveBorrow(const BExclusiveBorrow&) = delete; + + + BExclusiveBorrow& operator=(const BExclusiveBorrow&) = delete; + + + BExclusiveBorrow(BExclusiveBorrow&& other) noexcept + { + if (fAdminBlock) + fAdminBlock->Forfeit(); + fAdminBlock = other.fAdminBlock; + fPtr = other.fPtr; + other.fAdminBlock = nullptr; + other.fPtr = nullptr; + } + + + BExclusiveBorrow& + operator=(BExclusiveBorrow&& other) noexcept + { + if (fAdminBlock) + fAdminBlock->Forfeit(); + fAdminBlock = other.fAdminBlock; + fPtr = other.fPtr; + other.fAdminBlock = nullptr; + other.fPtr = nullptr; + return *this; + } + + + bool + HasValue() const noexcept + { + return bool(fPtr); + } + + + T& + operator*() const + { + if (fAdminBlock && !fAdminBlock->IsBorrowed()) + return *fPtr; + throw BBorrowError(__PRETTY_FUNCTION__); + } + + + T* + operator->() const + { + if (fAdminBlock && !fAdminBlock->IsBorrowed()) + return fPtr; + throw BBorrowError(__PRETTY_FUNCTION__); + } + + + std::unique_ptr + Release() + { + if (!fAdminBlock) + throw BBorrowError(__PRETTY_FUNCTION__); + fAdminBlock->Release(); + auto retval = std::unique_ptr(fPtr); + fPtr = nullptr; + fAdminBlock = nullptr; + return retval; + } +}; + + +template +class BBorrow { + T* fPtr = nullptr; + BorrowAdmin* fAdminBlock = nullptr; + +public: + BBorrow() noexcept + { + + } + + + BBorrow(nullptr_t) noexcept + { + + } + + + template + explicit BBorrow(BExclusiveBorrow

& owner) + : fPtr(owner.fPtr), fAdminBlock(owner.fAdminBlock) + { + fAdminBlock->Borrow(); + } + + + BBorrow(const BBorrow&) = delete; + + + BBorrow& operator=(const BBorrow&) = delete; + + + BBorrow(BBorrow&& other) noexcept + : fPtr(other.fPtr), fAdminBlock(other.fAdminBlock) + { + other.fPtr = nullptr; + other.fAdminBlock = nullptr; + } + + + BBorrow& + operator=(BBorrow&& other) noexcept + { + if (fAdminBlock) + fAdminBlock->Return(); + + fPtr = other.fPtr; + fAdminBlock = other.fAdminBlock; + other.fPtr = nullptr; + other.fAdminBlock = nullptr; + return *this; + } + + + ~BBorrow() + { + if (fAdminBlock) + fAdminBlock->Return(); + } + + + bool + HasValue() const noexcept + { + return bool(fPtr); + } + + + T& + operator*() const + { + if (fPtr) + return *fPtr; + throw BBorrowError(__PRETTY_FUNCTION__); + } + + + T* + operator->() const + { + if (fPtr) + return fPtr; + throw BBorrowError(__PRETTY_FUNCTION__); + } + + + void + Return() noexcept + { + if (fAdminBlock) + fAdminBlock->Return(); + fAdminBlock = nullptr; + fPtr = nullptr; + } +}; + + +template +BExclusiveBorrow +make_exclusive_borrow(_Args&& ...__args) +{ + auto guardedObject = std::make_unique(std::forward<_Args>(__args)...); + auto retval = BExclusiveBorrow(guardedObject.get()); + guardedObject.release(); + return retval; +} + + +} // namespace Network + +} // namespace BPrivate + +#endif // _B_EXCLUSIVE_BORROW_H diff --git a/src/tests/kits/net/netservices2/ExclusiveBorrowTest.cpp b/src/tests/kits/net/netservices2/ExclusiveBorrowTest.cpp new file mode 100644 index 0000000000..a264165cb6 --- /dev/null +++ b/src/tests/kits/net/netservices2/ExclusiveBorrowTest.cpp @@ -0,0 +1,218 @@ +/* + * Copyright 2022 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Niels Sascha Reedijk, niels.reedijk@gmail.com + */ + +#include "ExclusiveBorrowTest.h" + +#include +#include + +#include +#include + +#include + +using BPrivate::Network::BBorrow; +using BPrivate::Network::BBorrowError; +using BPrivate::Network::BExclusiveBorrow; +using BPrivate::Network::make_exclusive_borrow; + + +class DeleteTestHelper +{ +public: + DeleteTestHelper(std::atomic& deleted) + : fDeleted(deleted) + { + + } + + ~DeleteTestHelper() + { + fDeleted.store(true); + } + +private: + std::atomic& fDeleted; +}; + + +class Base { +public: + Base() + { + + } + + + virtual ~Base() + { + + } + + + virtual bool IsDerived() + { + return false; + } +}; + + +class Derived : public Base { +public: + Derived() { + + } + + + virtual ~Derived() { + + } + + + virtual bool IsDerived() override + { + return true; + } +}; + + +ExclusiveBorrowTest::ExclusiveBorrowTest() +{ + +} + + +void +ExclusiveBorrowTest::ObjectDeleteTest() +{ + // Case 1: object never gets borrowed and goes out of scope + std::atomic deleted = false; + { + auto object = make_exclusive_borrow(deleted); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE("(1) Expected object to be deleted", true, deleted.load()); + + // Case 2: object gets borrowed, returned and then goes out of scope + deleted.store(false); + { + auto object = make_exclusive_borrow(deleted); + { + auto borrow = BBorrow(object); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE("(2) Object should not be deleted", false, deleted.load()); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE("(2) Expected object to be deleted", true, deleted.load()); + + // Case 3: object gets borrowed, forfeited and then borrow goes out of scope + deleted.store(false); + { + auto borrow = BBorrow(nullptr); + { + auto object = make_exclusive_borrow(deleted); + borrow = BBorrow(object); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE("(3) Object should not be deleted", false, deleted.load()); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE("(3) Expected object to be deleted", true, deleted.load()); +} + + +void +ExclusiveBorrowTest::OwnershipTest() +{ + auto ownedObject = make_exclusive_borrow(1); + CPPUNIT_ASSERT(*ownedObject == 1); + + auto borrow = BBorrow(ownedObject); + try { + std::ignore = *ownedObject == 1; + CPPUNIT_FAIL("Unexpected access to the owned object while borrowed"); + } catch (const BBorrowError& e) { + // expected + } + + try { + std::ignore = *borrow == 1; + // should succeed + } catch (const BBorrowError& e) { + CPPUNIT_FAIL("Unexpected error accessing the borrowed object"); + } + + try { + auto borrowAgain = BBorrow(ownedObject); + CPPUNIT_FAIL("Unexpectedly able to borrow the owned object again"); + } catch (const BBorrowError& e) { + // expected + } + + try { + borrow = BBorrow(nullptr); + std::ignore = *borrow == 1; + CPPUNIT_FAIL("Unexpected access to an empty borrowed object"); + } catch (const BBorrowError& e) { + // expected + } + + try { + std::ignore = *ownedObject == 1; + } catch (const BBorrowError& e) { + CPPUNIT_FAIL("Unexpected error accessing the owned object"); + } +} + + +void +ExclusiveBorrowTest::PolymorphismTest() +{ + auto owned = make_exclusive_borrow(); + { + auto borrowDerived = BBorrow(owned); + CPPUNIT_ASSERT_EQUAL(true, borrowDerived->IsDerived()); + } + { + auto borrowBase = BBorrow(owned); + CPPUNIT_ASSERT_EQUAL(true, borrowBase->IsDerived()); + } +} + + +void +ExclusiveBorrowTest::ReleaseTest() +{ + auto ownedObject = make_exclusive_borrow(1); + auto ownedPointer = std::addressof(*ownedObject); + try { + auto borrow = BBorrow(ownedObject); + auto invalidClaimedPointer = ownedObject.Release(); + CPPUNIT_FAIL("Unexpectedly able to release a borrowed pointer"); + } catch (const BBorrowError&) { + // expected to fail + } + + auto validClaimedPointer = ownedObject.Release(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Expected released pointer to point to the same object", + validClaimedPointer.get(), ownedPointer); +} + + +/* static */ void +ExclusiveBorrowTest::AddTests(BTestSuite& parent) +{ + CppUnit::TestSuite& suite = *new CppUnit::TestSuite("ExclusiveBorrowTest"); + + suite.addTest(new CppUnit::TestCaller( + "ExclusiveBorrowTest::ObjectDeleteTest", &ExclusiveBorrowTest::ObjectDeleteTest)); + suite.addTest(new CppUnit::TestCaller( + "ExclusiveBorrowTest::OwnershipTest", &ExclusiveBorrowTest::OwnershipTest)); + suite.addTest(new CppUnit::TestCaller( + "ExclusiveBorrowTest::PolymorphismTest", &ExclusiveBorrowTest::PolymorphismTest)); + suite.addTest(new CppUnit::TestCaller( + "ExclusiveBorrowTest::ReleaseTest", &ExclusiveBorrowTest::ReleaseTest)); + + parent.addTest("ExclusiveBorrowTest", &suite); +} diff --git a/src/tests/kits/net/netservices2/ExclusiveBorrowTest.h b/src/tests/kits/net/netservices2/ExclusiveBorrowTest.h new file mode 100644 index 0000000000..ca8c8d27d0 --- /dev/null +++ b/src/tests/kits/net/netservices2/ExclusiveBorrowTest.h @@ -0,0 +1,26 @@ +/* + * Copyright 2021 Haiku, inc. + * Distributed under the terms of the MIT License. + */ +#ifndef EXCLUSIVE_BORROW_TEST_H +#define EXCLUSIVE_BORROW_TEST_H + +#include +#include + + +class ExclusiveBorrowTest: public BTestCase { +public: + ExclusiveBorrowTest(); + + void ObjectDeleteTest(); + void OwnershipTest(); + void PolymorphismTest(); + void ReleaseTest(); + + static void AddTests(BTestSuite& suite); +}; + + + +#endif // EXCLUSIVE_BORROW_TEST_H diff --git a/src/tests/kits/net/netservices2/Jamfile b/src/tests/kits/net/netservices2/Jamfile index 309a14fd3d..f4fba5b74f 100644 --- a/src/tests/kits/net/netservices2/Jamfile +++ b/src/tests/kits/net/netservices2/Jamfile @@ -7,6 +7,7 @@ if $(TARGET_PACKAGING_ARCH) != x86_gcc2 { UnitTestLib netservicekit2test.so : ServicesKitTestAddon.cpp + ExclusiveBorrowTest.cpp HttpDebugLogger.cpp HttpProtocolTest.cpp TestServer.cpp diff --git a/src/tests/kits/net/netservices2/ServicesKitTestAddon.cpp b/src/tests/kits/net/netservices2/ServicesKitTestAddon.cpp index b8aa49620d..645d2dcbed 100644 --- a/src/tests/kits/net/netservices2/ServicesKitTestAddon.cpp +++ b/src/tests/kits/net/netservices2/ServicesKitTestAddon.cpp @@ -7,6 +7,7 @@ #include #include +#include "ExclusiveBorrowTest.h" #include "HttpProtocolTest.h" @@ -15,6 +16,7 @@ getTestSuite() { BTestSuite* suite = new BTestSuite("NetServices2Kit"); + ExclusiveBorrowTest::AddTests(*suite); HttpProtocolTest::AddTests(*suite); HttpIntegrationTest::AddTests(*suite);