Commit dc958dec authored by Sebastian Eichelbaum's avatar Sebastian Eichelbaum
Browse files

[ADD] - added unit test for WPropertyVariable and fixed some minor bugs

parent e16bf649
......@@ -141,6 +141,13 @@ public:
*/
virtual bool accept( T newValue );
/**
* Tests whether a flag is currently valid. It is equal to accept( get() );
*
* \return true if current value is valid.
*/
virtual bool isValid();
/**
* True whenever the value inside this flag has changed since the last reset. It stays true until get( true ) is called.
*
......@@ -292,6 +299,12 @@ bool WFlag< T >::accept( T /* newValue */ )
return true;
}
template < typename T >
bool WFlag< T >::isValid()
{
return accept( get() );
}
template < typename T >
bool WFlag< T >::changed()
{
......
......@@ -63,6 +63,7 @@ template< typename T >
class WPropertyVariable: public WFlag< T >,
public WPropertyBase
{
friend class WPropertyVariableTest;
public:
/**
......@@ -345,6 +346,20 @@ public:
*/
boost::shared_ptr< PropertyConstraint > replaceConstraint( PROPERTYCONSTRAINT_TYPE constraint, PROPERTYCONSTRAINT_TYPE type );
/**
* Cleans list of constraints from all existing constrains of the specified type.
*
* \param type the type to remove.
*/
void removeConstraint( PROPERTYCONSTRAINT_TYPE type );
/**
* Removes the specified constraint if existent.
*
* \param constraint the constraint to remove.
*/
void removeConstraint( boost::shared_ptr< PropertyConstraint > constraint );
/**
* Method searching the first appearance of a constrained with the specified type.
*
......@@ -417,7 +432,7 @@ protected:
virtual void updateType();
/**
* Cleans m_constraints from all existing constrains of the specified type.
* Cleans list of constraints from all existing constrains of the specified type.
*
* \param type the type to remove.
* \param ticket the write ticket if already existent.
......@@ -431,12 +446,6 @@ protected:
*/
void propertyChangeNotifier();
/**
* this condition gets notified whenever the list of constraints changes.
* \note it gets AFTER the write lock has been released. So in callbacks, a read lock can be acquired.
*/
boost::shared_ptr< WCondition > m_constraintsChanged;
/**
* A set of constraints applied on this property.
*/
......@@ -449,13 +458,12 @@ template < typename T >
WPropertyVariable< T >::WPropertyVariable( std::string name, std::string description, const T& initial ):
WFlag< T >( new WCondition(), initial ),
WPropertyBase( name, description ),
m_constraintsChanged( new WCondition() ),
m_constraints()
{
updateType();
// set constraint and change condition to update condition set of WPropertyBase
m_updateCondition->add( m_constraintsChanged );
m_updateCondition->add( m_constraints.getChangeCondition() );
m_updateCondition->add( WFlag< T >::getValueChangeCondition() );
}
......@@ -463,13 +471,12 @@ template < typename T >
WPropertyVariable< T >::WPropertyVariable( std::string name, std::string description, const T& initial, boost::shared_ptr< WCondition > condition ):
WFlag< T >( condition, initial ),
WPropertyBase( name, description ),
m_constraintsChanged( new WCondition() ),
m_constraints()
{
updateType();
// set constraint and change condition to update condition set of WPropertyBase
m_updateCondition->add( m_constraintsChanged );
m_updateCondition->add( m_constraints.getChangeCondition() );
m_updateCondition->add( WFlag< T >::getValueChangeCondition() );
}
......@@ -478,17 +485,16 @@ WPropertyVariable< T >::WPropertyVariable( std::string name, std::string descrip
PropertyChangeNotifierType notifier ):
WFlag< T >( new WCondition(), initial ),
WPropertyBase( name, description ),
m_constraintsChanged( new WCondition() ),
m_constraints()
{
updateType();
// set constraint and change condition to update condition set of WPropertyBase
m_updateCondition->add( m_constraintsChanged );
m_updateCondition->add( m_constraints.getChangeCondition() );
m_updateCondition->add( WFlag< T >::getValueChangeCondition() );
// set custom notifier
m_notifierConnection = WFlag< T >::getCondition()->subscribeSignal( boost::bind( &WPropertyVariable< T >::propertyChangeNotifier, this ) );
m_notifierConnection = m_updateCondition->subscribeSignal( boost::bind( &WPropertyVariable< T >::propertyChangeNotifier, this ) );
signal_PropertyChange.connect( notifier );
}
......@@ -497,17 +503,16 @@ WPropertyVariable< T >::WPropertyVariable( std::string name, std::string descrip
PropertyChangeNotifierType notifier ):
WFlag< T >( condition, initial ),
WPropertyBase( name, description ),
m_constraintsChanged( new WCondition() ),
m_constraints()
{
updateType();
// set constraint and change condition to update condition set of WPropertyBase
m_updateCondition->add( m_constraintsChanged );
m_updateCondition->add( m_constraints.getChangeCondition() );
m_updateCondition->add( WFlag< T >::getValueChangeCondition() );
// set custom notifier
m_notifierConnection = WFlag< T >::getCondition()->subscribeSignal( boost::bind( &WPropertyVariable< T >::propertyChangeNotifier, this ) );
m_notifierConnection = m_updateCondition->subscribeSignal( boost::bind( &WPropertyVariable< T >::propertyChangeNotifier, this ) );
signal_PropertyChange.connect( notifier );
}
......@@ -515,7 +520,6 @@ template < typename T >
WPropertyVariable< T >::WPropertyVariable( const WPropertyVariable< T >& from ):
WFlag< T >( from ),
WPropertyBase( from ),
m_constraintsChanged( new WCondition() ),
m_constraints()
{
// copy the constraints
......@@ -524,19 +528,26 @@ WPropertyVariable< T >::WPropertyVariable( const WPropertyVariable< T >& from ):
typename WPropertyVariable< T >::ConstraintSharedContainerType::ReadTicket l =
const_cast< WPropertyVariable< T >& >( from ).m_constraints.getReadTicket();
// get write ticket too
typename WPropertyVariable< T >::ConstraintSharedContainerType::WriteTicket w = m_constraints.getWriteTicket();
// we need to make a deep copy here.
for ( ConstraintContainerIteratorType iter = l->get().begin(); iter != l->get().end(); ++iter )
{
// clone them to keep dynamic type
l->get().insert( ( *iter )->clone() );
w->get().insert( ( *iter )->clone() );
}
// set constraint and change condition to update condition set of WPropertyBase
m_updateCondition->add( m_constraints.getChangeCondition() );
m_updateCondition->add( WFlag< T >::getValueChangeCondition() );
}
template < typename T >
WPropertyVariable< T >::~WPropertyVariable()
{
// clean up
m_updateCondition->remove( m_constraintsChanged );
m_updateCondition->remove( m_constraints.getChangeCondition() );
m_updateCondition->remove( WFlag< T >::getValueChangeCondition() );
m_notifierConnection.disconnect();
......@@ -645,14 +656,12 @@ void WPropertyVariable< T >::addConstraint( boost::shared_ptr< PropertyConstrain
// unlock by hand
l.reset();
m_constraintsChanged->notify();
}
template < typename T >
boost::shared_ptr< WCondition > WPropertyVariable< T >::getContraintsChangedCondition()
{
return m_constraintsChanged;
return m_constraints.getChangeCondition();
}
template < typename T >
......@@ -698,11 +707,6 @@ void WPropertyVariable< T >::replaceConstraint( boost::shared_ptr< PropertyConst
removeConstraints( type, l );
l->get().insert( constraint );
// unlock by hand
l.reset();
m_constraintsChanged->notify();
}
template < typename T >
......@@ -803,11 +807,13 @@ void WPropertyVariable< T >::removeConstraints( PROPERTYCONSTRAINT_TYPE type,
l = m_constraints.getWriteTicket();
}
size_t nbErased = 0; // count how much items have been removed
for ( ConstraintContainerConstIteratorType it = l->get().begin(); it != l->get().end(); )
{
if ( ( *it )->getType() == type )
{
l->get().erase( it++ );
++nbErased;
}
else
{
......@@ -818,10 +824,34 @@ void WPropertyVariable< T >::removeConstraints( PROPERTYCONSTRAINT_TYPE type,
// only notify and unlock if locked earlier.
if ( useLock )
{
// no operations done? No condition fired
if ( nbErased == 0 )
{
l->suppressUnlockCondition();
}
// unlock by hand
l.reset();
}
}
m_constraintsChanged->notify();
template < typename T >
void WPropertyVariable< T >::removeConstraint( PROPERTYCONSTRAINT_TYPE type )
{
// simply forward the call
removeConstraints( type, typename WPropertyVariable< T >::ConstraintSharedContainerType::WriteTicket() );
}
template < typename T >
void WPropertyVariable< T >::removeConstraint( boost::shared_ptr< PropertyConstraint > constraint )
{
// lock released automatically
typename WPropertyVariable< T >::ConstraintSharedContainerType::WriteTicket l = m_constraints.getWriteTicket();
if ( l->get().erase( constraint ) == 0 )
{
// nothing changed. Suppress update condition to fire
l->suppressUnlockCondition();
}
}
......
......@@ -66,6 +66,15 @@ public:
return m_data;
};
/**
* If called, the unlock will NOT fire the condition. This is useful in some situations if you find out "hey there actually was nothing
* changed".
*/
void suppressUnlockCondition()
{
m_condition = boost::shared_ptr< WCondition >();
}
protected:
/**
......
......@@ -46,6 +46,7 @@ public:
*/
virtual ~WSharedObjectTicketRead()
{
// explicitly unlock to ensure the WSharedObjectTicket destructor can call the update callback AFTER the lock has been released
unlock();
};
......
......@@ -46,6 +46,7 @@ public:
*/
virtual ~WSharedObjectTicketWrite()
{
// explicitly unlock to ensure the WSharedObjectTicket destructor can call the update callback AFTER the lock has been released
unlock();
};
......
//---------------------------------------------------------------------------
//
// Project: OpenWalnut ( http://www.openwalnut.org )
//
// Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
// For more information see http://www.openwalnut.org/copying
//
// This file is part of OpenWalnut.
//
// OpenWalnut is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// OpenWalnut 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
//
//---------------------------------------------------------------------------
#ifndef WPROPERTYVARIABLE_TEST_H
#define WPROPERTYVARIABLE_TEST_H
#include <string>
#include <cxxtest/TestSuite.h>
#include "../WPropertyVariable.h"
#include "../constraints/WPropertyConstraintMin.h"
#include "../constraints/WPropertyConstraintMax.h"
#include "../exceptions/WPropertyNotUnique.h"
#include "../exceptions/WPropertyUnknown.h"
#include "../exceptions/WPropertyNameMalformed.h"
/**
* Test WPropertyVariable
*/
class WPropertyVariableTest : public CxxTest::TestSuite
{
public:
/**
* A temporary holder for some value.
*/
bool m_testTemporary1;
/**
* A temporary holder for some value.
*/
bool m_testTemporary2;
/**
* Helper function which simply sets the value above to true. It is used to test some conditions here.
*/
void setTemporary1()
{
m_testTemporary1 = true;
}
/**
* Helper function which simply sets the value above to true. It is used to test some conditions here.
*/
void setTemporary2()
{
m_testTemporary2 = true;
}
/**
* Test instantiation, also test name and description and type (from WPropertyBase)
*/
void testInstantiation( void )
{
boost::shared_ptr< WPropertyVariable< bool > > p;
TS_ASSERT_THROWS_NOTHING( p = boost::shared_ptr< WPropertyVariable< bool > >( new WPropertyVariable< bool >( "hey", "you", false ) ) );
// test names
TS_ASSERT( p->getName() == "hey" );
TS_ASSERT( p->getDescription() == "you" );
TS_ASSERT( p->getType() == PV_BOOL );
TS_ASSERT_THROWS_NOTHING( p.reset() );
}
/**
* Tests that only properties with proper names can be created
*/
void testNameConvention( void )
{
WException::disableBacktrace(); // in tests, turn of backtrace globally
boost::shared_ptr< WPropertyVariable< bool > > p;
TS_ASSERT_THROWS( p = boost::shared_ptr< WPropertyVariable< bool > >( new WPropertyVariable< bool >( "hey/you", "you", false ) ),
WPropertyNameMalformed );
}
/**
* Tests the cloning functionality.
*/
void testClone()
{
WException::disableBacktrace(); // in tests, turn of backtrace globally
/////////////////////////
// Create an original
// create an int property
boost::shared_ptr< WPropertyVariable< int > > p =
boost::shared_ptr< WPropertyVariable< int > >( new WPropertyVariable< int >( "hey", "you", false ) );
// add a min/max prop
WPropertyVariable< int >::PropertyConstraintMin cmin = p->setMin( 0 );
WPropertyVariable< int >::PropertyConstraintMax cmax = p->setMax( 9 );
p->set( 5 );
/////////////////////////
// Clone it
boost::shared_ptr< WPropertyVariable< int > > clone = p->clone()->toPropInt();
// some rudimentary tests (from WPropertyBase)
TS_ASSERT( clone );
TS_ASSERT( clone->getType() == PV_INT );
TS_ASSERT( clone->getName() == p->getName() );
TS_ASSERT( clone->getDescription() == p->getDescription() );
TS_ASSERT( clone->getPurpose() == p->getPurpose() );
// equal value?
TS_ASSERT( p->get() == clone->get() );
// different conditions?
TS_ASSERT( p->getContraintsChangedCondition() != clone->getContraintsChangedCondition() );
TS_ASSERT( p->getUpdateCondition() != clone->getUpdateCondition() ); // this is from WPropertyBase
// cloned constraints?
TS_ASSERT( p->getMin() != clone->getMin() ); // the constraints of course need to be cloned too
TS_ASSERT( p->getMax() != clone->getMax() ); // the constraints of course need to be cloned too
TS_ASSERT( p->getMin()->getMin() == clone->getMin()->getMin() ); // but their values need to be the same. This somehow tests the clone
// feature of WPropertyConstrainMin and Max
TS_ASSERT( p->getMax()->getMax() == clone->getMax()->getMax() ); // but their values need to be the same. This somehow tests the clone
// feature of WPropertyConstrainMin and Max
// check independence of both update conditions
p->getUpdateCondition()->subscribeSignal( boost::bind( &WPropertyVariableTest::setTemporary1, this ) );
clone->getUpdateCondition()->subscribeSignal( boost::bind( &WPropertyVariableTest::setTemporary2, this ) );
// set the value of the clone -> fire condition of clone but not of original
m_testTemporary1 = false;
m_testTemporary2 = false;
clone->set( 4 );
TS_ASSERT( !m_testTemporary1 );
TS_ASSERT( m_testTemporary2 );
// and test vice versa
m_testTemporary1 = false;
m_testTemporary2 = false;
p->set( 2 );
TS_ASSERT( m_testTemporary1 );
TS_ASSERT( !m_testTemporary2 );
// do the same for constraints
m_testTemporary1 = false;
m_testTemporary2 = false;
clone->removeConstraint( PC_MIN );
TS_ASSERT( !m_testTemporary1 );
TS_ASSERT( m_testTemporary2 );
// and vice versa
m_testTemporary1 = false;
m_testTemporary2 = false;
p->removeConstraint( PC_MIN );
TS_ASSERT( m_testTemporary1 );
TS_ASSERT( !m_testTemporary2 );
}
/**
* Test min/max functionality, including tests for set(), accept() and ensureValidity.
*/
void testMinMaxWithSetAndAccept()
{
WException::disableBacktrace(); // in tests, turn of backtrace globally
// create an int property
boost::shared_ptr< WPropertyVariable< int > > p =
boost::shared_ptr< WPropertyVariable< int > >( new WPropertyVariable< int >( "hey", "you", false ) );
// by default there should be no min/max property set. Only IF the property was created using a WProperties::addProperty.
WPropertyVariable< int >::PropertyConstraintMin cmin = p->getMin();
WPropertyVariable< int >::PropertyConstraintMax cmax = p->getMax();
TS_ASSERT( !cmin );
TS_ASSERT( !cmax );
// does set() and accept work if no constraints are there?
TS_ASSERT( p->set( 123 ) );
TS_ASSERT( p->get() == 123 );
TS_ASSERT( p->accept( 12345 ) );
// add a min prop
cmin = p->setMin( 10 );
cmax = p->setMax( 15 );
TS_ASSERT( cmin );
TS_ASSERT( cmax );
// compare that getMin/max returns the correct ones
TS_ASSERT( cmin == p->getMin() );
TS_ASSERT( cmax == p->getMax() );
// try to set a valid value
TS_ASSERT( p->set( 10 ) );
TS_ASSERT( p->get() == 10 );
// try to set an invalid value
TS_ASSERT( !p->set( 9 ) );
TS_ASSERT( p->get() == 10 );
TS_ASSERT( !p->set( 16 ) );
TS_ASSERT( p->get() == 10 );
// add another min value. Is the first one removed?
p->setMin( 5 );
p->setMax( 20 );
p->m_constraints.getReadTicket()->get().size();
// try to set a valid value, which was invalid previously
TS_ASSERT( p->set( 9 ) );
TS_ASSERT( p->get() == 9 );
TS_ASSERT( p->set( 16 ) );
TS_ASSERT( p->get() == 16 );
// finally, test ensureValidity
// this function helps to restore a property to a valid state after a constraint change
// currently, the state of p is valid. So ensureValidity should do nothing
TS_ASSERT( p->ensureValidity( 10 ) );
TS_ASSERT( p->get() == 16 );
// change the min constraint so that 16 gets invalid
TS_ASSERT( p->isValid() );
p->setMin( 17 );
TS_ASSERT( !p->isValid() );
TS_ASSERT( p->get() == 16 ); // setting a new constraint should NOT modify the current value
// use ensureValidity
TS_ASSERT( p->ensureValidity( 18 ) );
TS_ASSERT( p->get() == 18 );
TS_ASSERT( p->isValid() );
// what happens if the ensureValidity parameter itself is invalid? It should return false
p->setMin( 19 );
TS_ASSERT( !p->ensureValidity( 16 ) ); // 16 is invalid since minimum is 19
TS_ASSERT( !p->isValid() ); // the value should stay invalid
TS_ASSERT( p->get() == 18 );
}
/**
* Tests constraint management. Especially add,replace,remove,count,getFirst.
*/
void testConstraintManagement( void )
{
WException::disableBacktrace(); // in tests, turn of backtrace globally
// create an int property
boost::shared_ptr< WPropertyVariable< int > > p =
boost::shared_ptr< WPropertyVariable< int > >( new WPropertyVariable< int >( "hey", "you", false ) );
// register a condition callback
p->getUpdateCondition()->subscribeSignal( boost::bind( &WPropertyVariableTest::setTemporary1, this ) );
////////////////////////////////////
// add
// add a constraint
m_testTemporary1 = false;
WPropertyVariable< int >::PropertyConstraintMin cmin =
boost::shared_ptr< WPropertyConstraintMin< int > >( new WPropertyConstraintMin< int >( 10 ) );
p->addConstraint( cmin );
TS_ASSERT( p->m_constraints.getReadTicket()->get().size() == 1 );
TS_ASSERT( m_testTemporary1 ); // the update condition has to be fired on constraint updates
////////////////////////////////////
// count, getFirst
// count constraints
m_testTemporary1 = false;
TS_ASSERT( p->countConstraint( PC_MIN ) == 1 );
TS_ASSERT( p->countConstraint( PC_MAX ) == 0 );
// get first constraint should return the first constraint of a specified type
TS_ASSERT( cmin == p->getFirstConstraint( PC_MIN ) );
TS_ASSERT( !p->getFirstConstraint( PC_MAX ) ); // there is no max constraint
TS_ASSERT( !m_testTemporary1 ); // these operations should not fire the condition
////////////////////////////////////
// replace
// replace a constraint
m_testTemporary1 = false;
WPropertyVariable< int >::PropertyConstraintMax cmax =
boost::shared_ptr< WPropertyConstraintMax< int > >( new WPropertyConstraintMax< int >( 15 ) );
// replace non existent type
TS_ASSERT_THROWS_NOTHING( p->replaceConstraint( cmax, PC_MAX ) ); // since there is no max constraint, replace acts like addConstraint
TS_ASSERT( m_testTemporary1 );
// replace existent type ( note: there is now a min and a max constraint )
m_testTemporary1 = false;
WPropertyVariable< int >::PropertyConstraintMax cmax2 =
boost::shared_ptr< WPropertyConstraintMax< int > >( new WPropertyConstraintMax< int >( 20 ) );
p->replaceConstraint( cmax2, PC_MAX );
TS_ASSERT( m_testTemporary1 );
TS_ASSERT( cmax2 == p->getFirstConstraint( PC_MAX ) );
////////////////////////////////////
// remove
// removeConstraints should not fire the condition if nothing is removed
m_testTemporary1 = false;
p->removeConstraint( PC_NOTEMPTY );
TS_ASSERT( !m_testTemporary1 );