Skip to content
Snippets Groups Projects
testModules.cc 45.63 KiB
/*
 * testModules.cc - Test ApplicationModule, ModuleGroup and VariableGroup
 *
 *  Created on: Sep 27, 2017
 *      Author: Martin Hierholzer
 */

#include <chrono>
#include <future>

#define BOOST_TEST_MODULE testModules

#include <boost/test/included/unit_test.hpp>
#include <boost/test/test_case_template.hpp>
using namespace boost::unit_test_framework;

#include <boost/mpl/list.hpp>

#include "ApplicationCore.h"

namespace ctk = ChimeraTK;

/*********************************************************************************************************************/
/* Variable group used in the modules */

struct SomeGroup : ctk::VariableGroup {
  using ctk::VariableGroup::VariableGroup;
  ctk::ScalarPushInput<std::string> inGroup{this, "inGroup", "", "This is a string", {"C", "A"}};
  ctk::ArrayPushInput<int64_t> alsoInGroup{this, "alsoInGroup", "justANumber", 16, "A 64 bit number array", {"A", "D"}};
};

/*********************************************************************************************************************/
/* A plain application module for testing */

struct TestModule : public ctk::ApplicationModule {
  using ctk::ApplicationModule::ApplicationModule;

  ctk::ScalarPushInput<int> someInput{this, "nameOfSomeInput", "cm", "This is just some input for testing", {"A", "B"}};
  ctk::ScalarOutput<double> someOutput{this, "someOutput", "V", "Description", {"A", "C"}};

  SomeGroup someGroup{this, "someGroup", "Description of my test group"};

  struct AnotherGroup : ctk::VariableGroup {
    using ctk::VariableGroup::VariableGroup;
    ctk::ScalarPushInput<uint8_t> foo{this, "foo", "counts", "Some counter", {"D"}};
  } anotherGroup{this, "anotherName", "Description of my other group"};

  void mainLoop() {
    while(true) {
      someInput.read();
      int val = someInput;
      someOutput = val;
      someOutput.write();
    }
  }
};

/*********************************************************************************************************************/
/* Simple application with just one module */

struct OneModuleApp : public ctk::Application {
  OneModuleApp() : Application("myApp") {}
  ~OneModuleApp() { shutdown(); }

  using Application::makeConnections; // we call makeConnections() manually in
                                      // the tests to catch exceptions etc.
  void defineConnections() {}         // the setup is done in the tests

  TestModule testModule{this, "testModule", "Module to test"};
};

/*********************************************************************************************************************/
/* Application with a vector of modules */

struct VectorOfModulesApp : public ctk::Application {
  VectorOfModulesApp(size_t nInstances) : Application("myApp"), _nInstances(nInstances) {}
  ~VectorOfModulesApp() { shutdown(); }

  using Application::makeConnections; // we call makeConnections() manually in
                                      // the tests to catch exceptions etc.

  void defineConnections() {
    for(size_t i = 0; i < _nInstances; ++i) {
      std::string name = "testModule_" + std::to_string(i) + "_instance";
      vectorOfTestModule.emplace_back(this, name, "Description");
    }
  }

  size_t _nInstances;
  std::vector<TestModule> vectorOfTestModule;
};

/*********************************************************************************************************************/
/* An application module with a vector of a variable group*/

struct VectorModule : public ctk::ApplicationModule {
  VectorModule(ctk::EntityOwner* owner, const std::string& name, const std::string& description, size_t nInstances,
      bool eliminateHierarchy = false, const std::unordered_set<std::string>& tags = {})
  : ctk::ApplicationModule(owner, name, description, eliminateHierarchy, tags) {
    for(size_t i = 0; i < nInstances; ++i) {
      std::string name = "testGroup_" + std::to_string(i);
      vectorOfSomeGroup.emplace_back(this, name, "Description 2");
    }
  }
  VectorModule() {}

  ctk::ScalarPushInput<int> someInput{this, "nameOfSomeInput", "cm", "This is just some input for testing", {"A", "B"}};
  ctk::ArrayOutput<double> someOutput{this, "someOutput", "V", 1, "Description", {"A", "C"}};

  std::vector<SomeGroup> vectorOfSomeGroup;

  struct AnotherGroup : ctk::VariableGroup {
    using ctk::VariableGroup::VariableGroup;
    ctk::ScalarPushInput<uint8_t> foo{this, "foo", "counts", "Some counter", {"D"}};
  } anotherGroup{this, "anotherName", "Description of my other group"};

  void mainLoop() {
    while(true) {
      someInput.read();
      int val = someInput;
      someOutput[0] = val;
      someOutput.write();
    }
  }
};

/*********************************************************************************************************************/
/* An module group with a vector of a application moduoles */

struct VectorModuleGroup : public ctk::ModuleGroup {
  VectorModuleGroup(EntityOwner* owner, const std::string& name, const std::string& description, size_t nInstances,
      bool eliminateHierarchy = false, const std::unordered_set<std::string>& tags = {})
  : ctk::ModuleGroup(owner, name, description, eliminateHierarchy, tags) {
    for(size_t i = 0; i < nInstances; ++i) {
      std::string name = "test_" + std::to_string(i);
      vectorOfVectorModule.emplace_back(this, name, "Description 3", nInstances);
    }
  }

  VectorModuleGroup() {}

  std::vector<VectorModule> vectorOfVectorModule;
};

/*********************************************************************************************************************/
/* Application with a vector of module groups containing a vector of modules
 * containing a vector of variable groups */

struct VectorOfEverythingApp : public ctk::Application {
  VectorOfEverythingApp(size_t nInstances) : Application("myApp"), _nInstances(nInstances) {}
  ~VectorOfEverythingApp() { shutdown(); }

  using Application::makeConnections; // we call makeConnections() manually in
                                      // the tests to catch exceptions etc.

  void defineConnections() {
    for(size_t i = 0; i < _nInstances; ++i) {
      std::string name = "testModule_" + std::to_string(i) + "_instance";
      vectorOfVectorModuleGroup.emplace_back(this, name, "Description", _nInstances);
    }
  }

  size_t _nInstances;
  std::vector<VectorModuleGroup> vectorOfVectorModuleGroup;
};

/*********************************************************************************************************************/
/* Application with various modules that get initialised only during
 * defineConnections(). */

struct AssignModuleLaterApp : public ctk::Application {
  AssignModuleLaterApp() : Application("myApp") {}
  ~AssignModuleLaterApp() { shutdown(); }

  using Application::makeConnections; // we call makeConnections() manually in
                                      // the tests to catch exceptions etc.

  void defineConnections() {
    modGroupInstanceToAssignLater = VectorModuleGroup(this, "modGroupInstanceToAssignLater",
        "This instance of VectorModuleGroup was assigned using the operator=()", 42);
    modInstanceToAssignLater = VectorModule(
        this, "modInstanceToAssignLater", "This instance of VectorModule was assigned using the operator=()", 13);
  }

  VectorModuleGroup modGroupInstanceToAssignLater;
  VectorModule modInstanceToAssignLater;
};

/*********************************************************************************************************************/
/* test module and variable ownerships */

BOOST_AUTO_TEST_CASE(test_ownership) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> test_ownership" << std::endl;

  OneModuleApp app;

  BOOST_CHECK(app.testModule.getOwner() == &app);
  BOOST_CHECK(app.testModule.someGroup.getOwner() == &(app.testModule));
  BOOST_CHECK(app.testModule.anotherGroup.getOwner() == &(app.testModule));

  BOOST_CHECK(app.testModule.someInput.getOwner() == &(app.testModule));
  BOOST_CHECK(app.testModule.someOutput.getOwner() == &(app.testModule));

  BOOST_CHECK(app.testModule.someGroup.inGroup.getOwner() == &(app.testModule.someGroup));
  BOOST_CHECK(app.testModule.someGroup.alsoInGroup.getOwner() == &(app.testModule.someGroup));

  BOOST_CHECK(app.testModule.anotherGroup.foo.getOwner() == &(app.testModule.anotherGroup));
}

/*********************************************************************************************************************/
/* test that modules cannot be owned by the wrong types */

BOOST_AUTO_TEST_CASE(test_badHierarchies) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> test_badHierarchies" << std::endl;

  // ******************************************
  // *** Tests for ApplicationModule

  // check app ApplicationModules cannot be owned by other app modules
  {
    OneModuleApp app;
    try {
      TestModule willFail(&(app.testModule), "willFail", "");
      BOOST_FAIL("Exception expected");
    }
    catch(ChimeraTK::logic_error&) {
    }
  }

  // check app ApplicationModules cannot be owned by variable groups
  {
    OneModuleApp app;
    try {
      TestModule willFail(&(app.testModule.someGroup), "willFail", "");
      BOOST_FAIL("Exception expected");
    }
    catch(ChimeraTK::logic_error&) {
    }
  }

  // check app ApplicationModules cannot be owned by nothing
  {
    OneModuleApp app;
    try {
      TestModule willFail(nullptr, "willFail", "");
      BOOST_FAIL("Exception expected");
    }
    catch(ChimeraTK::logic_error&) {
    }
  }

  // ******************************************
  // *** Tests for VariableGroup

  // check app VariableGroup cannot be owned by Applications
  {
    OneModuleApp app;
    try {
      SomeGroup willFail(&(app), "willFail", "");
      BOOST_FAIL("Exception expected");
    }
    catch(ChimeraTK::logic_error&) {
    }
  }

  // check app VariableGroup cannot be owned by ModuleGroups
  {
    VectorOfEverythingApp app(1);
    app.defineConnections();
    try {
      SomeGroup willFail(&(app.vectorOfVectorModuleGroup[0]), "willFail", "");
      BOOST_FAIL("Exception expected");
    }
    catch(ChimeraTK::logic_error&) {
    }
  }

  // check app VariableGroup cannot be owned by nothing
  {
    OneModuleApp app;
    try {
      SomeGroup willFail(nullptr, "willFail", "");
      BOOST_FAIL("Exception expected");
    }
    catch(ChimeraTK::logic_error&) {
    }
  }

  // ******************************************
  // *** Tests for ModuleGroup

  // check app ModuleGroups cannot be owned by ApplicationModules
  {
    OneModuleApp app;
    try {
      VectorModuleGroup willFail(&(app.testModule), "willFail", "", 1);
      BOOST_FAIL("Exception expected");
    }
    catch(ChimeraTK::logic_error&) {
    }
  }

  // check app ModuleGroups cannot be owned by VariableGroups
  {
    OneModuleApp app;
    try {
      VectorModuleGroup willFail(&(app.testModule.someGroup), "willFail", "", 1);
      BOOST_FAIL("Exception expected");
    }
    catch(ChimeraTK::logic_error&) {
    }
  }

  // check app ModuleGroups cannot be owned by nothing
  {
    OneModuleApp app;
    try {
      VectorModuleGroup willFail(nullptr, "willFail", "", 1);
      BOOST_FAIL("Exception expected");
    }
    catch(ChimeraTK::logic_error&) {
    }
  }
}

/*********************************************************************************************************************/
/* test that modules can be owned by the right types */

BOOST_AUTO_TEST_CASE(test_allowedHierarchies) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> test_allowedHierarchies" << std::endl;

  // ******************************************
  // *** Tests for ApplicationModule
  // check ApplicationModules can be owned by Applications
  {
    OneModuleApp app;
    TestModule shouldNotFail(&(app), "shouldNotFail", "");
  }

  // check ApplicationModules can be owned by ModuleGroups
  {
    VectorOfEverythingApp app(1);
    app.defineConnections();
    TestModule shouldNotFail(&(app.vectorOfVectorModuleGroup[0]), "shouldNotFail", "");
  }

  // ******************************************
  // *** Tests for VariableGroup

  // check VariableGroup can be owned by ApplicationModules
  {
    OneModuleApp app;
    SomeGroup shouldNotFail(&(app.testModule), "shouldNotFail", "");
  }

  // check VariableGroup can be owned by VariableGroup
  {
    OneModuleApp app;
    SomeGroup shouldNotFail(&(app.testModule.someGroup), "shouldNotFail", "");
  }

  // ******************************************
  // *** Tests for ModuleGroup

  // check ModuleGroup can be owned by Applications
  {
    OneModuleApp app;
    VectorModuleGroup shouldNotFail(&(app), "shouldNotFail", "", 1);
  }

  // check ModuleGroup can be owned by ModuleGroups
  {
    VectorOfEverythingApp app(1);
    app.defineConnections();
    VectorModuleGroup shouldNotFail(&(app.vectorOfVectorModuleGroup[0]), "shouldNotFail", "", 1);
  }
}

/*********************************************************************************************************************/
/* test getSubmoduleList() and getSubmoduleListRecursive() */

BOOST_AUTO_TEST_CASE(test_getSubmoduleList) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> test_getSubmoduleList" << std::endl;

  OneModuleApp app;

  {
    std::list<ctk::Module*> list = app.getSubmoduleList();
    BOOST_CHECK(list.size() == 1);
    BOOST_CHECK(list.front() == &(app.testModule));
  }

  {
    std::list<ctk::Module*> list = app.testModule.getSubmoduleList();
    BOOST_CHECK(list.size() == 2);
    size_t foundSomeGroup = 0;
    size_t foundAnotherGroup = 0;
    for(auto mod : list) {
      if(mod == &(app.testModule.someGroup)) foundSomeGroup++;
      if(mod == &(app.testModule.anotherGroup)) foundAnotherGroup++;
    }
    BOOST_CHECK(foundSomeGroup == 1);
    BOOST_CHECK(foundAnotherGroup == 1);
  }

  {
    std::list<ctk::Module*> list = app.getSubmoduleListRecursive();
    BOOST_CHECK(list.size() == 3);
    size_t foundTestModule = 0;
    size_t foundSomeGroup = 0;
    size_t foundAnotherGroup = 0;
    for(auto mod : list) {
      if(mod == &(app.testModule)) foundTestModule++;
      if(mod == &(app.testModule.someGroup)) foundSomeGroup++;
      if(mod == &(app.testModule.anotherGroup)) foundAnotherGroup++;
    }
    BOOST_CHECK(foundTestModule == 1);
    BOOST_CHECK(foundSomeGroup == 1);
    BOOST_CHECK(foundAnotherGroup == 1);
  }

  {
    std::list<ctk::Module*> list = app.testModule.getSubmoduleListRecursive(); // identical to getSubmoduleList(),
                                                                               // since no deeper hierarchies
    BOOST_CHECK(list.size() == 2);
    size_t foundSomeGroup = 0;
    size_t foundAnotherGroup = 0;
    for(auto mod : list) {
      if(mod == &(app.testModule.someGroup)) foundSomeGroup++;
      if(mod == &(app.testModule.anotherGroup)) foundAnotherGroup++;
    }
    BOOST_CHECK(foundSomeGroup == 1);
    BOOST_CHECK(foundAnotherGroup == 1);
  }
}

/*********************************************************************************************************************/
/* test getAccessorList() and getAccessorListRecursive() */

BOOST_AUTO_TEST_CASE(test_getAccessorList) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> test_getAccessorList" << std::endl;

  OneModuleApp app;

  {
    std::list<ctk::VariableNetworkNode> list = app.testModule.getAccessorList();
    BOOST_CHECK(list.size() == 2);
    size_t foundSomeInput = 0;
    size_t foundSomeOutput = 0;
    for(auto var : list) {
      if(var == app.testModule.someInput) foundSomeInput++;
      if(var == app.testModule.someOutput) foundSomeOutput++;
    }
    BOOST_CHECK(foundSomeInput == 1);
    BOOST_CHECK(foundSomeOutput == 1);
  }

  {
    const SomeGroup& someGroup(app.testModule.someGroup);
    const std::list<ctk::VariableNetworkNode> list = someGroup.getAccessorList();
    BOOST_CHECK(list.size() == 2);
    size_t foundInGroup = 0;
    size_t foundAlsoInGroup = 0;
    for(auto var : list) {
      if(var == app.testModule.someGroup.inGroup) foundInGroup++;
      if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++;
    }
    BOOST_CHECK(foundInGroup == 1);
    BOOST_CHECK(foundAlsoInGroup == 1);
  }

  {
    std::list<ctk::VariableNetworkNode> list = app.getAccessorListRecursive();
    BOOST_CHECK(list.size() == 5);
    size_t foundSomeInput = 0;
    size_t foundSomeOutput = 0;
    size_t foundInGroup = 0;
    size_t foundAlsoInGroup = 0;
    size_t foundFoo = 0;
    for(auto var : list) {
      if(var == app.testModule.someInput) foundSomeInput++;
      if(var == app.testModule.someOutput) foundSomeOutput++;
      if(var == app.testModule.someGroup.inGroup) foundInGroup++;
      if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++;
      if(var == app.testModule.anotherGroup.foo) foundFoo++;
    }
    BOOST_CHECK(foundSomeInput == 1);
    BOOST_CHECK(foundSomeOutput == 1);
    BOOST_CHECK(foundInGroup == 1);
    BOOST_CHECK(foundAlsoInGroup == 1);
    BOOST_CHECK(foundFoo == 1);
  }

  {
    std::list<ctk::VariableNetworkNode> list = app.testModule.getAccessorListRecursive();
    BOOST_CHECK(list.size() == 5);
    size_t foundSomeInput = 0;
    size_t foundSomeOutput = 0;
    size_t foundInGroup = 0;
    size_t foundAlsoInGroup = 0;
    size_t foundFoo = 0;
    for(auto var : list) {
      if(var == app.testModule.someInput) foundSomeInput++;
      if(var == app.testModule.someOutput) foundSomeOutput++;
      if(var == app.testModule.someGroup.inGroup) foundInGroup++;
      if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++;
      if(var == app.testModule.anotherGroup.foo) foundFoo++;
    }
    BOOST_CHECK(foundSomeInput == 1);
    BOOST_CHECK(foundSomeOutput == 1);
    BOOST_CHECK(foundInGroup == 1);
    BOOST_CHECK(foundAlsoInGroup == 1);
    BOOST_CHECK(foundFoo == 1);
  }

  {
    std::list<ctk::VariableNetworkNode> list = app.testModule.anotherGroup.getAccessorListRecursive();
    BOOST_CHECK(list.size() == 1);
    size_t foundFoo = 0;
    for(auto var : list) {
      if(var == app.testModule.anotherGroup.foo) foundFoo++;
    }
    BOOST_CHECK(foundFoo == 1);
  }
}

/*********************************************************************************************************************/
/* test function call operator of the ApplicationModule */

BOOST_AUTO_TEST_CASE(testApplicationModuleFnCallOp) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> testApplicationModuleFnCallOp" << std::endl;

  OneModuleApp app;

  BOOST_CHECK(app.testModule("nameOfSomeInput") == static_cast<ctk::VariableNetworkNode>(app.testModule.someInput));
  BOOST_CHECK(app.testModule("nameOfSomeInput") != static_cast<ctk::VariableNetworkNode>(app.testModule.someOutput));
  BOOST_CHECK(app.testModule("someOutput") == static_cast<ctk::VariableNetworkNode>(app.testModule.someOutput));

  BOOST_CHECK(app.testModule("nameOfSomeInput").getType() == ctk::NodeType::Application);
  BOOST_CHECK(app.testModule("nameOfSomeInput").getMode() == ctk::UpdateMode::push);
  BOOST_CHECK(app.testModule("nameOfSomeInput").getDirection().dir == ctk::VariableDirection::consuming);
  BOOST_CHECK(app.testModule("nameOfSomeInput").getDirection().withReturn == false);
  BOOST_CHECK(app.testModule("nameOfSomeInput").getValueType() == typeid(int));
  BOOST_CHECK(app.testModule("nameOfSomeInput").getName() == "nameOfSomeInput");
  BOOST_CHECK(app.testModule("nameOfSomeInput").getQualifiedName() == "/myApp/testModule/nameOfSomeInput");
  BOOST_CHECK(app.testModule("nameOfSomeInput").getUnit() == "cm");
  BOOST_CHECK(
      app.testModule("nameOfSomeInput").getDescription() == "Module to test - This is just some input for testing");
  BOOST_CHECK(app.testModule("nameOfSomeInput").getTags() == std::unordered_set<std::string>({"A", "B"}));

  // check exception if variable not found
  try {
    app.testModule("notExisting");
    BOOST_FAIL("Exception expected");
  }
  catch(ChimeraTK::logic_error&) {
  }
}

/*********************************************************************************************************************/
/* test function call operator of the ApplicationModule */

BOOST_AUTO_TEST_CASE(testApplicationModuleSubscriptOp) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> testApplicationModuleSubscriptOp" << std::endl;

  OneModuleApp app;

  BOOST_CHECK(app.testModule["someGroup"].getName() == "someGroup");
  BOOST_CHECK(app.testModule["anotherName"].getName() == "anotherName");

  BOOST_CHECK(app.testModule["someGroup"]("inGroup") == app.testModule.someGroup.inGroup);
  BOOST_CHECK(app.testModule["someGroup"]("alsoInGroup") == app.testModule.someGroup.alsoInGroup);

  BOOST_CHECK(app.testModule["anotherName"]("foo") == app.testModule.anotherGroup.foo);

  // check exception if group not found
  try {
    app.testModule["notExisting"];
    BOOST_FAIL("Exception expected");
  }
  catch(ChimeraTK::logic_error&) {
  }
}

/*********************************************************************************************************************/
/* test finding variables by tag */

BOOST_AUTO_TEST_CASE(testFindTags) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> testFindTags" << std::endl;

  OneModuleApp app;

  // search for tag "A"
  {
    ctk::VirtualModule tagA = app.testModule.findTag("A");

    // check direct variables
    {
      std::list<ctk::VariableNetworkNode> list = tagA.getAccessorList();
      BOOST_CHECK(list.size() == 2);
      size_t foundSomeInput = 0;
      size_t foundSomeOutput = 0;
      for(auto var : list) {
        if(var == app.testModule.someInput) foundSomeInput++;
        if(var == app.testModule.someOutput) foundSomeOutput++;
      }
      BOOST_CHECK(foundSomeInput == 1);
      BOOST_CHECK(foundSomeOutput == 1);
    }

    // check number of submodules
    {
      std::list<ctk::Module*> list = tagA.getSubmoduleList();
      BOOST_CHECK(list.size() == 1);
    }

    // check content of submodule
    {
      std::list<ctk::VariableNetworkNode> list = tagA["someGroup"].getAccessorList();
      BOOST_CHECK(list.size() == 2);
      size_t foundInGroup = 0;
      size_t foundAlsoInGroup = 0;
      for(auto var : list) {
        if(var == app.testModule.someGroup.inGroup) foundInGroup++;
        if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++;
      }
      BOOST_CHECK(foundInGroup == 1);
      BOOST_CHECK(foundAlsoInGroup == 1);
    }
  }

  // search for tag "D"
  {
    ctk::VirtualModule tagD = app.testModule.findTag("D");

    // check direct variables
    {
      std::list<ctk::VariableNetworkNode> list = tagD.getAccessorList();
      BOOST_CHECK(list.size() == 0);
    }

    // check number of submodules
    {
      std::list<ctk::Module*> list = tagD.getSubmoduleList();
      BOOST_CHECK(list.size() == 2);
    }

    // check content of submodule "someGroup"
    {
      std::list<ctk::VariableNetworkNode> list = tagD["someGroup"].getAccessorList();
      BOOST_CHECK(list.size() == 1);
      size_t foundAlsoInGroup = 0;
      for(auto var : list) {
        if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++;
      }
      BOOST_CHECK(foundAlsoInGroup == 1);
    }

    // check content of submodule "anotherName"
    {
      std::list<ctk::VariableNetworkNode> list = tagD["anotherName"].getAccessorList();
      BOOST_CHECK(list.size() == 1);
      size_t foundFoo = 0;
      for(auto var : list) {
        if(var == app.testModule.anotherGroup.foo) foundFoo++;
      }
      BOOST_CHECK(foundFoo == 1);
    }
  }

  // search for tag "D", exclude tag "A"
  {
    ctk::VirtualModule tagDnotA = app.testModule.findTag("D").excludeTag("A");

    // check direct variables
    {
      std::list<ctk::VariableNetworkNode> list = tagDnotA.getAccessorList();
      BOOST_CHECK(list.size() == 0);
    }

    // check number of submodules
    {
      std::list<ctk::Module*> list = tagDnotA.getSubmoduleList();
      BOOST_CHECK(list.size() == 1);
    }

    // check content of submodule "anotherName"
    {
      std::list<ctk::VariableNetworkNode> list = tagDnotA["anotherName"].getAccessorList();
      BOOST_CHECK(list.size() == 1);
      size_t foundFoo = 0;
      for(auto var : list) {
        if(var == app.testModule.anotherGroup.foo) foundFoo++;
      }
      BOOST_CHECK(foundFoo == 1);
    }
  }
}

/*********************************************************************************************************************/
/* test flatten() */

BOOST_AUTO_TEST_CASE(testFlatten) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> testFlatten" << std::endl;

  OneModuleApp app;

  ctk::VirtualModule flattened = app.testModule.flatten();

  // check number of submodules
  {
    std::list<ctk::Module*> list = flattened.getSubmoduleList();
    BOOST_CHECK(list.size() == 0);
  }

  // check direct variables
  {
    std::list<ctk::VariableNetworkNode> list = flattened.getAccessorList();
    BOOST_CHECK(list.size() == 5);
    size_t foundSomeInput = 0;
    size_t foundSomeOutput = 0;
    size_t foundInGroup = 0;
    size_t foundAlsoInGroup = 0;
    size_t foundFoo = 0;
    for(auto var : list) {
      if(var == app.testModule.someInput) foundSomeInput++;
      if(var == app.testModule.someOutput) foundSomeOutput++;
      if(var == app.testModule.someGroup.inGroup) foundInGroup++;
      if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++;
      if(var == app.testModule.anotherGroup.foo) foundFoo++;
    }
    BOOST_CHECK(foundSomeInput == 1);
    BOOST_CHECK(foundSomeOutput == 1);
    BOOST_CHECK(foundInGroup == 1);
    BOOST_CHECK(foundAlsoInGroup == 1);
    BOOST_CHECK(foundFoo == 1);
  }
}

/*********************************************************************************************************************/
/* test addTag() */

BOOST_AUTO_TEST_CASE(testAddTag) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> testAddTag" << std::endl;

  OneModuleApp app;
  app.testModule.addTag("newTag");

  ctk::VirtualModule withNewTag = app.findTag("newTag");

  // check submodule hierarchy
  {
    BOOST_CHECK(withNewTag.getSubmoduleList().size() == 1);
    BOOST_CHECK(withNewTag.getAccessorList().size() == 0);
    BOOST_CHECK(withNewTag["testModule"].getSubmoduleList().size() == 2);
    BOOST_CHECK(withNewTag["testModule"].getAccessorList().size() == 2);
    BOOST_CHECK(withNewTag["testModule"]["someGroup"].getSubmoduleList().size() == 0);
    BOOST_CHECK(withNewTag["testModule"]["someGroup"].getAccessorList().size() == 2);
    BOOST_CHECK(withNewTag["testModule"]["anotherName"].getSubmoduleList().size() == 0);
    BOOST_CHECK(withNewTag["testModule"]["anotherName"].getAccessorList().size() == 1);
  }

  // check all variables
  {
    std::list<ctk::VariableNetworkNode> list = withNewTag.getAccessorListRecursive();
    BOOST_CHECK(list.size() == 5);
    size_t foundSomeInput = 0;
    size_t foundSomeOutput = 0;
    size_t foundInGroup = 0;
    size_t foundAlsoInGroup = 0;
    size_t foundFoo = 0;
    for(auto var : list) {
      if(var == app.testModule.someInput) foundSomeInput++;
      if(var == app.testModule.someOutput) foundSomeOutput++;
      if(var == app.testModule.someGroup.inGroup) foundInGroup++;
      if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++;
      if(var == app.testModule.anotherGroup.foo) foundFoo++;
    }
    BOOST_CHECK(foundSomeInput == 1);
    BOOST_CHECK(foundSomeOutput == 1);
    BOOST_CHECK(foundInGroup == 1);
    BOOST_CHECK(foundAlsoInGroup == 1);
    BOOST_CHECK(foundFoo == 1);
  }
}

/*********************************************************************************************************************/
/* test correct behaviour when using a std::vector of ApplicationModules */

BOOST_AUTO_TEST_CASE(testVectorOfApplicationModule) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> testVectorOfApplicationModule" << std::endl;

  // create app with a vector containing 10 modules
  size_t nInstances = 10;
  VectorOfModulesApp app(nInstances);

  // the app creates the 10 module instances in defineConnections, check if this
  // is done proplery (a quite redundant test...)
  BOOST_CHECK(app.vectorOfTestModule.size() == 0);
  app.defineConnections();
  BOOST_CHECK(app.vectorOfTestModule.size() == nInstances);

  // some direct checks on the created instances
  for(size_t i = 0; i < nInstances; ++i) {
    std::string name = "testModule_" + std::to_string(i) + "_instance";
    BOOST_CHECK(app.vectorOfTestModule[i].getName() == name);
    auto node = static_cast<ctk::VariableNetworkNode>(app.vectorOfTestModule[i].someInput);
    BOOST_CHECK(node.getQualifiedName() == "/myApp/" + name + "/nameOfSomeInput");

    // check accessor list
    std::list<ctk::VariableNetworkNode> accList = app.vectorOfTestModule[i].getAccessorList();
    BOOST_CHECK(accList.size() == 2);
    size_t foundSomeInput = 0;
    size_t foundSomeOutput = 0;
    for(auto& acc : accList) {
      if(acc == app.vectorOfTestModule[i].someInput) foundSomeInput++;
      if(acc == app.vectorOfTestModule[i].someOutput) foundSomeOutput++;
    }
    BOOST_CHECK(foundSomeInput == 1);
    BOOST_CHECK(foundSomeOutput == 1);

    // check submodule list
    std::list<ctk::Module*> modList = app.vectorOfTestModule[i].getSubmoduleList();
    BOOST_CHECK(modList.size() == 2);
    size_t foundSomeGroup = 0;
    size_t foundAnotherGroup = 0;
    for(auto mod : modList) {
      if(mod == &(app.vectorOfTestModule[i].someGroup)) foundSomeGroup++;
      if(mod == &(app.vectorOfTestModule[i].anotherGroup)) foundAnotherGroup++;
    }
    BOOST_CHECK(foundSomeGroup == 1);
    BOOST_CHECK(foundAnotherGroup == 1);
  }

  // check if instances appear properly in getSubmoduleList()
  {
    std::list<ctk::Module*> list = app.getSubmoduleList();
    BOOST_CHECK(list.size() == nInstances);
    std::map<size_t, size_t> instancesFound;
    for(size_t i = 0; i < nInstances; ++i) instancesFound[i] = 0;
    for(auto mod : list) {
      for(size_t i = 0; i < nInstances; ++i) {
        if(mod == &(app.vectorOfTestModule[i])) instancesFound[i]++;
      }
    }
    for(size_t i = 0; i < nInstances; ++i) {
      BOOST_CHECK(instancesFound[i] == 1);
    }
  }

  // check if instances appear properly in getSubmoduleListRecursive() as well
  {
    std::list<ctk::Module*> list = app.getSubmoduleListRecursive();
    BOOST_CHECK(list.size() == 3 * nInstances);
    std::map<size_t, size_t> instancesFound, instancesSomeGroupFound, instancesAnotherGroupFound;
    for(size_t i = 0; i < nInstances; ++i) {
      instancesFound[i] = 0;
      instancesSomeGroupFound[i] = 0;
      instancesAnotherGroupFound[i] = 0;
    }
    for(auto mod : list) {
      for(size_t i = 0; i < nInstances; ++i) {
        if(mod == &(app.vectorOfTestModule[i])) instancesFound[i]++;
        if(mod == &(app.vectorOfTestModule[i].someGroup)) instancesSomeGroupFound[i]++;
        if(mod == &(app.vectorOfTestModule[i].anotherGroup)) instancesAnotherGroupFound[i]++;
      }
    }
    for(size_t i = 0; i < nInstances; ++i) {
      BOOST_CHECK(instancesFound[i] == 1);
      BOOST_CHECK(instancesSomeGroupFound[i] == 1);
      BOOST_CHECK(instancesAnotherGroupFound[i] == 1);
    }
  }

  // check ownerships
  for(size_t i = 0; i < nInstances; ++i) {
    BOOST_CHECK(app.vectorOfTestModule[i].getOwner() == &app);
    BOOST_CHECK(app.vectorOfTestModule[i].someInput.getOwner() == &(app.vectorOfTestModule[i]));
    BOOST_CHECK(app.vectorOfTestModule[i].someOutput.getOwner() == &(app.vectorOfTestModule[i]));
    BOOST_CHECK(app.vectorOfTestModule[i].someGroup.getOwner() == &(app.vectorOfTestModule[i]));
    BOOST_CHECK(app.vectorOfTestModule[i].someGroup.inGroup.getOwner() == &(app.vectorOfTestModule[i].someGroup));
    BOOST_CHECK(app.vectorOfTestModule[i].someGroup.alsoInGroup.getOwner() == &(app.vectorOfTestModule[i].someGroup));
    BOOST_CHECK(app.vectorOfTestModule[i].anotherGroup.getOwner() == &(app.vectorOfTestModule[i]));
    BOOST_CHECK(app.vectorOfTestModule[i].anotherGroup.foo.getOwner() == &(app.vectorOfTestModule[i].anotherGroup));
  }
}

/*********************************************************************************************************************/
/* test correct behaviour when using a std::vector of ModuleGroup,
 * ApplicationModule and VariableGroup at the same time */

BOOST_AUTO_TEST_CASE(testVectorsOfAllModules) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> testVectorsOfAllModules" << std::endl;

  // create app with a vector containing 10 modules
  size_t nInstances = 10;
  VectorOfEverythingApp app(nInstances);

  //-------------------------------------------------------------------------------------------------------------------
  // the app creates the 10 module instances in defineConnections, check if this
  // is done proplery (a quite redundant test...)
  BOOST_CHECK(app.vectorOfVectorModuleGroup.size() == 0);
  app.defineConnections();
  BOOST_CHECK(app.vectorOfVectorModuleGroup.size() == nInstances);
  for(size_t i = 0; i < nInstances; ++i) {
    BOOST_CHECK(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule.size() == nInstances);
    for(size_t k = 0; k < nInstances; ++k) {
      BOOST_CHECK(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup.size() == nInstances);
    }
  }

  //-------------------------------------------------------------------------------------------------------------------
  // check presence in lists (getSubmoduleList() and getAccessorList())

  { // checks on first hierarchy level (application has the list of module
    // groups)
    std::list<ctk::Module*> list = app.getSubmoduleList();
    BOOST_CHECK(list.size() == nInstances);
    std::map<size_t, size_t> found;
    for(size_t i = 0; i < nInstances; ++i) found[i] = 0;
    for(auto mod : list) {
      for(size_t i = 0; i < nInstances; ++i) {
        if(mod == &(app.vectorOfVectorModuleGroup[i])) found[i]++;
      }
    }
    for(size_t i = 0; i < nInstances; ++i) BOOST_CHECK(found[i] == 1);
  }

  { // checks on second hierarchy level (each module group has the list of
    // modules)
    for(size_t i = 0; i < nInstances; ++i) {
      std::list<ctk::Module*> list = app.vectorOfVectorModuleGroup[i].getSubmoduleList();
      BOOST_CHECK(list.size() == nInstances);

      std::map<size_t, size_t> found;
      for(size_t k = 0; k < nInstances; ++k) found[k] = 0;
      for(auto mod : list) {
        for(size_t k = 0; k < nInstances; ++k) {
          if(mod == &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k])) found[k]++;
        }
      }
      for(size_t k = 0; k < nInstances; ++k) BOOST_CHECK(found[k] == 1);
    }
  }

  { // checks on third hierarchy level (each module has accessors and variable
    // groups)
    for(size_t i = 0; i < nInstances; ++i) {
      for(size_t k = 0; k < nInstances; ++k) {
        // search for accessors
        std::list<ctk::VariableNetworkNode> accList =
            app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].getAccessorList();
        BOOST_CHECK_EQUAL(accList.size(), 2);
        size_t someInputFound = 0;
        size_t someOutputFound = 0;
        for(auto acc : accList) {
          if(acc == app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someInput) someInputFound++;
          if(acc == app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someOutput) someOutputFound++;
        }
        BOOST_CHECK_EQUAL(someInputFound, 1);
        BOOST_CHECK_EQUAL(someOutputFound, 1);

        // search for variable groups
        std::list<ctk::Module*> modList = app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].getSubmoduleList();
        BOOST_CHECK_EQUAL(modList.size(), nInstances + 1);

        std::map<size_t, size_t> someGroupFound;
        for(size_t m = 0; m < nInstances; ++m) someGroupFound[m] = 0;
        size_t anotherGroupFound = 0;
        for(auto mod : modList) {
          for(size_t m = 0; m < nInstances; ++m) {
            if(mod == &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m]))
              someGroupFound[m]++;
          }
          if(mod == &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].anotherGroup)) anotherGroupFound++;
        }
        for(size_t m = 0; m < nInstances; ++m) {
          BOOST_CHECK_EQUAL(someGroupFound[m], 1);
        }
        BOOST_CHECK_EQUAL(anotherGroupFound, 1);
      }
    }
  }

  { // checks on fourth hierarchy level (each variable group has accessors)
    for(size_t i = 0; i < nInstances; ++i) {
      for(size_t k = 0; k < nInstances; ++k) {
        for(size_t m = 0; m < nInstances; ++m) {
          // search for accessors
          std::list<ctk::VariableNetworkNode> accList =
              app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].getAccessorList();
          BOOST_CHECK_EQUAL(accList.size(), 2);
          size_t inGroupFound = 0;
          size_t alsoInGroupFound = 0;
          for(auto acc : accList) {
            if(acc == app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].inGroup) {
              inGroupFound++;
            }
            if(acc == app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].alsoInGroup) {
              alsoInGroupFound++;
            }
          }
          BOOST_CHECK_EQUAL(inGroupFound, 1);
          BOOST_CHECK_EQUAL(alsoInGroupFound, 1);

          // make sure no further subgroups exist
          BOOST_CHECK_EQUAL(
              app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].getSubmoduleList().size(),
              0);
        }
      }
    }
  }
  //-------------------------------------------------------------------------------------------------------------------
  // check ownerships
  for(size_t i = 0; i < nInstances; ++i) {
    BOOST_CHECK(app.vectorOfVectorModuleGroup[i].getOwner() == &app);
    for(size_t k = 0; k < nInstances; ++k) {
      BOOST_CHECK(
          app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].getOwner() == &(app.vectorOfVectorModuleGroup[i]));
      BOOST_CHECK(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someInput.getOwner() ==
          &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k]));
      BOOST_CHECK(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someOutput.getOwner() ==
          &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k]));
      for(size_t m = 0; m < nInstances; ++m) {
        BOOST_CHECK(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].getOwner() ==
            &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k]));
        BOOST_CHECK(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].inGroup.getOwner() ==
            &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m]));
        BOOST_CHECK(
            app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].alsoInGroup.getOwner() ==
            &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m]));
      }
    }
  }

  //-------------------------------------------------------------------------------------------------------------------
  // check pointers to accessors in VariableNetworkNode
  for(size_t i = 0; i < nInstances; ++i) {
    for(size_t k = 0; k < nInstances; ++k) {
      {
        auto a =
            &(static_cast<ctk::VariableNetworkNode>(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someInput)
                    .getAppAccessorNoType());
        auto b = &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someInput);
        BOOST_CHECK(a == b);
      }
      {
        auto a =
            &(static_cast<ctk::VariableNetworkNode>(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someOutput)
                    .getAppAccessorNoType());
        auto b = &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someOutput);
        BOOST_CHECK(a == b);
      }
      for(size_t m = 0; m < nInstances; ++m) {
        {
          auto a = &(static_cast<ctk::VariableNetworkNode>(
              app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].inGroup)
                         .getAppAccessorNoType());
          auto b = &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].inGroup);
          BOOST_CHECK(a == b);
        }
        {
          auto a = &(static_cast<ctk::VariableNetworkNode>(
              app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].alsoInGroup)
                         .getAppAccessorNoType());
          auto b = &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].alsoInGroup);
          BOOST_CHECK(a == b);
        }
      }
    }
  }

  //-------------------------------------------------------------------------------------------------------------------
  // search for tags and check result
  auto searchResult = app.findTag("A");
  std::list<ctk::Module*> list = searchResult.getSubmoduleList();

  { // checks on first hierarchy level
    BOOST_CHECK(list.size() == nInstances);
    std::map<std::string, int> nameMap;
    for(auto mod : list) {
      nameMap[mod->getName()]++;
    }
    for(size_t i = 0; i < nInstances; ++i) {
      std::string name = "testModule_" + std::to_string(i) + "_instance";
      BOOST_CHECK(nameMap[name] == 1);
    }
  }

  { // checks on second hierarchy level
    for(auto mod : list) {
      std::list<ctk::Module*> list2 = mod->getSubmoduleList();

      BOOST_CHECK(list2.size() == nInstances);
      std::map<std::string, int> nameMap;
      for(auto mod2 : list2) {
        nameMap[mod2->getName()]++;
      }
      for(size_t i = 0; i < nInstances; ++i) {
        std::string name = "test_" + std::to_string(i);
        BOOST_CHECK(nameMap[name] == 1);
      }
    }
  }

  { // checks on third hierarchy level
    for(auto mod : list) {
      std::list<ctk::Module*> list2 = mod->getSubmoduleList();
      for(auto mod2 : list2) {
        std::list<ctk::Module*> list3 = mod2->getSubmoduleList();

        BOOST_CHECK(list3.size() == nInstances);
        std::map<std::string, int> nameMap;
        for(auto mod3 : list3) {
          nameMap[mod3->getName()]++;
        }
        for(size_t i = 0; i < nInstances; ++i) {
          std::string name = "testGroup_" + std::to_string(i);
          BOOST_CHECK(nameMap[name] == 1);
        }
      }
    }
  }

  { // checks on fourth hierarchy level (actual variables)
    for(auto mod : list) {
      std::list<ctk::Module*> list2 = mod->getSubmoduleList();
      for(auto mod2 : list2) {
        std::list<ctk::Module*> list3 = mod2->getSubmoduleList();
        for(auto mod3 : list3) {
          std::list<ctk::VariableNetworkNode> vars = mod3->getAccessorList();
          BOOST_CHECK(vars.size() == 2);

          size_t foundInGroup = 0;
          size_t foundAlsoInGroup = 0;
          for(auto var : vars) {
            if(var.getName() == "inGroup") ++foundInGroup;
            if(var.getName() == "alsoInGroup") ++foundAlsoInGroup;
          }
          BOOST_CHECK(foundInGroup == 1);
          BOOST_CHECK(foundAlsoInGroup == 1);
        }
      }
    }
  }
}

/*********************************************************************************************************************/
/* test late initialisation of modules using the assignment operator */

BOOST_AUTO_TEST_CASE(test_assignmentOperator) {
  std::cout << "***************************************************************"
               "******************************************************"
            << std::endl;
  std::cout << "==> test_assignmentOperator" << std::endl;
  std::cout << std::endl;

  AssignModuleLaterApp app;

  BOOST_CHECK(app.getSubmoduleList().size() == 0);
  BOOST_CHECK_EQUAL(app.modGroupInstanceToAssignLater.getSubmoduleList().size(), 0);

  app.defineConnections();

  BOOST_CHECK(app.modGroupInstanceToAssignLater.getName() == "modGroupInstanceToAssignLater");
  BOOST_CHECK(app.modGroupInstanceToAssignLater.getDescription() ==
      "This instance of VectorModuleGroup was assigned using the operator=()");

  BOOST_CHECK(app.modInstanceToAssignLater.getName() == "modInstanceToAssignLater");
  BOOST_CHECK(app.modInstanceToAssignLater.getDescription() ==
      "This instance of VectorModule was assigned using the operator=()");

  auto list = app.getSubmoduleList();
  BOOST_CHECK(list.size() == 2);

  bool modGroupInstanceToAssignLater_found = false;
  bool modInstanceToAssignLater_found = false;
  for(auto mod : list) {
    if(mod == &(app.modGroupInstanceToAssignLater)) modGroupInstanceToAssignLater_found = true;
    if(mod == &(app.modInstanceToAssignLater)) modInstanceToAssignLater_found = true;
  }

  BOOST_CHECK(modGroupInstanceToAssignLater_found);
  BOOST_CHECK(modInstanceToAssignLater_found);
  BOOST_CHECK_EQUAL(app.modGroupInstanceToAssignLater.getSubmoduleList().size(), 42);
  BOOST_CHECK_EQUAL(app.modInstanceToAssignLater.getSubmoduleList().size(), 14);
}