<commit_before>/******************************************************************************
 * This file is part of the Gluon Development Platform
 * Copyright (c) 2010 Arjen Hiemstra <ahiemstra@heimr.nl>
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
#include "projectmodel.h"

#include "core/gluonobject.h"
#include "core/debughelper.h"
#include "engine/game.h"
#include "engine/gameproject.h"
#include "engine/asset.h"
#include "engine/scene.h"

#include <KDebug>
#include <KLocalizedString>
#include <KMimeType>
#include <KIcon>

#include <QtCore/QMimeData>
#include <kmimetype.h>
#include <QtCore/QFileInfo>
#include <engine/filelocation.h>
#include <QDir>
#include <historymanager.h>
#include <objectmanager.h>

using namespace GluonCreator;

class ProjectModel::ProjectModelPrivate
{
    public:
        ProjectModelPrivate()
        {
            root = 0;
            project = 0;
        }

        QObject* root;
        GluonEngine::GameProject* project;

        QStringList acceptedMimeTypes;
};

ProjectModel::ProjectModel( QObject* parent ): QAbstractItemModel( parent ), d( new ProjectModelPrivate )
{
    connect( HistoryManager::instance(), SIGNAL( historyChanged( const QUndoCommand* ) ), SIGNAL( layoutChanged() ) );
}

ProjectModel::~ProjectModel()
{
    delete d;
}

GluonEngine::GameProject*
ProjectModel::project()
{
    return d->project;
}

void
ProjectModel::setProject( GluonEngine::GameProject* project )
{
    if( project )
    {
        d->root = new QObject( this );
        d->project = project;
        project->setParent( d->root );

        reset();
    }
}

QVariant
ProjectModel::data( const QModelIndex& index, int role ) const
{
    if( !index.isValid() )
        return QVariant();

    if( role == Qt::DecorationRole )
    {
        GluonCore::GluonObject* gobj = qobject_cast<GluonCore::GluonObject*>( static_cast<QObject*>( index.internalPointer() ) );
        if( gobj )
        {
            QVariant filename = gobj->property( "file" );
            QString classname( gobj->metaObject()->className() );
            if( classname == QLatin1String( "GluonCore::GluonObject" ) )
            {
                // In this case we're dealing with something which is a "folder"... show it as such
                return KIcon( "folder" );
            }
            if( qobject_cast<GluonEngine::Asset*>( gobj ) )
            {
                QIcon icon = qobject_cast<GluonEngine::Asset*>( gobj )->icon();
                if( icon.isNull() )
                {
                    if( filename.isValid() )
                    {
                        // If the asset doesn't provide an icon itself, but we do have a filename
                        // Get the icon for the mimetype of that url
                        QString name = filename.value<QString>();
                        return KIcon( KMimeType::iconNameForUrl( KUrl( name ) ) );
                    }
                    else
                        return KIcon( "unknown" );
                }
                return icon;
            }
            else
                return KIcon( "text-x-generic" );
        }
    }

    if( role == Qt::DisplayRole || role == Qt::EditRole )
    {
        GluonCore::GluonObject* gobj = qobject_cast<GluonCore::GluonObject*>( static_cast<QObject*>( index.internalPointer() ) );
        if( gobj )
            return gobj->name();
    }

    return QVariant();
}

int
ProjectModel::columnCount( const QModelIndex& parent ) const
{
    Q_UNUSED( parent )
    return 1;
}

int
ProjectModel::rowCount( const QModelIndex& parent ) const
{
    if( parent.column() > 0 )
        return 0;

    QObject* parentItem = d->root;
    if( parent.isValid() )
        parentItem = static_cast<QObject*>( parent.internalPointer() );

    if( parentItem )
    {
        if( qobject_cast<GluonCore::GluonObject*>( parentItem ) )
        {
            if( qobject_cast<GluonEngine::Scene*>( parentItem ) )
                return 0;

            int childCount = 0;
            const QObjectList allChildren = parentItem->children();
            foreach( const QObject * child, allChildren )
            {
                if( qobject_cast<const GluonCore::GluonObject* >( child ) )
                    ++childCount;
            }
            return childCount;
        }
        else
            return parentItem->children().count();
    }

    return 0;
}

QModelIndex
ProjectModel::parent( const QModelIndex& child ) const
{
    if( !child.isValid() )
        return QModelIndex();

    QObject* childItem = static_cast<QObject*>( child.internalPointer() );
    QObject* parentItem = childItem->parent();

    if( parentItem == d->root )
        return QModelIndex();

    QObject* grandParent = parentItem->parent();
    if( grandParent )
    {
        int childCount = -1;
        const QObjectList allChildren = grandParent->children();
        foreach( const QObject * grandChild, allChildren )
        {
            if( qobject_cast<const GluonCore::GluonObject* >( grandChild ) )
                ++childCount;
            if( grandChild == parentItem )
                return createIndex( childCount, 0, parentItem );
        }
    }
    return createIndex( -1, 0, grandParent );
}

QModelIndex
ProjectModel::index( int row, int column, const QModelIndex& parent ) const
{
    if( !hasIndex( row, column, parent ) )
        return QModelIndex();

    QObject* parentItem = d->root;
    if( parent.isValid() )
        parentItem = static_cast<QObject*>( parent.internalPointer() );

    int childCount = -1;
    const QObjectList allChildren = parentItem->children();
    foreach( const QObject * child, allChildren )
    {
        if( qobject_cast<const GluonCore::GluonObject*>( child ) )
            ++childCount;
        if( childCount == row )
            return createIndex( row, column, const_cast<QObject*>( child ) );
    }

    return QModelIndex();
}

QVariant
ProjectModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
    Q_UNUSED( section )
    Q_UNUSED( orientation )
    Q_UNUSED( role )

    return QVariant();
}

Qt::DropActions
ProjectModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
}

Qt::ItemFlags
ProjectModel::flags( const QModelIndex& index ) const
{
    if( index.isValid() )
    {
        //QObject* obj = static_cast<QObject*>( index.internalPointer() );
        GluonEngine::Asset* obj = qobject_cast<GluonEngine::Asset*>(static_cast<QObject*>(index.internalPointer()));
        // One does not simply drop Assets into Mord...other Assets!
        if( obj )//->inherits( "GluonEngine::Asset" ) )
        {
            return QAbstractItemModel::flags( index ) | Qt::ItemIsDragEnabled | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
        }
        else
        {
            return QAbstractItemModel::flags( index ) | Qt::ItemIsDragEnabled | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled;
        }
    }
    else
    {
        return Qt::ItemIsDropEnabled;
    }
}

QStringList
ProjectModel::mimeTypes() const
{
    if( d->acceptedMimeTypes.count() < 1 )
    {
        DEBUG_FUNC_NAME
        d->acceptedMimeTypes.append( "application/gluoncreator.projectmodel.gluonobject" );
        d->acceptedMimeTypes.append( "text/uri-list" );
        d->acceptedMimeTypes.append( GluonCore::GluonObjectFactory::instance()->objectMimeTypes() );
        foreach( const QString & theName, d->acceptedMimeTypes )
        {
            DEBUG_TEXT( QString( "%1" ).arg( theName ) );
        }
    }

    return d->acceptedMimeTypes;
}

QMimeData* ProjectModel::mimeData( const QModelIndexList& indexes ) const
{
    if( indexes.count() <= 0 )
        return 0;

    QStringList types = mimeTypes();
    if( types.isEmpty() )
        return 0;

    QMimeData* data = new QMimeData();
    QByteArray encodedData;

    QDataStream stream( &encodedData, QIODevice::WriteOnly );

    // There should really only be one, but let's do the loop-de-loop anyway
    foreach( const QModelIndex & index, indexes )
    {
        if( index.isValid() )
        {
            const GluonEngine::Asset* item = static_cast<GluonEngine::Asset*>( index.internalPointer() );
            if( item )
            {
                QString text = item->fullyQualifiedName();
                stream << text;
            }
        }
    }

    data->setData( "application/gluoncreator.projectmodel.gluonobject", encodedData );

    return data;
}

bool
ProjectModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent )
{
    Q_UNUSED( row )
    Q_UNUSED( column )
    DEBUG_FUNC_NAME

    if( action == Qt::IgnoreAction )
        return false;

    if( data->hasUrls() )
    {
        foreach( const QUrl & theUrl, data->urls() )
        {
            ObjectManager::instance()->createNewAsset( theUrl.toLocalFile() );
        }
        return true;
    }
    else if( data->hasFormat( "application/gluoncreator.projectmodel.gluonobject" ) )
    {
        QByteArray encodedData = data->data( "application/gluoncreator.projectmodel.gluonobject" );
        QDataStream stream( &encodedData, QIODevice::ReadOnly );
        QStringList newItems;
        int rows = 0;

        while( !stream.atEnd() )
        {
            QString text;
            stream >> text;
            newItems << text;
            ++rows;
        }

        DEBUG_TEXT2("Dropped item %1", newItems.join(" and "));
        return true;
    }

    return false;
}

bool
ProjectModel::setData( const QModelIndex& index, const QVariant& value, int role )
{
    if( index.isValid() && role == Qt::EditRole )
    {
        static_cast<GluonCore::GluonObject*>( index.internalPointer() )->setName( value.toString() );
        emit dataChanged( index, index );
        return true;
    }
    return false;
}

bool
ProjectModel::removeRows( int row, int count, const QModelIndex& parent )
{
    DEBUG_FUNC_NAME
    if( !parent.isValid() )
        return false;

    if( count < 1 )
        return false;

    GluonCore::GluonObject* parentObject = static_cast<GluonCore::GluonObject*>( parent.internalPointer() );
    DEBUG_TEXT( "Object removal begins..." );

    beginRemoveRows( parent, row, row + count - 1 );
    for( int i = row; i < row + count; ++i )
    {
        DEBUG_TEXT( QString( "Removing child at row %1" ).arg( i ) );
        GluonCore::GluonObject* child = parentObject->child( row );
        if( parentObject->removeChild( child ) )
            delete child;
    }
    endRemoveRows();

    return true;
}

void ProjectModel::addChild( QObject* newChild, QModelIndex& parent )
{
    if( parent.isValid() )
    {
        GluonCore::GluonObject* parentObject = static_cast<GluonCore::GluonObject*>( parent.internalPointer() );

        GluonCore::GluonObject* newObject = qobject_cast<GluonCore::GluonObject*>( newChild );
        if( newChild->parent() == parentObject )
        {
            //Remove the children from the parent to let rowCount return
            //an exact count.
            parentObject->removeChild( newObject );
        }
        int rcount = rowCount( parent );
        beginInsertRows( parent, rcount, rcount );

        parentObject->addChild( newObject );

        endInsertRows();
    }
}

//#include "projectmodel.moc"
<commit_msg>Move the dropped object in the Project tree to its new parent<commit_after>/******************************************************************************
 * This file is part of the Gluon Development Platform
 * Copyright (c) 2010 Arjen Hiemstra <ahiemstra@heimr.nl>
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
#include "projectmodel.h"

#include "core/gluonobject.h"
#include "core/debughelper.h"
#include "engine/game.h"
#include "engine/gameproject.h"
#include "engine/asset.h"
#include "engine/scene.h"

#include <KDebug>
#include <KLocalizedString>
#include <KMimeType>
#include <KIcon>

#include <QtCore/QMimeData>
#include <kmimetype.h>
#include <QtCore/QFileInfo>
#include <engine/filelocation.h>
#include <QDir>
#include <historymanager.h>
#include <objectmanager.h>

using namespace GluonCreator;

class ProjectModel::ProjectModelPrivate
{
    public:
        ProjectModelPrivate()
        {
            root = 0;
            project = 0;
        }

        QObject* root;
        GluonEngine::GameProject* project;

        QStringList acceptedMimeTypes;
};

ProjectModel::ProjectModel( QObject* parent ): QAbstractItemModel( parent ), d( new ProjectModelPrivate )
{
    connect( HistoryManager::instance(), SIGNAL( historyChanged( const QUndoCommand* ) ), SIGNAL( layoutChanged() ) );
}

ProjectModel::~ProjectModel()
{
    delete d;
}

GluonEngine::GameProject*
ProjectModel::project()
{
    return d->project;
}

void
ProjectModel::setProject( GluonEngine::GameProject* project )
{
    if( project )
    {
        d->root = new QObject( this );
        d->project = project;
        project->setParent( d->root );

        reset();
    }
}

QVariant
ProjectModel::data( const QModelIndex& index, int role ) const
{
    if( !index.isValid() )
        return QVariant();

    if( role == Qt::DecorationRole )
    {
        GluonCore::GluonObject* gobj = qobject_cast<GluonCore::GluonObject*>( static_cast<QObject*>( index.internalPointer() ) );
        if( gobj )
        {
            QVariant filename = gobj->property( "file" );
            QString classname( gobj->metaObject()->className() );
            if( classname == QLatin1String( "GluonCore::GluonObject" ) )
            {
                // In this case we're dealing with something which is a "folder"... show it as such
                return KIcon( "folder" );
            }
            if( qobject_cast<GluonEngine::Asset*>( gobj ) )
            {
                QIcon icon = qobject_cast<GluonEngine::Asset*>( gobj )->icon();
                if( icon.isNull() )
                {
                    if( filename.isValid() )
                    {
                        // If the asset doesn't provide an icon itself, but we do have a filename
                        // Get the icon for the mimetype of that url
                        QString name = filename.value<QString>();
                        return KIcon( KMimeType::iconNameForUrl( KUrl( name ) ) );
                    }
                    else
                        return KIcon( "unknown" );
                }
                return icon;
            }
            else
                return KIcon( "text-x-generic" );
        }
    }

    if( role == Qt::DisplayRole || role == Qt::EditRole )
    {
        GluonCore::GluonObject* gobj = qobject_cast<GluonCore::GluonObject*>( static_cast<QObject*>( index.internalPointer() ) );
        if( gobj )
            return gobj->name();
    }

    return QVariant();
}

int
ProjectModel::columnCount( const QModelIndex& parent ) const
{
    Q_UNUSED( parent )
    return 1;
}

int
ProjectModel::rowCount( const QModelIndex& parent ) const
{
    if( parent.column() > 0 )
        return 0;

    QObject* parentItem = d->root;
    if( parent.isValid() )
        parentItem = static_cast<QObject*>( parent.internalPointer() );

    if( parentItem )
    {
        if( qobject_cast<GluonCore::GluonObject*>( parentItem ) )
        {
            if( qobject_cast<GluonEngine::Scene*>( parentItem ) )
                return 0;

            int childCount = 0;
            const QObjectList allChildren = parentItem->children();
            foreach( const QObject * child, allChildren )
            {
                if( qobject_cast<const GluonCore::GluonObject* >( child ) )
                    ++childCount;
            }
            return childCount;
        }
        else
            return parentItem->children().count();
    }

    return 0;
}

QModelIndex
ProjectModel::parent( const QModelIndex& child ) const
{
    if( !child.isValid() )
        return QModelIndex();

    QObject* childItem = static_cast<QObject*>( child.internalPointer() );
    QObject* parentItem = childItem->parent();

    if( parentItem == d->root )
        return QModelIndex();

    QObject* grandParent = parentItem->parent();
    if( grandParent )
    {
        int childCount = -1;
        const QObjectList allChildren = grandParent->children();
        foreach( const QObject * grandChild, allChildren )
        {
            if( qobject_cast<const GluonCore::GluonObject* >( grandChild ) )
                ++childCount;
            if( grandChild == parentItem )
                return createIndex( childCount, 0, parentItem );
        }
    }
    return createIndex( -1, 0, grandParent );
}

QModelIndex
ProjectModel::index( int row, int column, const QModelIndex& parent ) const
{
    if( !hasIndex( row, column, parent ) )
        return QModelIndex();

    QObject* parentItem = d->root;
    if( parent.isValid() )
        parentItem = static_cast<QObject*>( parent.internalPointer() );

    int childCount = -1;
    const QObjectList allChildren = parentItem->children();
    foreach( const QObject * child, allChildren )
    {
        if( qobject_cast<const GluonCore::GluonObject*>( child ) )
            ++childCount;
        if( childCount == row )
            return createIndex( row, column, const_cast<QObject*>( child ) );
    }

    return QModelIndex();
}

QVariant
ProjectModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
    Q_UNUSED( section )
    Q_UNUSED( orientation )
    Q_UNUSED( role )

    return QVariant();
}

Qt::DropActions
ProjectModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
}

Qt::ItemFlags
ProjectModel::flags( const QModelIndex& index ) const
{
    if( index.isValid() )
    {
        //QObject* obj = static_cast<QObject*>( index.internalPointer() );
        GluonEngine::Asset* obj = qobject_cast<GluonEngine::Asset*>(static_cast<QObject*>(index.internalPointer()));
        // One does not simply drop Assets into Mord...other Assets!
        if( obj )//->inherits( "GluonEngine::Asset" ) )
        {
            return QAbstractItemModel::flags( index ) | Qt::ItemIsDragEnabled | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
        }
        else
        {
            return QAbstractItemModel::flags( index ) | Qt::ItemIsDragEnabled | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled;
        }
    }
    else
    {
        return Qt::ItemIsDropEnabled;
    }
}

QStringList
ProjectModel::mimeTypes() const
{
    if( d->acceptedMimeTypes.count() < 1 )
    {
        DEBUG_FUNC_NAME
        d->acceptedMimeTypes.append( "application/gluoncreator.projectmodel.gluonobject" );
        d->acceptedMimeTypes.append( "text/uri-list" );
        d->acceptedMimeTypes.append( GluonCore::GluonObjectFactory::instance()->objectMimeTypes() );
        foreach( const QString & theName, d->acceptedMimeTypes )
        {
            DEBUG_TEXT( QString( "%1" ).arg( theName ) );
        }
    }

    return d->acceptedMimeTypes;
}

QMimeData* ProjectModel::mimeData( const QModelIndexList& indexes ) const
{
    if( indexes.count() <= 0 )
        return 0;

    QStringList types = mimeTypes();
    if( types.isEmpty() )
        return 0;

    QMimeData* data = new QMimeData();
    QByteArray encodedData;

    QDataStream stream( &encodedData, QIODevice::WriteOnly );

    // There should really only be one, but let's do the loop-de-loop anyway
    foreach( const QModelIndex & index, indexes )
    {
        if( index.isValid() )
        {
            const GluonEngine::Asset* item = static_cast<GluonEngine::Asset*>( index.internalPointer() );
            if( item )
            {
                QString text = item->fullyQualifiedName();
                stream << text;
            }
        }
    }

    data->setData( "application/gluoncreator.projectmodel.gluonobject", encodedData );

    return data;
}

bool
ProjectModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent )
{
    Q_UNUSED( row )
    Q_UNUSED( column )
    DEBUG_FUNC_NAME

    if( action == Qt::IgnoreAction )
        return false;

    if( data->hasUrls() )
    {
        foreach( const QUrl & theUrl, data->urls() )
        {
            ObjectManager::instance()->createNewAsset( theUrl.toLocalFile() );
        }
        return true;
    }
    else if( data->hasFormat( "application/gluoncreator.projectmodel.gluonobject" ) )
    {
        QByteArray encodedData = data->data( "application/gluoncreator.projectmodel.gluonobject" );
        QDataStream stream( &encodedData, QIODevice::ReadOnly );
        QStringList newItems;
        int rows = 0;

        while( !stream.atEnd() )
        {
            QString text;
            stream >> text;
            newItems << text;
            ++rows;
        }

        foreach(const QString& item, newItems)
        {
            GluonCore::GluonObject* itemObject = d->project->findItemByName(item);
            if( itemObject )
            {
                GluonCore::GluonObject* newParentObject = static_cast<GluonCore::GluonObject*>(parent.internalPointer());
                newParentObject->addChild(itemObject);
            }
        }
        return true;
    }

    return false;
}

bool
ProjectModel::setData( const QModelIndex& index, const QVariant& value, int role )
{
    if( index.isValid() && role == Qt::EditRole )
    {
        static_cast<GluonCore::GluonObject*>( index.internalPointer() )->setName( value.toString() );
        emit dataChanged( index, index );
        return true;
    }
    return false;
}

bool
ProjectModel::removeRows( int row, int count, const QModelIndex& parent )
{
    DEBUG_FUNC_NAME
    if( !parent.isValid() )
        return false;

    if( count < 1 )
        return false;

    GluonCore::GluonObject* parentObject = static_cast<GluonCore::GluonObject*>( parent.internalPointer() );
    DEBUG_TEXT( "Object removal begins..." );

    beginRemoveRows( parent, row, row + count - 1 );
    for( int i = row; i < row + count; ++i )
    {
        DEBUG_TEXT( QString( "Removing child at row %1" ).arg( i ) );
        GluonCore::GluonObject* child = parentObject->child( row );
        if( parentObject->removeChild( child ) )
            delete child;
    }
    endRemoveRows();

    return true;
}

void ProjectModel::addChild( QObject* newChild, QModelIndex& parent )
{
    if( parent.isValid() )
    {
        GluonCore::GluonObject* parentObject = static_cast<GluonCore::GluonObject*>( parent.internalPointer() );

        GluonCore::GluonObject* newObject = qobject_cast<GluonCore::GluonObject*>( newChild );
        if( newChild->parent() == parentObject )
        {
            //Remove the children from the parent to let rowCount return
            //an exact count.
            parentObject->removeChild( newObject );
        }
        int rcount = rowCount( parent );
        beginInsertRows( parent, rcount, rcount );

        parentObject->addChild( newObject );

        endInsertRows();
    }
}

//#include "projectmodel.moc"
<|endoftext|>


<commit_before>/* mbed Microcontroller Library
 * Copyright (c) 2017 ARM Limited
 *
 * 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.
 */

#if 1 || !DEVICE_SLEEP
#error [NOT_SUPPORTED] sleep not supported for this target
#endif

#include "mbed.h"

#include "utest/utest.h"
#include "unity/unity.h"
#include "greentea-client/test_env.h"

#include "sleep_api_tests.h"

#define US_PER_S 1000000

using namespace utest::v1;

/* The following ticker frequencies are possible:
 * high frequency ticker: 250 KHz (1 tick per 4 us) - 8 Mhz (1 tick per 1/8 us)
 * low power ticker: 8 KHz (1 tick per 125 us) - 64 KHz (1 tick per ~15.6 us)
 */

/* Used for regular sleep mode, a target should be awake within 10 us. Define us delta value as follows:
 * delta = default 10 us + worst ticker resolution + extra time for code execution */
static const uint32_t sleep_mode_delta_us = (10 + 4 + 5);

/* Used for deep-sleep mode, a target should be awake within 10 ms. Define us delta value as follows:
 * delta = default 10 ms + worst ticker resolution + extra time for code execution */
static const uint32_t deepsleep_mode_delta_us = (10000 + 125 + 5);

unsigned int ticks_to_us(unsigned int ticks, unsigned int freq)
{
    return (unsigned int) ((unsigned long long) ticks * US_PER_S / freq);
}

unsigned int us_to_ticks(unsigned int us, unsigned int freq)
{
    return (unsigned int) ((unsigned long long) us * freq / US_PER_S);
}

unsigned int overflow_protect(unsigned int timestamp, unsigned int ticker_width)
{
    unsigned int counter_mask = ((1 << ticker_width) - 1);

    return (timestamp & counter_mask);
}

bool compare_timestamps(unsigned int delta_ticks, unsigned int ticker_width, unsigned int expected, unsigned int actual)
{
    const unsigned int counter_mask = ((1 << ticker_width) - 1);

    const unsigned int lower_bound = ((expected - delta_ticks) & counter_mask);
    const unsigned int upper_bound = ((expected + delta_ticks) & counter_mask);

    if (lower_bound < upper_bound) {
        if (actual >= lower_bound && actual <= upper_bound) {
            return true;
        } else {
            return false;
        }
    } else {
        if ((actual >= lower_bound && actual <= counter_mask) || (actual >= 0 && actual <= upper_bound)) {
            return true;
        } else {
            return false;
        }
    }
}

void us_ticker_isr(const ticker_data_t * const ticker_data)
{
    us_ticker_clear_interrupt();
}

#ifdef DEVICE_LPTICKER
void lp_ticker_isr(const ticker_data_t *const ticker_data)
{
    lp_ticker_clear_interrupt();
}
#endif

/* Test that wake-up time from sleep should be less than 10 us and
 * high frequency ticker interrupt can wake-up target from sleep. */
void sleep_usticker_test()
{
#if 0
    const ticker_data_t * ticker = get_us_ticker_data();
    const unsigned int ticker_freq = ticker->interface->get_info()->frequency;
    const unsigned int ticker_width = ticker->interface->get_info()->bits;

    const ticker_irq_handler_type us_ticker_irq_handler_org = set_us_ticker_irq_handler(us_ticker_isr);

    /* Test only sleep functionality. */
    sleep_manager_lock_deep_sleep();
    TEST_ASSERT_FALSE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should be locked");

    /* Testing wake-up time 10 us. */
    for (timestamp_t i = 100; i < 1000; i += 100) {
        /* note: us_ticker_read() operates on ticks. */
        const timestamp_t next_match_timestamp = overflow_protect(us_ticker_read() + us_to_ticks(i, ticker_freq),
                ticker_width);

        us_ticker_set_interrupt(next_match_timestamp);

        sleep();

        const unsigned int wakeup_timestamp = us_ticker_read();

        TEST_ASSERT(
                compare_timestamps(us_to_ticks(sleep_mode_delta_us, ticker_freq), ticker_width, next_match_timestamp,
                        wakeup_timestamp));
    }

    set_us_ticker_irq_handler(us_ticker_irq_handler_org);

    sleep_manager_unlock_deep_sleep();
    TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep());
#endif
}

#ifdef DEVICE_LPTICKER

/* Test that wake-up time from sleep should be less than 10 ms and
 * low power ticker interrupt can wake-up target from sleep. */
void deepsleep_lpticker_test()
{
    const ticker_data_t * ticker = get_lp_ticker_data();
    const unsigned int ticker_freq = ticker->interface->get_info()->frequency;
    const unsigned int ticker_width = ticker->interface->get_info()->bits;

    const ticker_irq_handler_type lp_ticker_irq_handler_org = set_lp_ticker_irq_handler(lp_ticker_isr);

    /* Give some time Green Tea to finish UART transmission before entering
     * deep-sleep mode.
     */
    wait_ms(10);

    TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should not be locked");

    /* Testing wake-up time 10 ms. */
    for (timestamp_t i = 20000; i < 200000; i += 20000) {
        /* note: lp_ticker_read() operates on ticks. */
        const timestamp_t next_match_timestamp = overflow_protect(lp_ticker_read() + us_to_ticks(i, ticker_freq), ticker_width);

        lp_ticker_set_interrupt(next_match_timestamp);

        sleep();

        const timestamp_t wakeup_timestamp = lp_ticker_read();

        TEST_ASSERT(compare_timestamps(us_to_ticks(deepsleep_mode_delta_us, ticker_freq), ticker_width, next_match_timestamp, wakeup_timestamp));
    }

    set_lp_ticker_irq_handler(lp_ticker_irq_handler_org);

}

void deepsleep_high_speed_clocks_turned_off_test()
{
    const ticker_data_t * us_ticker = get_us_ticker_data();
    const ticker_data_t * lp_ticker = get_lp_ticker_data();
    const unsigned int us_ticker_freq = us_ticker->interface->get_info()->frequency;
    const unsigned int lp_ticker_freq = lp_ticker->interface->get_info()->frequency;
    const unsigned int us_ticker_width = us_ticker->interface->get_info()->bits;
    const unsigned int lp_ticker_width = lp_ticker->interface->get_info()->bits;
    const unsigned int us_ticker_mask = ((1 << us_ticker_width) - 1);

    /* Give some time Green Tea to finish UART transmission before entering
     * deep-sleep mode.
     */
    wait_ms(10);

    TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should not be locked");

    const unsigned int us_ticks_before_sleep = us_ticker_read();

    const timestamp_t wakeup_time = lp_ticker_read() + us_to_ticks(20000, lp_ticker_freq);
    lp_ticker_set_interrupt(wakeup_time);

    sleep();

    const unsigned int us_ticks_after_sleep = us_ticker_read();
    const unsigned int lp_ticks_after_sleep = lp_ticker_read();

    /* High freqency ticker should be disabled in deep-sleep mode. We expect that time difference between
     * ticker reads before and after the sleep represents only code execution time between calls.
     * Since we went to sleep for about 20 ms check if time counted by high frequency timer does not
     * exceed 1 ms.
     */
    const unsigned int us_ticks_diff = (us_ticks_before_sleep <= us_ticks_after_sleep) ? (us_ticks_after_sleep - us_ticks_before_sleep) : (us_ticker_mask - us_ticks_before_sleep + us_ticks_after_sleep + 1);

    TEST_ASSERT_UINT32_WITHIN(1000, 0, ticks_to_us(us_ticks_diff, us_ticker_freq));

    /* Check if we have woken-up after expected time. */
    TEST_ASSERT(compare_timestamps(us_to_ticks(deepsleep_mode_delta_us, lp_ticker_freq), lp_ticker_width, wakeup_time, lp_ticks_after_sleep));
}

#endif

utest::v1::status_t greentea_failure_handler(const Case * const source, const failure_t reason)
{
    greentea_case_failure_abort_handler(source, reason);
    return STATUS_CONTINUE;
}

utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
{
    GREENTEA_SETUP(60, "default_auto");
    us_ticker_init();
#if DEVICE_LPTICKER
    lp_ticker_init();
#endif
    /* Suspend RTOS Kernel to enable sleep modes. */
    osKernelSuspend();
    return greentea_test_setup_handler(number_of_cases);
}

Case cases[] =
        { Case("sleep - source of wake-up - us ticker", sleep_usticker_test, greentea_failure_handler),
#if DEVICE_LPTICKER
        Case("deep-sleep - source of wake-up - lp ticker",deepsleep_lpticker_test, greentea_failure_handler),
        Case("deep-sleep - high-speed clocks are turned off",deepsleep_high_speed_clocks_turned_off_test, greentea_failure_handler),
#endif
        };

Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler);

int main()
{
    Harness::run(specification);
}
<commit_msg>HAL sleep test fix<commit_after>/* mbed Microcontroller Library
 * Copyright (c) 2017 ARM Limited
 *
 * 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.
 */

#if 1 || !DEVICE_SLEEP
#error [NOT_SUPPORTED] sleep not supported for this target
#endif

#include "mbed.h"

#include "utest/utest.h"
#include "unity/unity.h"
#include "greentea-client/test_env.h"

#include "sleep_api_tests.h"

#define US_PER_S 1000000

using namespace utest::v1;

/* The following ticker frequencies are possible:
 * high frequency ticker: 250 KHz (1 tick per 4 us) - 8 Mhz (1 tick per 1/8 us)
 * low power ticker: 8 KHz (1 tick per 125 us) - 64 KHz (1 tick per ~15.6 us)
 */

/* Used for regular sleep mode, a target should be awake within 10 us. Define us delta value as follows:
 * delta = default 10 us + worst ticker resolution + extra time for code execution */
static const uint32_t sleep_mode_delta_us = (10 + 4 + 5);

/* Used for deep-sleep mode, a target should be awake within 10 ms. Define us delta value as follows:
 * delta = default 10 ms + worst ticker resolution + extra time for code execution */
static const uint32_t deepsleep_mode_delta_us = (10000 + 125 + 5);

unsigned int ticks_to_us(unsigned int ticks, unsigned int freq)
{
    return (unsigned int) ((unsigned long long) ticks * US_PER_S / freq);
}

unsigned int us_to_ticks(unsigned int us, unsigned int freq)
{
    return (unsigned int) ((unsigned long long) us * freq / US_PER_S);
}

unsigned int overflow_protect(unsigned int timestamp, unsigned int ticker_width)
{
    unsigned int counter_mask = ((1 << ticker_width) - 1);

    return (timestamp & counter_mask);
}

bool compare_timestamps(unsigned int delta_ticks, unsigned int ticker_width, unsigned int expected, unsigned int actual)
{
    const unsigned int counter_mask = ((1 << ticker_width) - 1);

    const unsigned int lower_bound = ((expected - delta_ticks) & counter_mask);
    const unsigned int upper_bound = ((expected + delta_ticks) & counter_mask);

    if (lower_bound < upper_bound) {
        if (actual >= lower_bound && actual <= upper_bound) {
            return true;
        } else {
            return false;
        }
    } else {
        if ((actual >= lower_bound && actual <= counter_mask) || (actual >= 0 && actual <= upper_bound)) {
            return true;
        } else {
            return false;
        }
    }
}

void us_ticker_isr(const ticker_data_t * const ticker_data)
{
    us_ticker_clear_interrupt();
}

#ifdef DEVICE_LPTICKER
void lp_ticker_isr(const ticker_data_t *const ticker_data)
{
    lp_ticker_clear_interrupt();
}
#endif

/* Test that wake-up time from sleep should be less than 10 us and
 * high frequency ticker interrupt can wake-up target from sleep. */
void sleep_usticker_test()
{
#if 0
    const ticker_data_t * ticker = get_us_ticker_data();
    const unsigned int ticker_freq = ticker->interface->get_info()->frequency;
    const unsigned int ticker_width = ticker->interface->get_info()->bits;

    const ticker_irq_handler_type us_ticker_irq_handler_org = set_us_ticker_irq_handler(us_ticker_isr);

    // call ticker_read_us to initialize ticker upper layer
    // prevents subsequent scheduling of max_delta interrupt during ticker initialization while test execution
    // (e.g when ticker_read_us is called)
    ticker_read_us(ticker);

    /* Test only sleep functionality. */
    sleep_manager_lock_deep_sleep();
    TEST_ASSERT_FALSE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should be locked");

    /* Testing wake-up time 10 us. */
    for (timestamp_t i = 100; i < 1000; i += 100) {
        /* note: us_ticker_read() operates on ticks. */
        const timestamp_t next_match_timestamp = overflow_protect(us_ticker_read() + us_to_ticks(i, ticker_freq),
                ticker_width);

        us_ticker_set_interrupt(next_match_timestamp);

        sleep();

        const unsigned int wakeup_timestamp = us_ticker_read();

        TEST_ASSERT(
                compare_timestamps(us_to_ticks(sleep_mode_delta_us, ticker_freq), ticker_width, next_match_timestamp,
                        wakeup_timestamp));
    }

    set_us_ticker_irq_handler(us_ticker_irq_handler_org);

    sleep_manager_unlock_deep_sleep();
    TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep());
#endif
}

#ifdef DEVICE_LPTICKER

/* Test that wake-up time from sleep should be less than 10 ms and
 * low power ticker interrupt can wake-up target from sleep. */
void deepsleep_lpticker_test()
{
    const ticker_data_t * ticker = get_lp_ticker_data();
    const unsigned int ticker_freq = ticker->interface->get_info()->frequency;
    const unsigned int ticker_width = ticker->interface->get_info()->bits;

    // call ticker_read_us to initialize ticker upper layer
    // prevents subsequent scheduling of max_delta interrupt during ticker initialization while test execution
    // (e.g when ticker_read_us is called)
    ticker_read_us(ticker);

    const ticker_irq_handler_type lp_ticker_irq_handler_org = set_lp_ticker_irq_handler(lp_ticker_isr);

    /* Give some time Green Tea to finish UART transmission before entering
     * deep-sleep mode.
     */
    wait_ms(10);

    TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should not be locked");

    /* Testing wake-up time 10 ms. */
    for (timestamp_t i = 20000; i < 200000; i += 20000) {
        /* note: lp_ticker_read() operates on ticks. */
        const timestamp_t next_match_timestamp = overflow_protect(lp_ticker_read() + us_to_ticks(i, ticker_freq), ticker_width);

        lp_ticker_set_interrupt(next_match_timestamp);

        sleep();

        const timestamp_t wakeup_timestamp = lp_ticker_read();

        TEST_ASSERT(compare_timestamps(us_to_ticks(deepsleep_mode_delta_us, ticker_freq), ticker_width, next_match_timestamp, wakeup_timestamp));
    }

    set_lp_ticker_irq_handler(lp_ticker_irq_handler_org);

}

void deepsleep_high_speed_clocks_turned_off_test()
{
    const ticker_data_t * us_ticker = get_us_ticker_data();
    const ticker_data_t * lp_ticker = get_lp_ticker_data();
    const unsigned int us_ticker_freq = us_ticker->interface->get_info()->frequency;
    const unsigned int lp_ticker_freq = lp_ticker->interface->get_info()->frequency;
    const unsigned int us_ticker_width = us_ticker->interface->get_info()->bits;
    const unsigned int lp_ticker_width = lp_ticker->interface->get_info()->bits;
    const unsigned int us_ticker_mask = ((1 << us_ticker_width) - 1);

    /* Give some time Green Tea to finish UART transmission before entering
     * deep-sleep mode.
     */
    wait_ms(10);

    TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should not be locked");

    const unsigned int us_ticks_before_sleep = us_ticker_read();

    const timestamp_t wakeup_time = lp_ticker_read() + us_to_ticks(20000, lp_ticker_freq);
    lp_ticker_set_interrupt(wakeup_time);

    sleep();

    const unsigned int us_ticks_after_sleep = us_ticker_read();
    const unsigned int lp_ticks_after_sleep = lp_ticker_read();

    /* High freqency ticker should be disabled in deep-sleep mode. We expect that time difference between
     * ticker reads before and after the sleep represents only code execution time between calls.
     * Since we went to sleep for about 20 ms check if time counted by high frequency timer does not
     * exceed 1 ms.
     */
    const unsigned int us_ticks_diff = (us_ticks_before_sleep <= us_ticks_after_sleep) ? (us_ticks_after_sleep - us_ticks_before_sleep) : (us_ticker_mask - us_ticks_before_sleep + us_ticks_after_sleep + 1);

    TEST_ASSERT_UINT32_WITHIN(1000, 0, ticks_to_us(us_ticks_diff, us_ticker_freq));

    /* Check if we have woken-up after expected time. */
    TEST_ASSERT(compare_timestamps(us_to_ticks(deepsleep_mode_delta_us, lp_ticker_freq), lp_ticker_width, wakeup_time, lp_ticks_after_sleep));
}

#endif

utest::v1::status_t greentea_failure_handler(const Case * const source, const failure_t reason)
{
    greentea_case_failure_abort_handler(source, reason);
    return STATUS_CONTINUE;
}

utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
{
    GREENTEA_SETUP(60, "default_auto");
    us_ticker_init();
#if DEVICE_LPTICKER
    lp_ticker_init();
#endif
    /* Suspend RTOS Kernel to enable sleep modes. */
    osKernelSuspend();
    return greentea_test_setup_handler(number_of_cases);
}

Case cases[] =
        { Case("sleep - source of wake-up - us ticker", sleep_usticker_test, greentea_failure_handler),
#if DEVICE_LPTICKER
        Case("deep-sleep - source of wake-up - lp ticker",deepsleep_lpticker_test, greentea_failure_handler),
        Case("deep-sleep - high-speed clocks are turned off",deepsleep_high_speed_clocks_turned_off_test, greentea_failure_handler),
#endif
        };

Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler);

int main()
{
    Harness::run(specification);
}
<|endoftext|>


<commit_before>/* -*- coding: utf-8; mode: c++; tab-width: 3; indent-tabs-mode: nil -*-

Copyright 2015
Raffaello D. Di Napoli

This file is part of Abaclade.

Abaclade 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 3 of the
License, or (at your option) any later version.

Abaclade 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 Abaclade. If not, see
<http://www.gnu.org/licenses/>.
--------------------------------------------------------------------------------------------------*/

#include <abaclade.hxx>
#include <abaclade/coroutine.hxx>

#if ABC_HOST_API_POSIX
   #include <errno.h> // EINTR errno
   #if ABC_HOST_API_DARWIN
      #define _XOPEN_SOURCE
   #endif
   #include <signal.h> // MINSIGSTKSZ
   #include <ucontext.h>
   #if ABC_HOST_API_BSD
      #include <sys/types.h>
      #include <sys/event.h>
      #include <sys/time.h>
   #elif ABC_HOST_API_LINUX
      #include <sys/epoll.h>
   #endif
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
// abc::coroutine

namespace abc {

class coroutine::context : public noncopyable {
public:
   context(std::function<void ()> fnMain) :
      m_fnInnerMain(std::move(fnMain)) {
   }

#if ABC_HOST_API_POSIX
   void reset(::ucontext_t * puctxReturn) {
      if (::getcontext(&m_uctx) < 0) {
         exception::throw_os_error();
      }
      m_uctx.uc_stack.ss_sp = reinterpret_cast<char *>(&m_aiStack);
      m_uctx.uc_stack.ss_size = sizeof m_aiStack;
      m_uctx.uc_link = puctxReturn;
      ::makecontext(&m_uctx, reinterpret_cast<void (*)()>(&outer_main), 1, this);
   }
#elif ABC_HOST_API_WIN32 //if ABC_HOST_API_POSIX
   #error "TODO: HOST_API"
#else //if ABC_HOST_API_POSIX … elif ABC_HOST_API_WIN32
   #error "TODO: HOST_API"
#endif //if ABC_HOST_API_POSIX … elif ABC_HOST_API_WIN32 … else

#if ABC_HOST_API_POSIX
   /*! Returns a pointer to the internal ::ucontext_t.

   @return
      Pointer to the context’s ::ucontext_t member.
   */
   ::ucontext_t * ucontext_ptr() {
      return &m_uctx;
   }
#endif

private:
   static void outer_main(void * p) {
      context * pctx = static_cast<context *>(p);
      try {
         pctx->m_fnInnerMain();
      } catch (std::exception const & x) {
         exception::write_with_scope_trace(nullptr, &x);
         // TODO: maybe support “moving” the exception to the return coroutine context?
      } catch (...) {
         exception::write_with_scope_trace();
         // TODO: maybe support “moving” the exception to the return coroutine context?
      }
   }

private:
#if ABC_HOST_API_POSIX
   ::ucontext_t m_uctx;
   abc::max_align_t m_aiStack[ABC_ALIGNED_SIZE(MINSIGSTKSZ)];
#elif ABC_HOST_API_WIN32 //if ABC_HOST_API_POSIX
   #error "TODO: HOST_API"
#else //if ABC_HOST_API_POSIX … elif ABC_HOST_API_WIN32
   #error "TODO: HOST_API"
#endif //if ABC_HOST_API_POSIX … elif ABC_HOST_API_WIN32 … else
   std::function<void ()> m_fnInnerMain;
};

coroutine::coroutine() {
}
/*explicit*/ coroutine::coroutine(std::function<void ()> fnMain) :
   m_pctx(std::make_shared<coroutine::context>(std::move(fnMain))) {
}

coroutine::~coroutine() {
}

} //namespace abc

////////////////////////////////////////////////////////////////////////////////////////////////////
// abc::coroutine_scheduler

namespace abc {

#if ABC_HOST_API_POSIX

class coroutine_scheduler_impl : public coroutine_scheduler {
public:
   //! Constructor.
   coroutine_scheduler_impl() :
#if ABC_HOST_API_BSD
      m_fdKqueue(::kqueue()) {
      if (!m_fdKqueue) {
         exception::throw_os_error();
      }
      // TODO: set close-on-exec. ::kqueue1() may be available on some systems; investigate that.
#elif ABC_HOST_API_LINUX
      m_fdEpoll(::epoll_create1(EPOLL_CLOEXEC)) {
      if (!m_fdEpoll) {
         exception::throw_os_error();
      }
#else
   #error "TODO: HOST_API"
#endif
   }

   //! Destructor.
   virtual ~coroutine_scheduler_impl() {
      // TODO: verify that m_listStartingCoros and m_mapBlockedCoros are empty.
   }

   //! See coroutine_scheduler::run().
   virtual void run() override {
      while ((m_pcoroctxActive = find_coroutine_to_activate())) {
         if (::swapcontext(&m_uctxReturn, m_pcoroctxActive->ucontext_ptr()) < 0) {
            /* TODO: in case of errors, which should never happen here since all coroutines have the
            same stack size, inject a stack overflow exception in *m_pcoroctxActive. */
         }
      }
      // Release the last coroutine.
      m_pcoroctxActive.reset();
   }

   //! See coroutine_scheduler::yield_while_async_pending().
   virtual void yield_while_async_pending(io::filedesc const & fd, bool bWrite) override {
      // Add fd as a new event source.
#if ABC_HOST_API_BSD
      struct ::kevent ke;
      ke.ident = static_cast<std::uintptr_t>(fd.get());
      // Use EV_ONESHOT to avoid waking up multiple threads for the same fd becoming ready.
      ke.flags = EV_ADD | EV_ONESHOT | EV_EOF ;
      ke.filter = bWrite ? EVFILT_WRITE : EVFILT_READ;
      if (::kevent(m_fdKqueue.get(), &ke, 1, nullptr, 0, nullptr) < 0) {
         exception::throw_os_error();
      }
#elif ABC_HOST_API_LINUX
      ::epoll_event ee;
      ee.data.fd = fd.get();
      /* Use EPOLLONESHOT to avoid waking up multiple threads for the same fd becoming ready. This
      means we’d need to then rearm it in find_coroutine_to_activate() when it becomes ready, but
      we’ll remove it instead. */
      ee.events = EPOLLONESHOT | EPOLLPRI | (bWrite ? EPOLLOUT : EPOLLIN);
      if (::epoll_ctl(m_fdEpoll.get(), EPOLL_CTL_ADD, fd.get(), &ee) < 0) {
         exception::throw_os_error();
      }
#else
   #error "TODO: HOST_API"
#endif
      // Deactivate the current coroutine and find one to activate instead.
      coroutine::context * pcoroctxActive = m_pcoroctxActive.get();
      auto itBlockedCoro(
         m_mapBlockedCoros.add_or_assign(fd.get(), std::move(m_pcoroctxActive)).first
      );
      try {
         m_pcoroctxActive = find_coroutine_to_activate();
      } catch (...) {
         // If anything went wrong, restore the coroutine that was active.
         m_pcoroctxActive = m_mapBlockedCoros.extract(itBlockedCoro);
         throw;
      }
      /* If the context changed, i.e. the coroutine that’s ready to run is not the one that was
      active, switch to it. */
      if (m_pcoroctxActive.get() != pcoroctxActive) {
         if (::swapcontext(pcoroctxActive->ucontext_ptr(), m_pcoroctxActive->ucontext_ptr()) < 0) {
            /* TODO: in case of errors, which should never happen here since all coroutines have the
            same stack size, inject a stack overflow exception in *m_pcoroctxActive. */
         }
      }
   }

private:
   std::shared_ptr<coroutine::context> find_coroutine_to_activate() {
      // This loop will only repeat in case of EINTR from the blocking-wait API.
      for (;;) {
         if (m_listStartingCoros) {
            // There are coroutines that haven’t had a chance to run; remove and schedule the first.
            auto pcoroctx(m_listStartingCoros.pop_front());
            /* TODO: verify how this behaves in a multithreaded scenario: which thread should this
            coroutine return to? */
            pcoroctx->reset(&m_uctxReturn);
            return std::move(pcoroctx);
         } else if (m_mapBlockedCoros) {
            // There are blocked coroutines; wait for the first one to become ready again.
#if ABC_HOST_API_BSD
            struct ::kevent ke;
            if (::kevent(m_fdKqueue.get(), nullptr, 0, &ke, 1, nullptr) < 0) {
#elif ABC_HOST_API_LINUX
            ::epoll_event ee;
            if (::epoll_wait(m_fdEpoll.get(), &ee, 1, -1) < 0) {
#else
   #error "TODO: HOST_API"
#endif
               int iErr = errno;
               if (iErr == EINTR) {
                  continue;
               }
               exception::throw_os_error(iErr);
            }
            io::filedesc_t fd;
#if ABC_HOST_API_BSD
            fd = static_cast<int>(ke.ident);
#elif ABC_HOST_API_LINUX
            fd = ee.data.fd;
            // Remove fd from the epoll. Ignore errors since we wouldn’t know what to do aobut them.
            ::epoll_ctl(m_fdEpoll.get(), EPOLL_CTL_DEL, fd, nullptr);
#endif
            // Remove from m_mapBlockedCoros and return the coroutine that was waiting for fd.
            return m_mapBlockedCoros.extract(fd);
         } else {
            return nullptr;
         }
      }
   }

private:
#if ABC_HOST_API_BSD
   //! File descriptor of the internal kqueue.
   io::filedesc m_fdKqueue;
#elif ABC_HOST_API_LINUX
   //! File descriptor of the internal epoll.
   io::filedesc m_fdEpoll;
#else
   #error "TODO: HOST_API"
#endif
   //! Coroutines that are blocked on a fd wait.
   collections::map<io::filedesc_t, std::shared_ptr<coroutine::context>> m_mapBlockedCoros;
   //! Coroutine context that every coroutine eventually returns to.
   ::ucontext_t m_uctxReturn;
};

#elif ABC_HOST_API_WIN32 //if ABC_HOST_API_POSIX
   #error "TODO: HOST_API"
#else //if ABC_HOST_API_POSIX … elif ABC_HOST_API_WIN32
   #error "TODO: HOST_API"
#endif //if ABC_HOST_API_POSIX … elif ABC_HOST_API_WIN32 … else

thread_local_value<std::shared_ptr<coroutine_scheduler>> coroutine_scheduler::sm_pcorosched;

coroutine_scheduler::coroutine_scheduler() :
   m_pcoroctxActive(nullptr) {
}

coroutine_scheduler::~coroutine_scheduler() {
}

void coroutine_scheduler::add_coroutine(coroutine const & coro) {
   // Add the coroutine to those ready to start.
   m_listStartingCoros.push_back(coro.m_pctx);
}

/*static*/ coroutine_scheduler & coroutine_scheduler::attach_to_current_thread(
   std::shared_ptr<coroutine_scheduler> pcorosched /*= nullptr*/
) {
   if (sm_pcorosched.operator std::shared_ptr<coroutine_scheduler> const &()) {
      // The current thread already has a coroutine scheduler.
      // TODO: use a better exception class.
      ABC_THROW(generic_error, ());
   }
   if (!pcorosched) {
      pcorosched = std::make_shared<coroutine_scheduler_impl>();
   }
   return *(
      sm_pcorosched = std::move(pcorosched)
   ).operator std::shared_ptr<coroutine_scheduler> const &();
}

} //namespace abc

////////////////////////////////////////////////////////////////////////////////////////////////////
<commit_msg>Use SIGSTKSZ instead of MINSIGSTKSZ, which is too little under Linux<commit_after>/* -*- coding: utf-8; mode: c++; tab-width: 3; indent-tabs-mode: nil -*-

Copyright 2015
Raffaello D. Di Napoli

This file is part of Abaclade.

Abaclade 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 3 of the
License, or (at your option) any later version.

Abaclade 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 Abaclade. If not, see
<http://www.gnu.org/licenses/>.
--------------------------------------------------------------------------------------------------*/

#include <abaclade.hxx>
#include <abaclade/coroutine.hxx>

#if ABC_HOST_API_POSIX
   #include <errno.h> // EINTR errno
   #if ABC_HOST_API_DARWIN
      #define _XOPEN_SOURCE
   #endif
   #include <signal.h> // SIGSTKSZ
   #include <ucontext.h>
   #if ABC_HOST_API_BSD
      #include <sys/types.h>
      #include <sys/event.h>
      #include <sys/time.h>
   #elif ABC_HOST_API_LINUX
      #include <sys/epoll.h>
   #endif
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
// abc::coroutine

namespace abc {

class coroutine::context : public noncopyable {
public:
   context(std::function<void ()> fnMain) :
      m_fnInnerMain(std::move(fnMain)) {
   }

#if ABC_HOST_API_POSIX
   void reset(::ucontext_t * puctxReturn) {
      if (::getcontext(&m_uctx) < 0) {
         exception::throw_os_error();
      }
      m_uctx.uc_stack.ss_sp = reinterpret_cast<char *>(&m_aiStack);
      m_uctx.uc_stack.ss_size = sizeof m_aiStack;
      m_uctx.uc_link = puctxReturn;
      ::makecontext(&m_uctx, reinterpret_cast<void (*)()>(&outer_main), 1, this);
   }
#elif ABC_HOST_API_WIN32 //if ABC_HOST_API_POSIX
   #error "TODO: HOST_API"
#else //if ABC_HOST_API_POSIX … elif ABC_HOST_API_WIN32
   #error "TODO: HOST_API"
#endif //if ABC_HOST_API_POSIX … elif ABC_HOST_API_WIN32 … else

#if ABC_HOST_API_POSIX
   /*! Returns a pointer to the internal ::ucontext_t.

   @return
      Pointer to the context’s ::ucontext_t member.
   */
   ::ucontext_t * ucontext_ptr() {
      return &m_uctx;
   }
#endif

private:
   static void outer_main(void * p) {
      context * pctx = static_cast<context *>(p);
      try {
         pctx->m_fnInnerMain();
      } catch (std::exception const & x) {
         exception::write_with_scope_trace(nullptr, &x);
         // TODO: maybe support “moving” the exception to the return coroutine context?
      } catch (...) {
         exception::write_with_scope_trace();
         // TODO: maybe support “moving” the exception to the return coroutine context?
      }
   }

private:
#if ABC_HOST_API_POSIX
   ::ucontext_t m_uctx;
   abc::max_align_t m_aiStack[ABC_ALIGNED_SIZE(SIGSTKSZ)];
#elif ABC_HOST_API_WIN32 //if ABC_HOST_API_POSIX
   #error "TODO: HOST_API"
#else //if ABC_HOST_API_POSIX … elif ABC_HOST_API_WIN32
   #error "TODO: HOST_API"
#endif //if ABC_HOST_API_POSIX … elif ABC_HOST_API_WIN32 … else
   std::function<void ()> m_fnInnerMain;
};

coroutine::coroutine() {
}
/*explicit*/ coroutine::coroutine(std::function<void ()> fnMain) :
   m_pctx(std::make_shared<coroutine::context>(std::move(fnMain))) {
}

coroutine::~coroutine() {
}

} //namespace abc

////////////////////////////////////////////////////////////////////////////////////////////////////
// abc::coroutine_scheduler

namespace abc {

#if ABC_HOST_API_POSIX

class coroutine_scheduler_impl : public coroutine_scheduler {
public:
   //! Constructor.
   coroutine_scheduler_impl() :
#if ABC_HOST_API_BSD
      m_fdKqueue(::kqueue()) {
      if (!m_fdKqueue) {
         exception::throw_os_error();
      }
      // TODO: set close-on-exec. ::kqueue1() may be available on some systems; investigate that.
#elif ABC_HOST_API_LINUX
      m_fdEpoll(::epoll_create1(EPOLL_CLOEXEC)) {
      if (!m_fdEpoll) {
         exception::throw_os_error();
      }
#else
   #error "TODO: HOST_API"
#endif
   }

   //! Destructor.
   virtual ~coroutine_scheduler_impl() {
      // TODO: verify that m_listStartingCoros and m_mapBlockedCoros are empty.
   }

   //! See coroutine_scheduler::run().
   virtual void run() override {
      while ((m_pcoroctxActive = find_coroutine_to_activate())) {
         if (::swapcontext(&m_uctxReturn, m_pcoroctxActive->ucontext_ptr()) < 0) {
            /* TODO: in case of errors, which should never happen here since all coroutines have the
            same stack size, inject a stack overflow exception in *m_pcoroctxActive. */
         }
      }
      // Release the last coroutine.
      m_pcoroctxActive.reset();
   }

   //! See coroutine_scheduler::yield_while_async_pending().
   virtual void yield_while_async_pending(io::filedesc const & fd, bool bWrite) override {
      // Add fd as a new event source.
#if ABC_HOST_API_BSD
      struct ::kevent ke;
      ke.ident = static_cast<std::uintptr_t>(fd.get());
      // Use EV_ONESHOT to avoid waking up multiple threads for the same fd becoming ready.
      ke.flags = EV_ADD | EV_ONESHOT | EV_EOF ;
      ke.filter = bWrite ? EVFILT_WRITE : EVFILT_READ;
      if (::kevent(m_fdKqueue.get(), &ke, 1, nullptr, 0, nullptr) < 0) {
         exception::throw_os_error();
      }
#elif ABC_HOST_API_LINUX
      ::epoll_event ee;
      ee.data.fd = fd.get();
      /* Use EPOLLONESHOT to avoid waking up multiple threads for the same fd becoming ready. This
      means we’d need to then rearm it in find_coroutine_to_activate() when it becomes ready, but
      we’ll remove it instead. */
      ee.events = EPOLLONESHOT | EPOLLPRI | (bWrite ? EPOLLOUT : EPOLLIN);
      if (::epoll_ctl(m_fdEpoll.get(), EPOLL_CTL_ADD, fd.get(), &ee) < 0) {
         exception::throw_os_error();
      }
#else
   #error "TODO: HOST_API"
#endif
      // Deactivate the current coroutine and find one to activate instead.
      coroutine::context * pcoroctxActive = m_pcoroctxActive.get();
      auto itBlockedCoro(
         m_mapBlockedCoros.add_or_assign(fd.get(), std::move(m_pcoroctxActive)).first
      );
      try {
         m_pcoroctxActive = find_coroutine_to_activate();
      } catch (...) {
         // If anything went wrong, restore the coroutine that was active.
         m_pcoroctxActive = m_mapBlockedCoros.extract(itBlockedCoro);
         throw;
      }
      /* If the context changed, i.e. the coroutine that’s ready to run is not the one that was
      active, switch to it. */
      if (m_pcoroctxActive.get() != pcoroctxActive) {
         if (::swapcontext(pcoroctxActive->ucontext_ptr(), m_pcoroctxActive->ucontext_ptr()) < 0) {
            /* TODO: in case of errors, which should never happen here since all coroutines have the
            same stack size, inject a stack overflow exception in *m_pcoroctxActive. */
         }
      }
   }

private:
   std::shared_ptr<coroutine::context> find_coroutine_to_activate() {
      // This loop will only repeat in case of EINTR from the blocking-wait API.
      for (;;) {
         if (m_listStartingCoros) {
            // There are coroutines that haven’t had a chance to run; remove and schedule the first.
            auto pcoroctx(m_listStartingCoros.pop_front());
            /* TODO: verify how this behaves in a multithreaded scenario: which thread should this
            coroutine return to? */
            pcoroctx->reset(&m_uctxReturn);
            return std::move(pcoroctx);
         } else if (m_mapBlockedCoros) {
            // There are blocked coroutines; wait for the first one to become ready again.
#if ABC_HOST_API_BSD
            struct ::kevent ke;
            if (::kevent(m_fdKqueue.get(), nullptr, 0, &ke, 1, nullptr) < 0) {
#elif ABC_HOST_API_LINUX
            ::epoll_event ee;
            if (::epoll_wait(m_fdEpoll.get(), &ee, 1, -1) < 0) {
#else
   #error "TODO: HOST_API"
#endif
               int iErr = errno;
               if (iErr == EINTR) {
                  continue;
               }
               exception::throw_os_error(iErr);
            }
            io::filedesc_t fd;
#if ABC_HOST_API_BSD
            fd = static_cast<int>(ke.ident);
#elif ABC_HOST_API_LINUX
            fd = ee.data.fd;
            // Remove fd from the epoll. Ignore errors since we wouldn’t know what to do aobut them.
            ::epoll_ctl(m_fdEpoll.get(), EPOLL_CTL_DEL, fd, nullptr);
#endif
            // Remove from m_mapBlockedCoros and return the coroutine that was waiting for fd.
            return m_mapBlockedCoros.extract(fd);
         } else {
            return nullptr;
         }
      }
   }

private:
#if ABC_HOST_API_BSD
   //! File descriptor of the internal kqueue.
   io::filedesc m_fdKqueue;
#elif ABC_HOST_API_LINUX
   //! File descriptor of the internal epoll.
   io::filedesc m_fdEpoll;
#else
   #error "TODO: HOST_API"
#endif
   //! Coroutines that are blocked on a fd wait.
   collections::map<io::filedesc_t, std::shared_ptr<coroutine::context>> m_mapBlockedCoros;
   //! Coroutine context that every coroutine eventually returns to.
   ::ucontext_t m_uctxReturn;
};

#elif ABC_HOST_API_WIN32 //if ABC_HOST_API_POSIX
   #error "TODO: HOST_API"
#else //if ABC_HOST_API_POSIX … elif ABC_HOST_API_WIN32
   #error "TODO: HOST_API"
#endif //if ABC_HOST_API_POSIX … elif ABC_HOST_API_WIN32 … else

thread_local_value<std::shared_ptr<coroutine_scheduler>> coroutine_scheduler::sm_pcorosched;

coroutine_scheduler::coroutine_scheduler() :
   m_pcoroctxActive(nullptr) {
}

coroutine_scheduler::~coroutine_scheduler() {
}

void coroutine_scheduler::add_coroutine(coroutine const & coro) {
   // Add the coroutine to those ready to start.
   m_listStartingCoros.push_back(coro.m_pctx);
}

/*static*/ coroutine_scheduler & coroutine_scheduler::attach_to_current_thread(
   std::shared_ptr<coroutine_scheduler> pcorosched /*= nullptr*/
) {
   if (sm_pcorosched.operator std::shared_ptr<coroutine_scheduler> const &()) {
      // The current thread already has a coroutine scheduler.
      // TODO: use a better exception class.
      ABC_THROW(generic_error, ());
   }
   if (!pcorosched) {
      pcorosched = std::make_shared<coroutine_scheduler_impl>();
   }
   return *(
      sm_pcorosched = std::move(pcorosched)
   ).operator std::shared_ptr<coroutine_scheduler> const &();
}

} //namespace abc

////////////////////////////////////////////////////////////////////////////////////////////////////
<|endoftext|>


<commit_before>#include "DySySim.h"
#include "LibInfoDySySim.h"
#include "SimBlock.h"

#include <UnitTest++/UnitTest++.h>

#include <iostream>

namespace dss = dysysim;
using namespace std;

const double EPS{0.01};

SUITE(DySySim)
{
   TEST(Constant)
   {
      cout << "-- Constant" << endl;

      dss::SimTime::set(0.1, 0.3);

      dss::Log log;
      log.config({1, {2}, {}});

      dss::Constant con;
      double c = 3.0;
      con.config({2, {}, {c}});

      log.exe();
      do {
         CHECK_CLOSE(c, con.output(), EPS);
      } while (dss::SimTime::simulation_on());
   }

   TEST(Attenuator)
   {
      cout << "-- Attenuator" << endl;

      dss::SimTime::set(1.0, 3.0);

      dss::Log log;
      log.config({1, {2, 3}, {}});

      dss::Attenuator att1;
      att1.config({2, {}, {10.0}});
      dss::Attenuator att2;
      att2.config({3, {}, {-10.0}});

      log.exe();
      do {
         att1.input(1.0);
         CHECK_CLOSE(0.1, att1.output(), EPS);
         att2.input(1.0);
         CHECK_CLOSE(-0.1, att2.output(), EPS);
      } while (dss::SimTime::simulation_on());
   }

   TEST(Gain)
   {
      cout << "-- Gain" << endl;

      dss::SimTime::set(1.0, 3.0);

      dss::Gain gain1;
      gain1.config({2, {}, {10.0}});
      dss::Gain gain2;
      gain2.config({3, {}, {-5}});

      dss::Log log;
      log.config({1, {2, 3}, {}});

      log.exe();
      do {
         gain1.input(1.0);
         CHECK_CLOSE(10.0, gain1.output(), EPS);
         gain2.input(-1.0);
         CHECK_CLOSE(5, gain2.output(), EPS);
      } while (dss::SimTime::simulation_on());
   }

   TEST(Limit)
   {
      cout << "-- Limit" << endl;

      dss::SimTime::set(1.0, 3.0);

      dss::Limit limit;
      limit.config({1, {}, {-10.0, 20.0}});

      do {
         limit.input(0.0);
         CHECK_CLOSE(0.0, limit.output(), EPS);
         limit.input(-12.0);
         CHECK_CLOSE(-10.0, limit.output(), EPS);
         limit.input(22.0);
         CHECK_CLOSE(20.0, limit.output(), EPS);
      } while (dss::SimTime::simulation_on());
   }

   TEST(OnOff)
   {
      cout << "-- OnOff" << endl;

      dss::SimBlock::clearSimBlocks();

      dss::OnOff onoff;
      double off = -2.0;
      double on = 2.0;
      double on_off = 1.0;
      // OnOff has no input from other block (use id 0).
      onoff.config({1, {0}, {off, on, on_off}});

      onoff.input(-1.0);
      CHECK_CLOSE(off, onoff.output(), EPS);
      onoff.input(0.0);
      CHECK_CLOSE(off, onoff.output(), EPS);
      onoff.input(1.5);
      CHECK_CLOSE(on, onoff.output(), EPS);
      onoff.input(10.0);
      CHECK_CLOSE(on, onoff.output(), EPS);
   }

   TEST(Time)
   {
      cout << "-- Time" << endl;

      const double delta_t{0.1};

      dss::SimTime::set(delta_t, 3.0);

      dss::Time time;
      time.config({1, {}, {}});

      std::vector<int> exeSequence{{1}};
      dss::SimBlock::setExeSequence(exeSequence);

      do {
         CHECK_CLOSE(dss::SimTime::t, time.output(), EPS);
      } while (dss::SimTime::simulation_on());
   }

   TEST(Step)
   {
      const double delta_t{0.1};

      dss::SimTime::set(delta_t, 10 * delta_t);

      cout << "-- Step" << endl;

      dss::Step step;
      double off = -22.0;
      double on = 11.0;
      double t_on = 4 * delta_t;
      step.config({1, {}, {off, on, t_on}});

      do {
         if (dss::SimTime::t < 4 * delta_t) {
            CHECK_CLOSE(off, step.output(), EPS);
         } else {
            CHECK_CLOSE(on, step.output(), EPS);
         }
      } while (dss::SimTime::simulation_on());
   }

   TEST(Puls)
   {
      const double delta_t{0.1};

      dss::SimTime::set(delta_t, 10 * delta_t);

      cout << "-- Puls" << endl;

      dss::Puls puls;
      double off = 0.0;
      double on = 1.0;
      double t_on = 5 * delta_t;
      double t_off = 10 * delta_t;
      puls.config({2, {}, {off, on, t_on, t_off}});

      dss::Log log;
      log.config({3, {2}, {}});

      log.exe();
      while (dss::SimTime::t < t_on) {
         CHECK_CLOSE(off, puls.output(), EPS);
         dysysim::SimBlock::exeSimBlocks();
      }

      while (dss::SimTime::t >= t_on and dss::SimTime::t < t_off) {
         CHECK_CLOSE(on, puls.output(), EPS);
         dysysim::SimBlock::exeSimBlocks();
      }

      while (dss::SimTime::t >= t_off + (5 * delta_t)) {
         CHECK_CLOSE(off, puls.output(), EPS);
         dysysim::SimBlock::exeSimBlocks();
      }
   }

   TEST(Delay)
   {
      const double delta_t{0.1};
      const double delayTime{3 * delta_t};

      dss::SimTime::set(delta_t, 10 * delta_t);

      cout << "-- Delay" << endl;

      dss::Log log;
      log.config({1, {2, 3}, {}});

      double t_on = 2 * delta_t;
      dss::Step step;
      step.config({2, {}, {0.0, 1.0, t_on}});

      dss::Delay delay;
      delay.config({3, {}, {0.0, delayTime}});

      CHECK_CLOSE(0.0, delay.output(), EPS);

      log.exe();
      do {
         auto input = step.output();
         delay.input(input);
         if (dss::SimTime::t < t_on + delayTime) {
            CHECK_CLOSE(0.0, delay.output(), EPS);
         } else {
            CHECK_CLOSE(1.0, delay.output(), EPS);
         }
      } while (dss::SimTime::simulation_on());
   }

   // TEST(FirstOrder)
   // {
   //    const double delta_t{0.005};
   //    const double tau{100 * delta_t};
   //    const double stp{1.0};
   //    const double stp_t{5 * delta_t};

   //    dss::Time time;
   //    time.config({1, {}, {delta_t}});

   //    auto fio_response = [tau, stp_t, stp](double t) {
   //       return (t < stp_t) ? 0.0 : (stp * (1 - exp(-(t - stp_t) / tau)));
   //    };

   //    cout << "-- FirstOrder" << endl;

   //    dss::Step step;
   //    step.config({2, {}, {0.0, stp, stp_t}});

   //    dss::FirstOrder fio;
   //    fio.config({3, {}, {tau, 0.0}});

   //    while (time.output() < stp_t + 10 * tau) {
   //       auto input = step.output();
   //       fio.input(input);
   //       auto out1 = fio.output();
   //       auto out2 = fio_response(time.output());
   //       // std::cout << out1 << " == " << out2 << std::endl;

   //       CHECK_CLOSE(out1, out2, EPS);

   //       time.exe();
   //       step.exe();
   //    }
   // }

   //    TEST(RC_FirstOrder)
   //    {
   //       const double delta_t{0.005};
   //       const double tau{100 * delta_t};
   //       const double stp{1.0};
   //       const double stp_t{5 * delta_t};

   //       cout << "-- compare RC filter and FirstOrder" << endl;

   //       dss::Time time;
   //       time.config({0, {}, {delta_t}});

   //       dss::Step step;
   //       step.config({1, {}, {0.0, stp, stp_t}});

   //       dss::Attenuator att;
   //       att.config({2, {}, {tau}});

   //       dss::Integrator intgt;
   //       intgt.config({3, {}, {0.0}});

   //       dss::FirstOrder fio;
   //       fio.config({4, {}, {tau, 0.0}});

   //       while (time.output() < stp_t + 10 * tau) {
   //          auto input = step.output();

   //          att.input(input);
   //          att.input(step.output() - intgt.output());
   //          intgt.input(att.output());

   //          fio.input(input);

   //          auto out1 = intgt.output();
   //          auto out2 = fio.output();
   //          // std::cout << out1 << " == " << out2 << std::endl;

   //          CHECK_CLOSE(out1, out2, EPS);

   //          time.exe();
   //          step.exe();
   //       }
   //    }
}

int main()
{
   cout << "\n== Tests DySySim lib: " << dss::libVersion << " "
        << string(50, '=') << "\n\n";

   auto result = UnitTest::RunAllTests();

   cout << "\n" << string(80, '=') << endl;

   return result;
}
<commit_msg>Improve test code<commit_after>#include "DySySim.h"
#include "LibInfoDySySim.h"
#include "SimBlock.h"

#include <UnitTest++/UnitTest++.h>

#include <iostream>

namespace dss = dysysim;
using namespace std;

const double EPS{0.01};

SUITE(DySySim)
{
   TEST(Constant)
   {
      cout << "-- Constant" << endl;

      dss::SimTime::set(0.1, 0.3);

      dss::Log log;
      log.config({1, {2}, {}});

      dss::Constant con;
      double c = 3.0;
      con.config({2, {}, {c}});

      log.exe();
      do {
         CHECK_CLOSE(c, con.output(), EPS);
      } while (dss::SimTime::simulation_on());
   }

   TEST(Attenuator)
   {
      cout << "-- Attenuator" << endl;

      dss::SimTime::set(1.0, 3.0);

      dss::Log log;
      log.config({1, {2, 3}, {}});

      dss::Attenuator att1;
      att1.config({2, {}, {10.0}});
      dss::Attenuator att2;
      att2.config({3, {}, {-10.0}});

      log.exe();
      do {
         att1.input(1.0);
         CHECK_CLOSE(0.1, att1.output(), EPS);
         att2.input(1.0);
         CHECK_CLOSE(-0.1, att2.output(), EPS);
      } while (dss::SimTime::simulation_on());
   }

   TEST(Gain)
   {
      cout << "-- Gain" << endl;

      dss::SimTime::set(1.0, 3.0);

      dss::Gain gain1;
      gain1.config({2, {}, {10.0}});
      dss::Gain gain2;
      gain2.config({3, {}, {-5}});

      dss::Log log;
      log.config({1, {2, 3}, {}});

      log.exe();
      do {
         gain1.input(1.0);
         CHECK_CLOSE(10.0, gain1.output(), EPS);
         gain2.input(-1.0);
         CHECK_CLOSE(5, gain2.output(), EPS);
      } while (dss::SimTime::simulation_on());
   }

   TEST(Limit)
   {
      cout << "-- Limit" << endl;

      dss::SimTime::set(1.0, 3.0);

      dss::Limit limit;
      limit.config({1, {}, {-10.0, 20.0}});

      do {
         limit.input(0.0);
         CHECK_CLOSE(0.0, limit.output(), EPS);
         limit.input(-12.0);
         CHECK_CLOSE(-10.0, limit.output(), EPS);
         limit.input(22.0);
         CHECK_CLOSE(20.0, limit.output(), EPS);
      } while (dss::SimTime::simulation_on());
   }

   TEST(OnOff)
   {
      cout << "-- OnOff" << endl;

      dss::SimBlock::clearSimBlocks();

      dss::OnOff onoff;
      double off = -2.0;
      double on = 2.0;
      double on_off = 1.0;
      // OnOff has no input from other block (use id 0).
      onoff.config({1, {0}, {off, on, on_off}});

      onoff.input(-1.0);
      CHECK_CLOSE(off, onoff.output(), EPS);
      onoff.input(0.0);
      CHECK_CLOSE(off, onoff.output(), EPS);
      onoff.input(1.5);
      CHECK_CLOSE(on, onoff.output(), EPS);
      onoff.input(10.0);
      CHECK_CLOSE(on, onoff.output(), EPS);
   }

   TEST(Time)
   {
      cout << "-- Time" << endl;

      const double delta_t{0.1};

      dss::SimTime::set(delta_t, 3.0);

      dss::Time time;
      time.config({1, {}, {}});

      std::vector<int> exeSequence{{1}};
      dss::SimBlock::setExeSequence(exeSequence);

      do {
         CHECK_CLOSE(dss::SimTime::t, time.output(), EPS);
      } while (dss::SimTime::simulation_on());
   }

   TEST(Step)
   {
      const double delta_t{0.1};

      dss::SimTime::set(delta_t, 10 * delta_t);

      cout << "-- Step" << endl;

      dss::Step step;
      double off = -22.0;
      double on = 11.0;
      double t_on = 4 * delta_t;
      step.config({1, {}, {off, on, t_on}});

      std::vector<int> exeSequence{{1}};
      dss::SimBlock::setExeSequence(exeSequence);

      do {
         if (dss::SimTime::t < 4 * delta_t) {
            CHECK_CLOSE(off, step.output(), EPS);
         } else {
            CHECK_CLOSE(on, step.output(), EPS);
         }
      } while (dss::SimTime::simulation_on());
   }

   TEST(Puls)
   {
      const double delta_t{0.1};

      dss::SimTime::set(delta_t, 10 * delta_t);

      cout << "-- Puls" << endl;

      dss::Puls puls;
      double off = 0.0;
      double on = 1.0;
      double t_on = 5 * delta_t;
      double t_off = 10 * delta_t;
      puls.config({1, {}, {off, on, t_on, t_off}});

      dss::Log log;
      log.config({2, {1}, {}});

      std::vector<int> exeSequence{{1, 2}};
      dss::SimBlock::setExeSequence(exeSequence);

      CHECK_CLOSE(0.0, puls.output(), EPS);
      log.exe();
      do {
         if (dss::SimTime::t < t_on) {
            CHECK_CLOSE(off, puls.output(), EPS);
         }
         if (dss::SimTime::t >= t_on and dss::SimTime::t < t_off) {
            CHECK_CLOSE(on, puls.output(), EPS);
         }
         if (dss::SimTime::t >= t_off + (5 * delta_t)) {
            CHECK_CLOSE(off, puls.output(), EPS);
         }
      } while (dss::SimTime::simulation_on());
   }

   TEST(Delay)
   {
      const double delta_t{0.1};
      const double delayTime{3 * delta_t};

      dss::SimTime::set(delta_t, 10 * delta_t);

      cout << "-- Delay" << endl;

      dss::Log log;
      log.config({1, {2, 3}, {}});

      double t_on = 2 * delta_t;
      dss::Step step;
      step.config({2, {}, {0.0, 1.0, t_on}});

      dss::Delay delay;
      delay.config({3, {}, {0.0, delayTime}});

      std::vector<int> exeSequence{{1, 2, 3}};
      dss::SimBlock::setExeSequence(exeSequence);

      CHECK_CLOSE(0.0, delay.output(), EPS);
      log.exe();
      do {
         auto input = step.output();
         delay.input(input);
         if (dss::SimTime::t < t_on + delayTime) {
            CHECK_CLOSE(0.0, delay.output(), EPS);
         } else {
            CHECK_CLOSE(1.0, delay.output(), EPS);
         }
      } while (dss::SimTime::simulation_on());
   }

   TEST(FirstOrder)
   {
      const double delta_t{0.01};
      const double tau{10 * delta_t};
      const double stp{1.0};
      const double stp_t{5 * delta_t};

      dss::SimTime::set(delta_t, 6 * tau);

      auto fio_response = [tau, stp_t, stp](double t) {
         return (t < stp_t) ? 0.0 : (stp * (1 - exp(-(t - stp_t) / tau)));
      };

      cout << "-- FirstOrder" << endl;

      dss::Step step;
      step.config({1, {}, {0.0, stp, stp_t}});

      dss::FirstOrder fio;
      fio.config({2, {1}, {tau, 0.0}});

      dss::Log log;
      log.config({3, {1, 2}, {}});

      std::vector<int> exeSequence{{1, 2, 3}};
      dss::SimBlock::setExeSequence(exeSequence);

      CHECK_CLOSE(0.0, fio.output(), EPS);
      log.exe();
      do {
         auto input = step.output();
         fio.input(input);
         auto out1 = fio.output();
         auto out2 = fio_response(dss::SimTime::t);
         CHECK_CLOSE(out1, out2, EPS*8);
      } while (dss::SimTime::simulation_on());
   }

   //    TEST(RC_FirstOrder)
   //    {
   //       const double delta_t{0.005};
   //       const double tau{100 * delta_t};
   //       const double stp{1.0};
   //       const double stp_t{5 * delta_t};

   //       cout << "-- compare RC filter and FirstOrder" << endl;

   //       dss::Time time;
   //       time.config({0, {}, {delta_t}});

   //       dss::Step step;
   //       step.config({1, {}, {0.0, stp, stp_t}});

   //       dss::Attenuator att;
   //       att.config({2, {}, {tau}});

   //       dss::Integrator intgt;
   //       intgt.config({3, {}, {0.0}});

   //       dss::FirstOrder fio;
   //       fio.config({4, {}, {tau, 0.0}});

   //       while (time.output() < stp_t + 10 * tau) {
   //          auto input = step.output();

   //          att.input(input);
   //          att.input(step.output() - intgt.output());
   //          intgt.input(att.output());

   //          fio.input(input);

   //          auto out1 = intgt.output();
   //          auto out2 = fio.output();
   //          // std::cout << out1 << " == " << out2 << std::endl;

   //          CHECK_CLOSE(out1, out2, EPS);

   //          time.exe();
   //          step.exe();
   //       }
   //    }
}

int main()
{
   cout << "\n== Tests DySySim lib: " << dss::libVersion << " "
        << string(50, '=') << "\n\n";

   auto result = UnitTest::RunAllTests();

   cout << "\n" << string(80, '=') << endl;

   return result;
}
<|endoftext|>