From 760b35766ab4be2426ccebdb434d9be4e7b4c20d Mon Sep 17 00:00:00 2001 From: Kory Draughn Date: Thu, 21 Sep 2023 16:37:33 -0400 Subject: [PATCH] [#7256] Tweaked unit tests for new API endpoint. --- .../test_get_resource_info_for_operation.cpp | 287 +++++++++++------- unit_tests/unit_tests_list.json | 1 + 2 files changed, 178 insertions(+), 110 deletions(-) diff --git a/unit_tests/src/test_get_resource_info_for_operation.cpp b/unit_tests/src/test_get_resource_info_for_operation.cpp index 038d5202e0..4c150e6692 100644 --- a/unit_tests/src/test_get_resource_info_for_operation.cpp +++ b/unit_tests/src/test_get_resource_info_for_operation.cpp @@ -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 #include #include +#include #include #include +#include #include #include +#include #include #include -#include #include "unit_test_utils.hpp" @@ -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(conn); rodsEnv env; _getRodsEnv(env); @@ -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(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 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(conn), &inp, &char_buffer) == 0); + REQUIRE(nullptr != char_buffer); auto results = nlohmann::json::parse(char_buffer); - REQUIRE(results["host"].get() == hostname); - REQUIRE(results["resource_hierarchy"].get() == default_resource); + CHECK(results.at("host").get_ref() == hostname); + CHECK(results.at("resource_hierarchy").get_ref() == 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() == hostname); - REQUIRE(results["resource_hierarchy"].get() == default_resource); + std::strncpy(static_cast(inp.objPath), path.c_str(), MAX_NAME_LEN); + + // clang-format off + const auto test_cases = std::to_array>({ + {"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(conn), &inp, &char_buffer) == 0); + REQUIRE(nullptr != char_buffer); + + const auto results = nlohmann::json::parse(char_buffer); + CHECK(results.at("host").get_ref() == hostname); + CHECK(results.at("resource_hierarchy").get_ref() == 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(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>; - 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> 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(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(conn), &inp, &char_buffer) == 0); + REQUIRE(nullptr != char_buffer); + + const auto results = nlohmann::json::parse(char_buffer); + CHECK(results.at("host").get_ref() == hostname); + CHECK(results.at("resource_hierarchy").get_ref() == "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() == hostname); - REQUIRE(results["resource_hierarchy"].get() == "ut_pt_resc;ut_ufs_resc_1"); + SECTION("DataObjInp is null") + { + CHECK(rc_get_resource_info_for_operation(static_cast(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(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(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(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(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(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(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(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 diff --git a/unit_tests/unit_tests_list.json b/unit_tests/unit_tests_list.json index 534e6bf086..ff50ac4a5d 100644 --- a/unit_tests/unit_tests_list.json +++ b/unit_tests/unit_tests_list.json @@ -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",