1
0
Fork 0
arangodb/tests/Aql/ExecutionBlockImplTest.cpp

397 lines
14 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2018 ArangoDB GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Tobias Goedderz
/// @author Michael Hackstein
/// @author Heiko Kernbach
/// @author Jan Christoph Uhde
////////////////////////////////////////////////////////////////////////////////
#include "gtest/gtest.h"
#include "AqlItemBlockHelper.h"
#include "TestEmptyExecutorHelper.h"
#include "TestExecutorHelper.h"
#include "WaitingExecutionBlockMock.h"
#include "fakeit.hpp"
#include "Aql/AqlItemBlock.h"
#include "Aql/ExecutionBlockImpl.h"
#include "Aql/ExecutionEngine.h"
#include "Aql/Query.h"
#include "Aql/SingleRowFetcher.h"
#include "Transaction/Context.h"
#include "Transaction/Methods.h"
using namespace arangodb;
using namespace arangodb::aql;
namespace arangodb {
namespace tests {
namespace aql {
// This test is supposed to only test getSome return values,
// it is not supposed to test the fetch logic!
class ExecutionBlockImplTest : public ::testing::Test {
protected:
// ExecutionState state
SharedAqlItemBlockPtr result;
// Mock of the ExecutionEngine
fakeit::Mock<ExecutionEngine> mockEngine;
ExecutionEngine& engine;
// Mock of the AqlItemBlockManager
fakeit::Mock<AqlItemBlockManager> mockBlockManager;
AqlItemBlockManager& itemBlockManager;
// Mock of the transaction
fakeit::Mock<transaction::Methods> mockTrx;
transaction::Methods& trx;
// Mock of the transaction context
fakeit::Mock<transaction::Context> mockContext;
transaction::Context& context;
// Mock of the Query
fakeit::Mock<Query> mockQuery;
Query& query;
ExecutionState state;
ResourceMonitor monitor;
// Mock of the QueryOptions
fakeit::Mock<QueryOptions> mockQueryOptions;
QueryOptions& lqueryOptions;
ProfileLevel profile;
// This is not used thus far in Base-Clase
ExecutionNode const* node = nullptr;
// Executor Infos
TestExecutorHelperInfos infos;
TestEmptyExecutorHelperInfos emptyInfos;
SharedAqlItemBlockPtr block;
ExecutionBlockImplTest()
: engine(mockEngine.get()),
itemBlockManager(mockBlockManager.get()),
trx(mockTrx.get()),
context(mockContext.get()),
query(mockQuery.get()),
lqueryOptions(mockQueryOptions.get()),
profile(ProfileLevel(PROFILE_LEVEL_NONE)),
node(nullptr),
infos(0, 1, 1, {}, {0}),
emptyInfos(0, 1, 1, {}, {0}),
block(nullptr) {
fakeit::When(Method(mockBlockManager, requestBlock)).AlwaysDo([&](size_t nrItems, RegisterId nrRegs) -> SharedAqlItemBlockPtr {
return SharedAqlItemBlockPtr{new AqlItemBlock(itemBlockManager, nrItems, nrRegs)};
});
fakeit::When(Method(mockEngine, itemBlockManager)).AlwaysReturn(itemBlockManager);
fakeit::When(Method(mockEngine, getQuery)).AlwaysReturn(&query);
fakeit::When(OverloadedMethod(mockBlockManager, returnBlock, void(AqlItemBlock*&)))
.AlwaysDo([&](AqlItemBlock*& block) -> void {
AqlItemBlockManager::deleteBlock(block);
block = nullptr;
});
fakeit::When(Method(mockBlockManager, resourceMonitor)).AlwaysReturn(&monitor);
fakeit::When(ConstOverloadedMethod(mockQuery, queryOptions, QueryOptions const&()))
.AlwaysDo([&]() -> QueryOptions const& { return lqueryOptions; });
fakeit::When(OverloadedMethod(mockQuery, queryOptions, QueryOptions & ()))
.AlwaysDo([&]() -> QueryOptions& { return lqueryOptions; });
fakeit::When(Method(mockQuery, trx)).AlwaysReturn(&trx);
fakeit::When(Method(mockQueryOptions, getProfileLevel)).AlwaysReturn(profile);
fakeit::When(Method(mockTrx, transactionContextPtr)).AlwaysReturn(&context);
fakeit::When(Method(mockContext, getVPackOptions)).AlwaysReturn(&velocypack::Options::Defaults);
}
};
TEST_F(ExecutionBlockImplTest,
there_is_a_block_in_the_upstream_with_no_rows_inside_the_executor_waits_using_getsome) {
std::deque<SharedAqlItemBlockPtr> blockDeque;
SharedAqlItemBlockPtr block = buildBlock<1>(itemBlockManager, {{42}});
blockDeque.push_back(std::move(block));
WaitingExecutionBlockMock dependency{&engine, node, std::move(blockDeque)};
ExecutionBlockImpl<TestExecutorHelper> testee(&engine, node, std::move(infos));
testee.addDependency(&dependency);
size_t atMost = 1000;
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
std::tie(state, block) = testee.getSome(atMost);
ASSERT_NE(block, nullptr);
ASSERT_EQ(block->size(), 1);
ASSERT_EQ(state, ExecutionState::DONE);
// done should stay done!
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(block, nullptr);
ASSERT_EQ(state, ExecutionState::DONE);
}
TEST_F(ExecutionBlockImplTest,
there_is_a_block_in_the_upstream_with_no_rows_inside_the_executor_waits_using_skipsome) {
std::deque<SharedAqlItemBlockPtr> blockDeque;
SharedAqlItemBlockPtr block = buildBlock<1>(itemBlockManager, {{42}});
blockDeque.push_back(std::move(block));
WaitingExecutionBlockMock dependency{&engine, node, std::move(blockDeque)};
ExecutionBlockImpl<TestExecutorHelper> testee(&engine, node, std::move(infos));
testee.addDependency(&dependency);
size_t atMost = 1;
size_t skipped = 0;
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
ASSERT_EQ(skipped, 0);
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::DONE);
ASSERT_EQ(skipped, 1);
// done should stay done!
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::DONE);
ASSERT_EQ(skipped, 0);
}
TEST_F(ExecutionBlockImplTest,
there_are_multiple_blocks_in_the_upstream_with_no_rows_inside_the_executor_waits_using_getsome_one_block) {
// we are checking multiple input blocks
// we are only fetching 1 row each (atMost = 1)
// after a DONE is returned, it must stay done!
std::deque<SharedAqlItemBlockPtr> blockDeque;
SharedAqlItemBlockPtr blocka = buildBlock<1>(itemBlockManager, {{42}});
SharedAqlItemBlockPtr blockb = buildBlock<1>(itemBlockManager, {{42}});
SharedAqlItemBlockPtr blockc = buildBlock<1>(itemBlockManager, {{42}});
SharedAqlItemBlockPtr blockd = buildBlock<1>(itemBlockManager, {{42}});
SharedAqlItemBlockPtr blocke = buildBlock<1>(itemBlockManager, {{42}});
blockDeque.push_back(std::move(blocka));
blockDeque.push_back(std::move(blockb));
blockDeque.push_back(std::move(blockc));
blockDeque.push_back(std::move(blockd));
blockDeque.push_back(std::move(blocke));
WaitingExecutionBlockMock dependency{&engine, node, std::move(blockDeque)};
ExecutionBlockImpl<TestExecutorHelper> testee(&engine, node, std::move(infos));
testee.addDependency(&dependency);
size_t atMost = 1;
size_t total = 0;
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::HASMORE);
total = total + block->size();
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::HASMORE);
total = total + block->size();
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::HASMORE);
total = total + block->size();
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::HASMORE);
total = total + block->size();
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::DONE);
total = total + block->size();
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::DONE);
ASSERT_EQ(total, 5);
}
TEST_F(ExecutionBlockImplTest,
there_are_multiple_blocks_in_the_upstream_with_no_rows_inside_the_executor_waits_using_getsome_multiple_blocks) {
// as test above, BUT with a higher atMost value.
std::deque<SharedAqlItemBlockPtr> blockDeque;
SharedAqlItemBlockPtr blocka = buildBlock<1>(itemBlockManager, {{42}});
SharedAqlItemBlockPtr blockb = buildBlock<1>(itemBlockManager, {{42}});
SharedAqlItemBlockPtr blockc = buildBlock<1>(itemBlockManager, {{42}});
SharedAqlItemBlockPtr blockd = buildBlock<1>(itemBlockManager, {{42}});
SharedAqlItemBlockPtr blocke = buildBlock<1>(itemBlockManager, {{42}});
blockDeque.push_back(std::move(blocka));
blockDeque.push_back(std::move(blockb));
blockDeque.push_back(std::move(blockc));
blockDeque.push_back(std::move(blockd));
blockDeque.push_back(std::move(blocke));
WaitingExecutionBlockMock dependency{&engine, node, std::move(blockDeque)};
ExecutionBlockImpl<TestExecutorHelper> testee(&engine, node, std::move(infos));
testee.addDependency(&dependency);
size_t atMost = 2;
size_t total = 0;
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::HASMORE);
total = total + block->size();
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::HASMORE);
total = total + block->size();
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::DONE);
total = total + block->size();
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::DONE);
ASSERT_EQ(block, nullptr);
ASSERT_EQ(total, 5);
}
TEST_F(ExecutionBlockImplTest,
there_are_multiple_blocks_in_the_upstream_with_no_rows_inside_the_executor_waits_using_skipsome) {
// we are checking multiple input blocks
// we are only fetching 1 row each (atMost = 1)
// after a DONE is returned, it must stay done!
std::deque<SharedAqlItemBlockPtr> blockDeque;
SharedAqlItemBlockPtr blocka = buildBlock<1>(itemBlockManager, {{42}});
SharedAqlItemBlockPtr blockb = buildBlock<1>(itemBlockManager, {{42}});
SharedAqlItemBlockPtr blockc = buildBlock<1>(itemBlockManager, {{42}});
SharedAqlItemBlockPtr blockd = buildBlock<1>(itemBlockManager, {{42}});
SharedAqlItemBlockPtr blocke = buildBlock<1>(itemBlockManager, {{42}});
blockDeque.push_back(std::move(blocka));
blockDeque.push_back(std::move(blockb));
blockDeque.push_back(std::move(blockc));
blockDeque.push_back(std::move(blockd));
blockDeque.push_back(std::move(blocke));
WaitingExecutionBlockMock dependency{&engine, node, std::move(blockDeque)};
ExecutionBlockImpl<TestExecutorHelper> testee(&engine, node, std::move(infos));
testee.addDependency(&dependency);
size_t atMost = 1;
size_t skipped = 0;
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
ASSERT_EQ(skipped, 0);
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::HASMORE);
ASSERT_EQ(skipped, 1);
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
ASSERT_EQ(skipped, 0);
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::HASMORE);
ASSERT_EQ(skipped, 1);
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
ASSERT_EQ(skipped, 0);
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::HASMORE);
ASSERT_EQ(skipped, 1);
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
ASSERT_EQ(skipped, 0);
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::HASMORE);
ASSERT_EQ(skipped, 1);
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::WAITING);
ASSERT_EQ(skipped, 0);
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::DONE);
ASSERT_EQ(skipped, 1);
std::tie(state, skipped) = testee.skipSome(atMost);
ASSERT_EQ(state, ExecutionState::DONE);
ASSERT_EQ(skipped, 0);
}
TEST_F(ExecutionBlockImplTest,
there_is_an_invalid_empty_block_in_the_upstream_the_executor_waits_using_getsome) {
std::deque<SharedAqlItemBlockPtr> blockDeque;
SharedAqlItemBlockPtr block = buildBlock<1>(itemBlockManager, {{42}});
blockDeque.push_back(std::move(block));
WaitingExecutionBlockMock dependency{&engine, node, std::move(blockDeque)};
ExecutionBlockImpl<TestEmptyExecutorHelper> testee(&engine, node, std::move(emptyInfos));
testee.addDependency(&dependency);
size_t atMost = 1000;
std::tie(state, block) = testee.getSome(atMost);
ASSERT_EQ(state, ExecutionState::DONE);
ASSERT_EQ(block, nullptr);
}
} // namespace aql
} // namespace tests
} // namespace arangodb