Skip to content

Commit

Permalink
[7256] Tweaked unit tests for new API endpoint.
Browse files Browse the repository at this point in the history
  • Loading branch information
korydraughn committed Sep 29, 2023
1 parent 89164a5 commit d78404a
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 110 deletions.
287 changes: 177 additions & 110 deletions unit_tests/src/test_get_resource_info_for_operation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,23 @@
#include "irods/replica.hpp"
#include "irods/resource_administration.hpp"
#include "irods/rodsClient.h"
#include "irods/rodsErrorTable.h"
#include "irods/transport/default_transport.hpp"

#include <boost/filesystem.hpp>
#include <fmt/format.h>
#include <nlohmann/json.hpp>

#include <csignal>
#include <chrono>
#include <iostream>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>

#include <unistd.h>
#include <signal.h>

#include "unit_test_utils.hpp"

Expand All @@ -36,15 +39,15 @@ namespace io = irods::experimental::io;

namespace
{
auto get_hostname() noexcept -> std::string;
auto create_resource_vault(const std::string& _vault_name) -> boost::filesystem::path;
auto create_resource_vault_path(const std::string& _vault_name) -> std::string;
} //namespace

TEST_CASE("test api resource_info_for_operation")
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
TEST_CASE("get_resource_info_for_operation")
{
load_client_api_plugins();

irods::experimental::client_connection conn;
RcComm& comm = static_cast<RcComm&>(conn);

rodsEnv env;
_getRodsEnv(env);
Expand All @@ -57,165 +60,229 @@ TEST_CASE("test api resource_info_for_operation")
irods::at_scope_exit remove_sandbox{
[&conn, &sandbox] { REQUIRE(fs::client::remove_all(conn, sandbox, fs::remove_options::no_trash)); }};

std::string default_resource{"demoResc"};
const std::string_view default_resource{"demoResc"};

// get host name for default_resource
// Get host name for default_resource.
auto p = ra::client::resource_info(conn, default_resource);
REQUIRE(p.has_value());
auto hostname = p->host_name();

io::client::native_transport tp{conn};

const std::string path{sandbox / "data_object.txt"};
const auto path{sandbox / "data_object.txt"};

#define BUFFER_CLEARING_AND_NULLING_LAMBDA \
[&char_buffer] { \
std::free(char_buffer); \
char_buffer = nullptr; \
}

SECTION("For CREATE")
SECTION("operation is CREATE")
{
DataObjInp inp{};
ix::key_value_proxy kvp{inp.condInput};
std::strncpy(inp.objPath, path.c_str(), MAX_NAME_LEN);
std::strncpy(static_cast<char*>(inp.objPath), path.c_str(), MAX_NAME_LEN);

irods::at_scope_exit free_memory{[&kvp] { kvp.clear(); }};
irods::at_scope_exit clear_kvp{[&kvp] { kvp.clear(); }};
kvp[GET_RESOURCE_INFO_OP_TYPE_KW] = "CREATE";

char* char_buffer{};
//irods::at_scope_exit free_json_buffer{free_and_null_buffer}; // doesn't work => needs deduction guide (DWM -
//why?)

std::function<void()> free_and_null_buffer{BUFFER_CLEARING_AND_NULLING_LAMBDA};
irods::at_scope_exit free_json_buffer{BUFFER_CLEARING_AND_NULLING_LAMBDA};
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory, cppcoreguidelines-no-malloc)
irods::at_scope_exit free_char_buffer{[&char_buffer] { std::free(char_buffer); }};

rc_get_resource_info_for_operation(&comm, &inp, &char_buffer);
REQUIRE(char_buffer != nullptr);
REQUIRE(rc_get_resource_info_for_operation(static_cast<RcComm*>(conn), &inp, &char_buffer) == 0);
REQUIRE(nullptr != char_buffer);

auto results = nlohmann::json::parse(char_buffer);
REQUIRE(results["host"].get<std::string>() == hostname);
REQUIRE(results["resource_hierarchy"].get<std::string>() == default_resource);
CHECK(results.at("host").get_ref<const std::string&>() == hostname);
CHECK(results.at("resource_hierarchy").get_ref<const std::string&>() == default_resource);
}

SECTION("For OPEN")
SECTION("operation is OPEN or UNLINK")
{
// create data object

io::odstream data_object_out{tp, path, io::leaf_resource_name{default_resource}};
data_object_out.close();
// Create a data object.
{
io::client::native_transport tp{conn};
io::odstream{tp, path, io::leaf_resource_name{std::string{default_resource}}};
}

DataObjInp inp{};
ix::key_value_proxy kvp{inp.condInput};
std::strncpy(inp.objPath, path.c_str(), MAX_NAME_LEN);

irods::at_scope_exit free_memory{[&kvp] { kvp.clear(); }};
kvp[GET_RESOURCE_INFO_OP_TYPE_KW] = "OPEN";

char* char_buffer{};
rc_get_resource_info_for_operation(&comm, &inp, &char_buffer);
REQUIRE(char_buffer != nullptr);

auto results = nlohmann::json::parse(char_buffer);
REQUIRE(results["host"].get<std::string>() == hostname);
REQUIRE(results["resource_hierarchy"].get<std::string>() == default_resource);
std::strncpy(static_cast<char*>(inp.objPath), path.c_str(), MAX_NAME_LEN);

// clang-format off
const auto test_cases = std::to_array<std::pair<const char*, const char*>>({
{"operation is OPEN", "OPEN"},
{"operation is UNLINK", "UNLINK"}
});
// clang-format on

for (auto&& [title, keyword_value] : test_cases) {
DYNAMIC_SECTION(title)
{
irods::at_scope_exit clear_kvp{[&kvp] { kvp.clear(); }};
kvp[GET_RESOURCE_INFO_OP_TYPE_KW] = keyword_value;

char* char_buffer{};
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory, cppcoreguidelines-no-malloc)
irods::at_scope_exit free_char_buffer{[&char_buffer] { std::free(char_buffer); }};

REQUIRE(rc_get_resource_info_for_operation(static_cast<RcComm*>(conn), &inp, &char_buffer) == 0);
REQUIRE(nullptr != char_buffer);

const auto results = nlohmann::json::parse(char_buffer);
CHECK(results.at("host").get_ref<const std::string&>() == hostname);
CHECK(results.at("resource_hierarchy").get_ref<const std::string&>() == default_resource);
}
}
}

SECTION("For WRITE")
SECTION("operation is WRITE")
{
const std::string path_for_write{sandbox / "data_object_for_WRITE.txt"};
const auto path_for_write{sandbox / "data_object_for_WRITE.txt"};

const auto vault = create_resource_vault("irods_get_resc_info_for_op_vault_1");
const std::string hostname = get_hostname(); //"localhost";//DWM
const auto vault = create_resource_vault_path("irods_get_resc_info_for_op_vault_1");
const std::string hostname = unit_test_utils::get_hostname();

// Clean up resource configuration.
const auto cmd = fmt::format("iadmin mkresc ut_ufs_resc_1 unixfilesystem {}:{}", hostname, vault.c_str());
REQUIRE(std::system(cmd.c_str()) == 0);
REQUIRE(std::system("iadmin mkresc ut_pt_resc passthru") == 0);
REQUIRE(std::system("iadmin addchildtoresc ut_pt_resc ut_ufs_resc_1") == 0);
REQUIRE(std::system(cmd.c_str()) == 0); // NOLINT(cert-env33-c)
REQUIRE(std::system("iadmin mkresc ut_pt_resc passthru") == 0); // NOLINT(cert-env33-c)
REQUIRE(std::system("iadmin addchildtoresc ut_pt_resc ut_ufs_resc_1") == 0); // NOLINT(cert-env33-c)

// A new connection is required to pick up the test resource configuration.
irods::experimental::client_connection conn;
RcComm& comm = static_cast<RcComm&>(conn);

// RAII clean-up path for the test resource configuration.
irods::at_scope_exit remove_resources{[&conn, &path_for_write] {
if (fs::client::exists(conn, path_for_write)) {
REQUIRE(fs::client::remove(conn, path_for_write, fs::remove_options::no_trash));
}
REQUIRE(std::system("iadmin rmchildfromresc ut_pt_resc ut_ufs_resc_1") == 0);
REQUIRE(std::system("iadmin rmresc ut_ufs_resc_1") == 0);
REQUIRE(std::system("iadmin rmresc ut_pt_resc") == 0);
REQUIRE(std::system("iadmin rmchildfromresc ut_pt_resc ut_ufs_resc_1") == 0); // NOLINT(cert-env33-c)
REQUIRE(std::system("iadmin rmresc ut_ufs_resc_1") == 0); // NOLINT(cert-env33-c)
REQUIRE(std::system("iadmin rmresc ut_pt_resc") == 0); // NOLINT(cert-env33-c)
}};

io::client::native_transport tp{conn};
io::odstream data_object_out{tp, path_for_write, io::root_resource_name{"ut_pt_resc"}};
data_object_out.close();

using tuples = std::vector<std::tuple<std::string, std::string>>;
auto once = False;
for (const auto& [resource_keyword, value] : tuples{// test case for one existing replica
{"", ""},
// test case for two existing replicas
{RESC_NAME_KW, "ut_pt_resc"},
{RESC_HIER_STR_KW, "ut_pt_resc;ut_ufs_resc_1"}})
// Create a data object.
{
DataObjInp inp{};
ix::key_value_proxy kvp{inp.condInput};
irods::at_scope_exit free_memory{[&kvp] { kvp.clear(); }};
std::strncpy(inp.objPath, path_for_write.c_str(), MAX_NAME_LEN);

kvp[GET_RESOURCE_INFO_OP_TYPE_KW] = "WRITE";
io::client::native_transport tp{conn};
io::odstream{tp, path_for_write, io::root_resource_name{"ut_pt_resc"}};
}

if (!resource_keyword.empty()) {
kvp[resource_keyword] = value;
// clang-format off
const std::vector<std::tuple<std::string, std::string, std::string>> test_cases{
// Test case for one existing replica.
{"single existing replica - no keywords", "", ""},
// Test case for two existing replicas.
{"multiple replicas - using RESC_NAME_KW", RESC_NAME_KW, "ut_pt_resc"},
{"multiple replicas - using RESC_HIER_STR_KW", RESC_HIER_STR_KW, "ut_pt_resc;ut_ufs_resc_1"}
};
// clang-format on

auto replicate_data_object = true;

for (const auto& [title, resource_keyword, value] : test_cases) {
DYNAMIC_SECTION(title)
{
DataObjInp inp{};
ix::key_value_proxy kvp{inp.condInput};
irods::at_scope_exit clear_kvp{[&kvp] { kvp.clear(); }};
std::strncpy(static_cast<char*>(inp.objPath), path_for_write.c_str(), MAX_NAME_LEN);

kvp[GET_RESOURCE_INFO_OP_TYPE_KW] = "WRITE";

if (!resource_keyword.empty()) {
kvp[resource_keyword] = value;
}

char* char_buffer{};
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory, cppcoreguidelines-no-malloc)
irods::at_scope_exit free_char_buffer{[&char_buffer] { std::free(char_buffer); }};

REQUIRE(rc_get_resource_info_for_operation(static_cast<RcComm*>(conn), &inp, &char_buffer) == 0);
REQUIRE(nullptr != char_buffer);

const auto results = nlohmann::json::parse(char_buffer);
CHECK(results.at("host").get_ref<const std::string&>() == hostname);
CHECK(results.at("resource_hierarchy").get_ref<const std::string&>() == "ut_pt_resc;ut_ufs_resc_1");

// Ensure on all but first repetition that there are two replicas.
if (replicate_data_object) {
replicate_data_object = false;
unit_test_utils::replicate_data_object(conn, path_for_write.c_str(), "demoResc");
}
}
}
}

char* char_buffer{};
irods::at_scope_exit free_char_buffer{[&char_buffer] { std::free(char_buffer); }};
SECTION("returns error on invalid inputs")
{
DataObjInp input{};
char* json_output{};

rc_get_resource_info_for_operation(&comm, &inp, &char_buffer);
REQUIRE(char_buffer != nullptr);
SECTION("RcComm is null")
{
CHECK(rc_get_resource_info_for_operation(nullptr, &input, &json_output) == SYS_INVALID_INPUT_PARAM);
CHECK(nullptr == json_output);
}

auto results = nlohmann::json::parse(char_buffer);
REQUIRE(results["host"].get<std::string>() == hostname);
REQUIRE(results["resource_hierarchy"].get<std::string>() == "ut_pt_resc;ut_ufs_resc_1");
SECTION("DataObjInp is null")
{
CHECK(rc_get_resource_info_for_operation(static_cast<RcComm*>(conn), nullptr, &json_output) ==
SYS_INVALID_INPUT_PARAM);
CHECK(nullptr == json_output);
}

// Ensure on all but first repetition that there are two replicas.
if (!once) {
unit_test_utils::replicate_data_object(conn, path_for_write, "demoResc");
once = True;
}
SECTION("Output pointer is null")
{
CHECK(rc_get_resource_info_for_operation(static_cast<RcComm*>(conn), &input, nullptr) ==
SYS_INVALID_INPUT_PARAM);
CHECK(nullptr == json_output);
}

SECTION("GET_RESOURCE_INFO_OP_TYPE_KW not set")
{
CHECK(rc_get_resource_info_for_operation(static_cast<RcComm*>(conn), &input, &json_output) ==
SYS_INVALID_INPUT_PARAM);
CHECK(nullptr == json_output);
}

SECTION("invalid value for GET_RESOURCE_INFO_OP_TYPE_KW")
{
addKeyVal(&input.condInput, GET_RESOURCE_INFO_OP_TYPE_KW, "invalid");
CHECK(rc_get_resource_info_for_operation(static_cast<RcComm*>(conn), &input, &json_output) ==
INVALID_OPERATION);
CHECK(nullptr == json_output);
}

SECTION("invalid value for RESC_NAME_KW")
{
addKeyVal(&input.condInput, GET_RESOURCE_INFO_OP_TYPE_KW, "CREATE");
addKeyVal(&input.condInput, RESC_NAME_KW, "invalid");
CHECK(rc_get_resource_info_for_operation(static_cast<RcComm*>(conn), &input, &json_output) ==
SYS_RESC_DOES_NOT_EXIST);
CHECK(nullptr == json_output);
}

SECTION("invalid value for RESC_HIER_STR_KW")
{
addKeyVal(&input.condInput, GET_RESOURCE_INFO_OP_TYPE_KW, "CREATE");
addKeyVal(&input.condInput, RESC_HIER_STR_KW, "invalid");
CHECK(rc_get_resource_info_for_operation(static_cast<RcComm*>(conn), &input, &json_output) ==
SYS_RESC_DOES_NOT_EXIST);
CHECK(nullptr == json_output);
}

SECTION("invalid logical path")
{
const auto path = sandbox / "does_not_exist/data_object.txt";
std::strncpy(static_cast<char*>(input.objPath), path.c_str(), sizeof(DataObjInp::objPath));
addKeyVal(&input.condInput, GET_RESOURCE_INFO_OP_TYPE_KW, "OPEN");
CHECK(rc_get_resource_info_for_operation(static_cast<RcComm*>(conn), &input, &json_output) ==
CAT_NO_ROWS_FOUND);
CHECK(nullptr == json_output);
}
}
}

namespace
{

auto get_hostname() noexcept -> std::string
{
char hostname[HOST_NAME_MAX + 1]{};
gethostname(hostname, sizeof(hostname));
return hostname;
}

auto create_resource_vault(const std::string& _vault_name) -> boost::filesystem::path
auto create_resource_vault_path(const std::string& _vault_name) -> std::string
{
namespace fs = boost::filesystem;

const auto suffix = "_" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
const auto vault = boost::filesystem::temp_directory_path() / (_vault_name + suffix).data();

// Create the vault for the resource and allow the iRODS server to
// read and write to the vault.

if (!exists(vault)) {
REQUIRE(fs::create_directory(vault));
}

fs::permissions(vault, fs::perms::add_perms | fs::perms::others_read | fs::perms::others_write);

return vault;
}
return vault.string();
} // create_resource_vault_path
} //namespace
1 change: 1 addition & 0 deletions unit_tests/unit_tests_list.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"irods_generate_random_alphanumeric_string",
"irods_get_delay_rule_info",
"irods_get_file_descriptor_info",
"irods_get_resource_info_for_operation",
"irods_hierarchy_parser",
"irods_hostname_cache",
"irods_json_apis_from_client",
Expand Down

0 comments on commit d78404a

Please sign in to comment.