Skip to content

Commit

Permalink
executor fixture
Browse files Browse the repository at this point in the history
Signed-off-by: Mikhail Boldyrev <miboldyrev@gmail.com>
  • Loading branch information
MBoldyrev committed Aug 16, 2019
1 parent 22ce9a8 commit 2c86dd0
Show file tree
Hide file tree
Showing 6 changed files with 396 additions and 0 deletions.
1 change: 1 addition & 0 deletions test/integration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

add_subdirectory(acceptance)
add_subdirectory(consensus)
add_subdirectory(executor)
add_subdirectory(pipeline)
add_subdirectory(validation)

Expand Down
12 changes: 12 additions & 0 deletions test/integration/executor/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright Soramitsu Co., Ltd. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

add_library(executor_fixture executor_fixture.cpp)
target_link_libraries(executor_fixture
executor_itf
)

add_library(executor_fixture_param executor_fixture_param.cpp)
target_link_libraries(executor_fixture_param
gtest::main
)
156 changes: 156 additions & 0 deletions test/integration/executor/executor_fixture.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

#include "integration/executor/executor_fixture.hpp"

#include <gtest/gtest.h>
#include <boost/range/adaptor/transformed.hpp>
#include "cryptography/crypto_provider/crypto_defaults.hpp"
#include "framework/common_constants.hpp"
#include "framework/result_gtest_checkers.hpp"
#include "interfaces/query_responses/query_response.hpp"
#include "interfaces/query_responses/signatories_response.hpp"
#include "module/shared_model/mock_objects_factories/mock_command_factory.hpp"
#include "module/shared_model/mock_objects_factories/mock_query_factory.hpp"

using namespace common_constants;
using namespace executor_testing;
using namespace framework::expected;
using namespace iroha::ametsuchi;
using namespace iroha::expected;
using namespace iroha::integration_framework;
using namespace shared_model::interface::types;

namespace executor_testing {

void checkCommandError(const CommandResult &command_result,
CommandError::ErrorCodeType error_code) {
if (auto err = resultToOptionalError(command_result)) {
EXPECT_EQ(err->error_code, error_code);
} else {
ADD_FAILURE() << "Did not get the expected command error!";
}
}

std::pair<std::string, std::string> splitNameAndDomain(const std::string &id,
char delimeter) {
auto it = id.find(delimeter);
if (it == std::string::npos) {
throw std::runtime_error(std::string{"Failed to split '"} + id + "' by '"
+ delimeter + "' because delimeter not found.");
}
if (id.find(delimeter, it + 1) != std::string::npos) {
throw std::runtime_error(std::string{"Failed to split '"} + id + "' by '"
+ delimeter
+ "' because delimeter found more than once.");
}
return std::make_pair(id.substr(0, it), id.substr(it + 1));
}

std::pair<std::string, std::string> splitAssetId(const std::string &id) {
return splitNameAndDomain(id, '#');
}

std::pair<std::string, std::string> splitAccountId(const std::string &id) {
return splitNameAndDomain(id, '@');
}

} // namespace executor_testing

void ExecutorTestBase::SetUp() {
getBackendParam()->clearBackendState();
auto executor_itf_result =
ExecutorItf::create(getBackendParam()->getExecutorItfParam());
assertResultValue(executor_itf_result);
executor_itf_ =
resultToOptionalValue((std::move(executor_itf_result))).value();
}

ExecutorItf &ExecutorTestBase::getItf() const {
return *executor_itf_;
}

void ExecutorTestBase::createAsset(const std::string &name,
const std::string &domain,
PrecisionType precision) const {
SCOPED_TRACE("createAsset");
assertResultValue(getItf().executeMaintenanceCommand(
*getItf().getMockCommandFactory()->constructCreateAsset(
name, domain, precision)));
}

void ExecutorTestBase::addAsset(
const AccountIdType &dest_account_id,
const AssetIdType &asset_id,
const shared_model::interface::Amount &quantity) {
SCOPED_TRACE("addAsset");
assertResultValue(getItf().executeMaintenanceCommand(
*getItf().getMockCommandFactory()->constructAddAssetQuantity(asset_id,
quantity)));
assertResultValue(getItf().executeMaintenanceCommand(
*getItf().getMockCommandFactory()->constructTransferAsset(
kAdminId, dest_account_id, asset_id, "adding asset", quantity)));
}

void ExecutorTestBase::checkAssetQuantities(
const AccountAssetCollectionType &test_quantities,
const std::vector<AssetQuantity> &reference_quantities) {
static const auto make_asset_matcher = [](AssetQuantity reference) {
return ::testing::Truly(
[reference](const shared_model::interface::AccountAsset &tested) {
if (tested.assetId() == reference.asset_id) {
EXPECT_EQ(tested.balance(), reference.balance)
<< "Wrong balance of asset " << reference.asset_id;
return true;
}
return false;
});
};

auto asset_matchers = boost::copy_range<
std::vector<decltype(make_asset_matcher(reference_quantities.front()))>>(
reference_quantities | boost::adaptors::transformed(make_asset_matcher));

EXPECT_THAT(test_quantities,
::testing::UnorderedElementsAreArray(asset_matchers));
}

void ExecutorTestBase::checkAssetQuantities(
const std::string &account_id,
const std::vector<AssetQuantity> &quantities) {
auto pagination_meta =
getItf().getMockQueryFactory()->constructAssetPaginationMeta(
quantities.size(), boost::none);
getItf()
.executeQueryAndConvertResult(
*getItf().getMockQueryFactory()->constructGetAccountAssets(
account_id, *pagination_meta))
.specific_response.match(
[&](const auto &get_account_assets_response) {
checkAssetQuantities(
get_account_assets_response.value.accountAssets(), quantities);
},
[](const auto &other_response) {
ADD_FAILURE() << "Unexpected query response: "
<< other_response.error->toString();
});
}

void ExecutorTestBase::checkSignatories(
const std::string &account_id,
const std::vector<shared_model::crypto::PublicKey> &keys) {
getItf()
.executeQueryAndConvertResult(
*getItf().getMockQueryFactory()->constructGetSignatories(account_id))
.specific_response.match(
[&](const auto &get_signatories_response) {
EXPECT_THAT(get_signatories_response.value.keys(),
::testing::UnorderedElementsAreArray(keys));
},
[](const auto &other_response) {
ADD_FAILURE() << "Unexpected query response: "
<< other_response.error->toString();
});
}
177 changes: 177 additions & 0 deletions test/integration/executor/executor_fixture.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/**
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef TEST_INTEGRATION_EXECUTOR_FIXTURE_HPP
#define TEST_INTEGRATION_EXECUTOR_FIXTURE_HPP

#include "framework/executor_itf/executor_itf.hpp"

#include <gtest/gtest.h>
#include "common/result.hpp"
#include "cryptography/crypto_provider/crypto_defaults.hpp"
#include "framework/common_constants.hpp"
#include "framework/result_gtest_checkers.hpp"
#include "integration/executor/executor_fixture_param.hpp"
#include "interfaces/query_responses/error_query_response.hpp"
#include "module/shared_model/mock_objects_factories/mock_command_factory.hpp"
#include "module/shared_model/mock_objects_factories/mock_query_factory.hpp"

namespace executor_testing {

namespace error_codes {
using shared_model::interface::ErrorQueryResponse;

// TODO [IR-1816] Akvinikym 06.12.18: remove these constants after
// introducing a uniform way to use them in code
static constexpr ErrorQueryResponse::ErrorCodeType kNoStatefulError = 0;
static constexpr ErrorQueryResponse::ErrorCodeType kNoPermissions = 2;
static constexpr ErrorQueryResponse::ErrorCodeType kInvalidPagination = 4;
static constexpr ErrorQueryResponse::ErrorCodeType kInvalidAccountId = 5;
static constexpr ErrorQueryResponse::ErrorCodeType kInvalidAssetId = 6;
static constexpr ErrorQueryResponse::ErrorCodeType kInvalidHeight = 3;
} // namespace error_codes

std::pair<std::string, std::string> splitAssetId(const std::string &id);

std::pair<std::string, std::string> splitAccountId(const std::string &id);

/**
* Check that general query response contains a specific result type and
* execute a callback on it.
* @tparam SpecificQueryResponse - Expected specific query response.
* @tparam Callback - Type of callback.
* @param response - The response to be checked.
* @param callback - The callback to be executed on specific result.
*/
template <typename SpecificQueryResponse, typename Callback>
void checkSuccessfulResult(
const iroha::ametsuchi::QueryExecutorResult &response,
Callback callback) {
auto specific_result =
boost::strict_get<const SpecificQueryResponse &>(&response->get());
if (not specific_result) {
ADD_FAILURE() << "Wrong query response type: " << response->toString();
return;
}
std::forward<Callback>(callback)(*specific_result);
}

/**
* Check that general command response contains an error with a specific error
* code.
* @param command_result - The response to be checked.
* @param error_code - The expected error code.
*/
void checkCommandError(
const iroha::ametsuchi::CommandResult &command_result,
iroha::ametsuchi::CommandError::ErrorCodeType error_code);

/**
* Check that general query response contains a specific error type and
* execute a callback on it.
* @tparam SpecificErrorResponse - Expected specific query error response.
* @param response - The response to be checked.
* @param error_code - The expected error code.
*/
template <typename SpecificErrorResponse>
void checkQueryError(
const iroha::ametsuchi::QueryExecutorResult &response,
shared_model::interface::ErrorQueryResponse::ErrorCodeType error_code) {
static const auto error_type = typeid(SpecificErrorResponse).name();
if (auto error = boost::strict_get<
const shared_model::interface::ErrorQueryResponse &>(
&response->get())) {
EXPECT_TRUE(
boost::strict_get<const SpecificErrorResponse &>(&error->get()))
<< "Expected an error of type " << error_type << ", but got "
<< error->toString();
EXPECT_EQ(error->errorCode(), error_code)
<< "Wrong query result error code!";
} else {
ADD_FAILURE() << "Expected an error of type " << error_type
<< ", but got " << response->toString();
}
}

/// Base class for Executor ITF tests.
class ExecutorTestBase : public ::testing::Test {
public:
void SetUp();

iroha::integration_framework::ExecutorItf &getItf() const;

// ---------------- ledger populators --------------

void createAsset(
const std::string &name,
const std::string &domain,
shared_model::interface::types::PrecisionType precision) const;

void addAsset(
const shared_model::interface::types::AccountIdType &dest_account_id,
const shared_model::interface::types::AssetIdType &asset_id,
const shared_model::interface::Amount &quantity);

// ---------------- checkers -----------------

/// A plain representation of an asset quantity.
struct AssetQuantity {
AssetQuantity(std::string asset_id, const std::string &balance)
: asset_id(std::move(asset_id)), balance(balance) {}
std::string asset_id;
shared_model::interface::Amount balance;
};

/**
* Check that the given account assets collection contains the reference
* assets and quantities.
*/
static void checkAssetQuantities(
const shared_model::interface::types::AccountAssetCollectionType
&test_quantities,
const std::vector<AssetQuantity> &reference_quantities);

/**
* Check that the given account contains the exact provided assets and
* quantities.
*/
void checkAssetQuantities(const std::string &account_id,
const std::vector<AssetQuantity> &quantities);

/// Check that the given account contains the exact provided signatures.
void checkSignatories(
const std::string &account_id,
const std::vector<shared_model::crypto::PublicKey> &keys);

protected:
virtual std::shared_ptr<ExecutorTestParam> getBackendParam() = 0;

private:
std::unique_ptr<iroha::integration_framework::ExecutorItf> executor_itf_;
};

/**
* A class that provides the backend parameter from GTest parametric test.
* @tparam SpecificQueryFixture Is supposed to be either ExecutorTestBase or
* its derivative.
*
* When different test cases require different parameters, users are supposed
* to implement the required logic in a class derived from ExecutorTestBase,
* and then derive from it helper classes like this to instantiate different
* parametric cases.
*/
template <typename SpecificQueryFixture>
class BasicExecutorTest : public SpecificQueryFixture,
public ::testing::WithParamInterface<
std::shared_ptr<ExecutorTestParam>> {
protected:
virtual std::shared_ptr<ExecutorTestParam> getBackendParam() {
return GetParam();
}
};

} // namespace executor_testing

#endif /* TEST_INTEGRATION_EXECUTOR_FIXTURE_HPP */
15 changes: 15 additions & 0 deletions test/integration/executor/executor_fixture_param.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

#include "integration/executor/executor_fixture_param.hpp"

using namespace executor_testing;

ExecutorTestParam::~ExecutorTestParam() = default;

std::string executor_testing::paramToString(
testing::TestParamInfo<std::shared_ptr<ExecutorTestParam>> param) {
return param.param->toString();
}

0 comments on commit 2c86dd0

Please sign in to comment.