/* VirtualCopySupport(Test) - copy and clone type-erased objects Copyright (C) 2015, Hermann Vosseler   **Lumiera** 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. See the file COPYING for further details. * *****************************************************************/ /** @file virtual-copy-support-test.cpp ** unit test \ref VirtualCopySupport_test */ #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/format-string.hpp" #include "lib/format-cout.hpp" #include "lib/util.hpp" #include #include using util::_Fmt; using util::isnil; using std::string; using LERR_(LOGIC); using LERR_(WRONG_TYPE); namespace lib { namespace meta { namespace test { namespace { // Test fixture... int _CheckSum_ = 0; class Interface; /** 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 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") % util::typeStr(this) % i % access(); } virtual bool empty() const { return not access(); } public: /* == full set of copy and assignment operations == */ ~Sub() { _CheckSum_ -= access(); } Sub() { access() = 'A' + rani(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 class Regular : public Sub { }; template class UnAssignable : public Sub { void operator= (UnAssignable const&); // private and unimplemented public: UnAssignable() = default; UnAssignable(UnAssignable&&) = default; UnAssignable(UnAssignable const&) = default; }; template class OnlyMovable : public UnAssignable { public: OnlyMovable (OnlyMovable const&) = delete; OnlyMovable() = default; OnlyMovable(OnlyMovable&&) = default; }; template class Noncopyable : public OnlyMovable { public: Noncopyable (Noncopyable&&) = delete; Noncopyable () = default; }; /* == concrete implementation subclass with virtual copy support == */ template class Opaque //-----Interface| CRTP-Impl | direct Baseclass : public CopySupport::template Policy, IMP> { public: static Opaque& downcast (Interface& bas) { Opaque* impl = dynamic_cast (&bas); if (!impl) throw error::Logic{"virtual copy works only on instances " "of the same concrete implementation class" , LERR_(WRONG_TYPE)}; else return *impl; } }; // == Test subject(s)========================== using RegularImpl = Opaque>; using ClonableImpl = Opaque>; using MovableImpl = Opaque>; using ImobileImpl = Opaque>; }//(End)Test fixture /**********************************************************************************************//** * @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) { seedRand(); 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 << a < 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 << cc < d; OnlyMovable<'D'> dd (std::move(d)); cout << 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 (&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> divergent; Interface& evil(divergent); VERIFY_ERROR (WRONG_TYPE, evil.copyInto(i)); VERIFY_ERROR (WRONG_TYPE, evil.moveInto(i)); cout << "==fullVirtualCopySupport=="< (&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=="< (&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=="<