Program Listing for File testing_utils.hpp

Return to documentation for file (include/moveit_setup_framework/testing_utils.hpp)

/*********************************************************************
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2022, Metro Robots
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of Metro Robots nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************/

/* Author: David V. Lu!! */

#pragma once

#include <moveit_setup_framework/data_warehouse.hpp>
#include <moveit_setup_framework/setup_step.hpp>
#include <gtest/gtest.h>
#include <algorithm>
#include <filesystem>

namespace moveit_setup
{
class MoveItSetupTest : public ::testing::Test
{
protected:
  void SetUp() override
  {
    node_ = rclcpp::Node::make_shared("test_node");
    config_data_ = std::make_shared<moveit_setup::DataWarehouse>(node_);
    output_dir_ = std::filesystem::temp_directory_path() / "moveit_setup_test";
  }

  template <typename T>
  void generateFiles(const std::string& config_name)
  {
    std::vector<moveit_setup::GeneratedFilePtr> files;
    config_data_->get<T>(config_name)->collectFiles(output_dir_, placeholder_timestamp_, files);
    for (moveit_setup::GeneratedFilePtr& file : files)
    {
      file->write();
    }
  }

  void initializeStep(SetupStep& setup_step)
  {
    setup_step.initialize(node_, config_data_);
  }

  void TearDown() override
  {
    if (delete_when_finished_)
      std::filesystem::remove_all(output_dir_);
  }

  rclcpp::Node::SharedPtr node_;
  moveit_setup::DataWarehousePtr config_data_;
  std::filesystem::path output_dir_;
  moveit_setup::GeneratedTime placeholder_timestamp_;
  bool delete_when_finished_{ true };  // Set to false to keep the files around
};

std::set<std::string> getKeys(const YAML::Node& node)
{
  std::set<std::string> keys;
  for (const auto& kv : node)
  {
    keys.insert(kv.first.as<std::string>());
  }
  return keys;
}

void expectYamlEquivalence(const YAML::Node& generated, const YAML::Node& reference,
                           const std::filesystem::path& generated_path, const std::string& yaml_namespace = "")
{
  std::string msg_prefix =
      std::string("In ") + generated_path.c_str() + ", node with namespace '" + yaml_namespace + "' ";

  if (generated.Type() != reference.Type())
  {
    ADD_FAILURE() << msg_prefix + "does not have matching types!";
    return;
  }

  if (generated.IsSequence())
  {
    if (generated.size() != reference.size())
    {
      ADD_FAILURE() << msg_prefix + "does not have matching sizes!";
      return;
    }
    for (std::size_t i = 0; i < generated.size(); ++i)
    {
      std::string sub_namespace = yaml_namespace + "[" + std::to_string(i) + "]";
      expectYamlEquivalence(generated[i], reference[i], generated_path, sub_namespace);
    }
  }
  else if (generated.IsMap())
  {
    std::set<std::string> gkeys = getKeys(generated), rkeys = getKeys(reference), missing_keys, common_keys, extra_keys;

    std::set_difference(rkeys.begin(), rkeys.end(), gkeys.begin(), gkeys.end(),
                        std::inserter(missing_keys, missing_keys.end()));
    std::set_difference(gkeys.begin(), gkeys.end(), rkeys.begin(), rkeys.end(),
                        std::inserter(extra_keys, extra_keys.end()));
    std::set_intersection(gkeys.begin(), gkeys.end(), rkeys.begin(), rkeys.end(),
                          std::inserter(common_keys, common_keys.end()));

    for (const std::string& key : missing_keys)
    {
      ADD_FAILURE() << msg_prefix << "is missing the key '" << key << "'";
    }
    for (const std::string& key : extra_keys)
    {
      ADD_FAILURE() << msg_prefix << "has an extra key '" << key << "'";
    }
    for (const std::string& key : common_keys)
    {
      std::string sub_namespace = yaml_namespace + "/" + key;
      expectYamlEquivalence(generated[key], reference[key], generated_path, sub_namespace);
    }
  }
  else
  {
    EXPECT_EQ(generated.as<std::string>(), reference.as<std::string>())
        << msg_prefix << "does not match scalar values!";
  }
}

void expectYamlEquivalence(const std::filesystem::path& generated_path, const std::filesystem::path& reference_path)
{
  EXPECT_EQ(std::filesystem::is_regular_file(generated_path), std::filesystem::is_regular_file(reference_path));
  if (!std::filesystem::is_regular_file(reference_path))
  {
    return;
  }
  const YAML::Node& generated = YAML::LoadFile(generated_path);
  const YAML::Node& reference = YAML::LoadFile(reference_path);
  expectYamlEquivalence(generated, reference, generated_path);
}

}  // namespace moveit_setup