lumiera_/tests/library/meta/virtual-copy-support-test.cpp

549 lines
16 KiB
C++

/*
VirtualCopySupport(Test) - copy and clone type-erased objects
Copyright (C) Lumiera.org
2015, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
#include "lib/test/run.hpp"
#include "lib/format-string.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/meta/virtual-copy-support.hpp"
#include "lib/util.hpp"
#include <iostream>
#include <string>
#include <type_traits>
using util::_Fmt;
using util::isnil;
using std::string;
using std::cout;
using std::endl;
using lumiera::error::LUMIERA_ERROR_LOGIC;
using lumiera::error::LUMIERA_ERROR_WRONG_TYPE;
namespace lib {
namespace meta {
namespace test {
namespace { // Test fixture...
class Interface;
/** helper: shortened type display */
string
typeID(Interface const& obj)
{
string typeStr = lib::test::demangleCxx(
lib::test::showType(obj));
size_t pos = typeStr.rfind("::");
if (pos != string::npos)
typeStr = typeStr.substr(pos+2);
return typeStr;
}
int _CheckSum_ = 0;
/** Interface for the Virtual copy operations.
* @remarks we define this explicitly here for the tests solely.
* In real use, you'd just mix in VirtualCopySupportInterface.
* But since we want to verify the text fixture in isolation,
* we use here empty base implementations instead of pure
* virtual functions, so we can always instantiate all
* test classes.
*/
class CopyInterface
{
public:
virtual void copyInto (void* ) const { /* NOP */ };
virtual void moveInto (void* ) { /* NOP */ };
virtual void copyInto (Interface&) const { /* NOP */ };
virtual void moveInto (Interface&) { /* NOP */ };
};
/**
* The official Interface for our test class hierarchy
*/
class Interface
: public CopyInterface
{
public:
virtual ~Interface() { }
virtual operator string() const =0;
virtual bool empty() const =0;
};
/**
* implementation class with "special" memory layout
*/
template<uint i>
class Sub
: public Interface
{
static_assert (0 < i, "invalid construction");
char storage_[i];
char&
access()
{
return storage_[i-1];
}
char const&
access() const
{
return storage_[i-1];
}
public: /* == Implementation of the Interface == */
virtual operator string() const override
{
return _Fmt("Sub|%s|%d|-%s")
% typeID(*this)
% i
% access();
}
virtual bool
empty() const
{
return !bool(access());
}
public: /* == full set of copy and assignment operations == */
~Sub()
{
_CheckSum_ -= access();
}
Sub()
{
access() = 'A' + rand() % 23;
_CheckSum_ += access();
}
Sub (Sub const& osub)
{
access() = osub.access();
_CheckSum_ += access();
}
Sub (Sub&& rsub)
{
access() = 0;
std::swap(access(),rsub.access());
}
Sub&
operator= (Sub const& osub)
{
if (!util::isSameObject (*this, osub))
{
_CheckSum_ -= access();
access() = osub.access();
_CheckSum_ += access();
}
return *this;
}
Sub&
operator= (Sub&& rsub)
{
_CheckSum_ -= access();
access() = 0;
std::swap(access(),rsub.access());
return *this;
}
};
/* == create various flavours of copyable / noncopyable objects == */
template<char c>
class Regular
: public Sub<c>
{ };
template<char c>
class UnAssignable
: public Sub<c>
{
void operator= (UnAssignable const&); // private and unimplemented
public:
UnAssignable() = default;
UnAssignable(UnAssignable&&) = default;
UnAssignable(UnAssignable const&) = default;
};
template<char c>
class OnlyMovable
: public UnAssignable<c>
{
public:
OnlyMovable (OnlyMovable const&) = delete;
OnlyMovable() = default;
OnlyMovable(OnlyMovable&&) = default;
};
template<char c>
class Noncopyable
: public OnlyMovable<c>
{
public:
Noncopyable (Noncopyable&&) = delete;
Noncopyable () = default;
};
/* == concrete implementation subclass with virtual copy support == */
template<class IMP>
class Opaque //-----Interface| CRTP-Impl | direct Baseclass
: public CopySupport<IMP>::template Policy<Interface, Opaque<IMP>, IMP>
{
public:
static Opaque&
downcast (Interface& bas)
{
Opaque* impl = dynamic_cast<Opaque*> (&bas);
if (!impl)
throw error::Logic("virtual copy works only on instances "
"of the same concrete implementation class"
,error::LUMIERA_ERROR_WRONG_TYPE);
else
return *impl;
}
};
// == Test subject(s)==========================
using RegularImpl = Opaque<Regular<'a'>>;
using ClonableImpl = Opaque<UnAssignable<'b'>>;
using MovableImpl = Opaque<OnlyMovable<'c'>>;
using ImobileImpl = Opaque<Noncopyable<'d'>>;
}//(End)Test fixture
// GCC 4.7 workaround
// SFINAE does not work properly on private functions
// instead of dropping the template instance, it causes compilation failure
}//now in namespace meta
template<char c>
struct can_use_assignment<test::UnAssignable<c>>
{ static constexpr bool value = false; };
namespace test {
/**********************************************************************************************//**
* @test verify a mechanism to allow for cloning and placement new of opaque, type-erased entities.
* Basically we allow only assignments and copy between objects of the same concrete type,
* but we want to initiate those operations from the base interface, without any further
* knowledge about the actual types involved.
*/
class VirtualCopySupport_test : public Test
{
virtual void
run (Arg)
{
CHECK(0 == _CheckSum_);
verify_TestFixture();
CHECK(0 == _CheckSum_);
verify_fullVirtualCopySupport();
verify_noAssignementSupport();
verify_onlyMovableSupport();
verify_disabledCopySupport();
CHECK(0 == _CheckSum_);
}
/** @test our test fixture is comprised of
* - a common interface (#Interface)
* - a implementation template #Sub to hold a buffer and
* manage a distinct random value at some position in that buffer,
* which depends on the concrete implementation type
* - layered on top are adapters to make this implementation class
* either fully copyable, non-assignable, only movable or noncopyable.
* - a global checksum, based on the random value of all instances,
* which are incremented on construction and decremented on destruction.
* After destroying everything this checksum should go to zero.
* This test case just verifies this implementation mechanic.
*/
void
verify_TestFixture()
{
/* == full copy, move and assignment == */
Regular<'A'> a;
Regular<'A'> aa(a);
Regular<'A'> a1;
cout << string(a) <<endl
<< string(aa)<<endl
<< string(a1)<<endl;
a1 = a;
CHECK (string(a) == string(aa));
CHECK (string(a) == string(a1));
CHECK (!isnil(a1));
a = std::move(a1);
CHECK (isnil(a1));
CHECK (string(a) == string(aa));
/* == interface vs. concrete class == */
Regular<'B'> b;
Interface& ii = b;
string prevID(b);
ii = a; // Copy operation of Base-Interface is NOP
CHECK (string(b) == prevID);
/* == assignment inhibited == */
UnAssignable<'C'> c;
UnAssignable<'C'> cc(c);
CHECK (string(c) == string(cc));
prevID = cc;
UnAssignable<'C'> ccc(std::move(cc));
cout << string(cc) <<endl
<< string(ccc)<<endl;
CHECK (string(ccc) == prevID);
CHECK (string(cc) != prevID);
CHECK (!isnil(ccc));
CHECK (isnil (cc)); // killed by moving away
// c = cc; // does not compile
/* == only move construction allowed == */
OnlyMovable<'D'> d;
OnlyMovable<'D'> dd (std::move(d));
cout << string(d) <<endl
<< string(dd)<<endl;
CHECK (string(dd) != string(d));
CHECK (!isnil(dd));
CHECK (isnil(d));
// OnlyMovable<'D'> d1 (dd); // does not compile
// d = dd; // does not compile;
/* == all copy/assignment inhibited == */
Noncopyable<'E'> e;
// Noncopyable<'E'> ee (e); // does not compile
// Noncopyable<'E'> eee (std::move (e)); // does not compile
// e = Noncopyable<'E'>(); // does not compile
CHECK (!isnil (e));
}
void
verify_fullVirtualCopySupport()
{
RegularImpl a,aa,aaa;
Interface& i(a);
Interface& ii(aa);
Interface& iii(aaa);
char storage[sizeof(RegularImpl)];
Interface& iiii (*reinterpret_cast<Interface*> (&storage));
string prevID = a;
CHECK (!isnil (a));
i.moveInto(&storage);
CHECK (string(iiii) == prevID);
CHECK (!isnil(iiii));
CHECK ( isnil(i));
ii.copyInto(i);
CHECK (!isnil(i));
CHECK (!isnil(ii));
CHECK (string(i) == string(ii));
prevID = iii;
iii.moveInto(ii);
CHECK (!isnil(ii));
CHECK ( isnil(iii));
CHECK (string(ii) == prevID);
// Verify that type mismatch in assignment is detected...
Opaque<Regular<'!'>> divergent;
Interface& evil(divergent);
VERIFY_ERROR (WRONG_TYPE, evil.copyInto(i));
VERIFY_ERROR (WRONG_TYPE, evil.moveInto(i));
cout << "==fullVirtualCopySupport=="<<endl
<< string(i) <<endl
<< string(ii) <<endl
<< string(iii) <<endl
<< string(iiii)<<endl;
//need to clean-up the placement-new instance explicitly
iiii.~Interface();
}
void
verify_noAssignementSupport()
{
ClonableImpl b,bb,bbb;
Interface& i(b);
Interface& ii(bb);
Interface& iii(bbb);
char storage[sizeof(ClonableImpl)];
Interface& iiii (*reinterpret_cast<Interface*> (&storage));
string prevID = b;
CHECK (!isnil (b));
i.moveInto(&storage);
CHECK (string(iiii) == prevID);
CHECK (!isnil(iiii));
CHECK ( isnil(i));
iiii.~Interface(); //clean-up previously placed instance
prevID = ii;
ii.copyInto(&storage);
CHECK (!isnil(ii));
CHECK (!isnil(iiii));
CHECK ( isnil(i));
CHECK (string(iiii) == prevID);
CHECK (string(ii) == prevID);
prevID = iii;
VERIFY_ERROR (LOGIC, iii.copyInto(ii));
VERIFY_ERROR (LOGIC, iii.moveInto(ii));
CHECK (string(iii) == prevID);
CHECK (!isnil(iii));
cout << "==noAssignementSupport=="<<endl
<< string(i) <<endl
<< string(ii) <<endl
<< string(iii) <<endl
<< string(iiii)<<endl;
//clean-up placement-new instance
iiii.~Interface();
}
void
verify_onlyMovableSupport()
{
MovableImpl c,cc;
Interface& i(c);
Interface& ii(cc);
char storage[sizeof(MovableImpl)];
Interface& iiii (*reinterpret_cast<Interface*> (&storage));
string prevID = i;
CHECK (!isnil (i));
i.moveInto(&storage);
CHECK (string(iiii) == prevID);
CHECK (!isnil(iiii));
CHECK ( isnil(i));
prevID = ii;
VERIFY_ERROR (LOGIC, ii.copyInto(&storage));
VERIFY_ERROR (LOGIC, ii.copyInto(i));
VERIFY_ERROR (LOGIC, ii.moveInto(i));
CHECK (string(ii) == prevID);
CHECK (!isnil(ii));
CHECK ( isnil(i));
cout << "==onlyMovableSupport=="<<endl
<< string(i) <<endl
<< string(ii) <<endl
<< string(iiii)<<endl;
//clean-up placement-new instance
iiii.~Interface();
}
void
verify_disabledCopySupport()
{
ImobileImpl d,dd;
Interface& i(d);
Interface& ii(dd);
char storage[sizeof(ImobileImpl)];
CHECK (!isnil (i));
string prevID = ii;
VERIFY_ERROR (LOGIC, ii.copyInto(&storage));
VERIFY_ERROR (LOGIC, ii.moveInto(&storage));
VERIFY_ERROR (LOGIC, ii.copyInto(i));
VERIFY_ERROR (LOGIC, ii.moveInto(i));
CHECK (string(ii) == prevID);
CHECK (!isnil(ii));
CHECK (!isnil (i));
cout << "==disabledCopySupport=="<<endl
<< string(i) <<endl
<< string(ii) <<endl;
//no clean-up,
//since we never created anything in the storage buffer
}
};
/** Register this test class... */
LAUNCHER (VirtualCopySupport_test, "unit common");
}}} // namespace lib::meta::test