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

473 lines
14 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @brief test sr AQL Datefunctions
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2017 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 Michael Hackstein
/// @author Copyright 2018, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
#include "gtest/gtest.h"
#include "fakeit.hpp"
#include "Aql/AqlValue.h"
#include "Aql/ExpressionContext.h"
#include "Aql/Functions.h"
#include "Basics/SmallVector.h"
#include "Transaction/Methods.h"
#include <velocypack/Builder.h>
#include <velocypack/Iterator.h>
#include <velocypack/Slice.h>
#include <velocypack/velocypack-aliases.h>
using namespace arangodb;
using namespace arangodb::aql;
namespace arangodb {
namespace tests {
namespace date_functions_aql {
struct TestDateModifierFlagFactory {
public:
enum FLAGS { INVALID, MILLI, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR };
static std::vector<std::string> createAllFlags(FLAGS const& e) {
switch (e) {
case INVALID:
return {"abc"};
case MILLI:
return {"f", "millisecond", "milliseconds", "MiLLiSeCOnd"};
case SECOND:
return {"s", "second", "seconds", "SeCoNd"};
case MINUTE:
return {"i", "minute", "minutes", "MiNutEs"};
case HOUR:
return {"h", "hour", "hours", "HoUr"};
case DAY:
return {"d", "day", "days", "daYs"};
case WEEK:
return {"w", "week", "weeks", "WeEkS"};
case MONTH:
return {"m", "month", "months", "mOnTHs"};
case YEAR:
return {"y", "year", "years", "yeArS"};
}
return {"abc"};
}
static std::string createFlag(FLAGS const& e) {
switch (e) {
case INVALID:
return "abc";
case MILLI:
return "f";
case SECOND:
return "s";
case MINUTE:
return "i";
case HOUR:
return "h";
case DAY:
return "d";
case WEEK:
return "w";
case MONTH:
return "m";
case YEAR:
return "y";
}
}
};
namespace is_datestring {
struct TestDate {
public:
TestDate(std::string const json, bool v) : _date(nullptr), _isValid(v) {
// Make sure to only insert valid JSON.
// We are not testing the parser here.
_date = arangodb::velocypack::Parser::fromJson(json);
}
std::string const testName() const {
return _date->toJson() + " => " + (_isValid ? "true" : "false");
}
void buildParams(VPackFunctionParameters& input) const {
input.emplace_back(_date.get());
}
void validateResult(AqlValue const& result) const {
ASSERT_TRUE(result.isBoolean());
ASSERT_TRUE(result.toBoolean() == _isValid);
}
private:
std::shared_ptr<arangodb::velocypack::Builder> _date;
bool _isValid;
};
TEST(DateFunctionsTest, IS_DATESTRING) {
fakeit::Mock<ExpressionContext> expressionContextMock;
ExpressionContext& expressionContext = expressionContextMock.get();
fakeit::Mock<transaction::Methods> trxMock;
transaction::Methods& trx = trxMock.get();
std::vector<TestDate> testees = {
#include "IS_DATESTRING.testcases"
};
for (auto const& testee : testees) {
SmallVector<AqlValue>::allocator_type::arena_type arena;
SmallVector<AqlValue> params{arena};
testee.buildParams(params);
AqlValue res = Functions::IsDatestring(&expressionContext, &trx, params);
testee.validateResult(res);
// Free input parameters
for (auto& it : params) {
it.destroy();
}
}
}
} // namespace is_datestring
namespace date_compare {
struct TestDate {
public:
TestDate(std::vector<std::string> const args, bool v) : _isValid(v) {
_argBuilder.openArray();
for (auto const& it : args) {
_argBuilder.add(VPackValue(it));
}
_argBuilder.close();
}
std::string const testName() const {
return "Input: " + _argBuilder.toJson() + " => " + (_isValid ? "true" : "false");
}
void buildParams(VPackFunctionParameters& input) const {
for (auto const& it : VPackArrayIterator(_argBuilder.slice())) {
input.emplace_back(it);
}
}
void validateResult(AqlValue const& result) const {
ASSERT_TRUE(result.isBoolean());
ASSERT_TRUE(result.toBoolean() == _isValid);
}
private:
arangodb::velocypack::Builder _argBuilder;
bool _isValid;
};
TEST(DateFunctionsTest, DATE_COMPARE) {
fakeit::Mock<ExpressionContext> expressionContextMock;
ExpressionContext& expressionContext = expressionContextMock.get();
fakeit::Mock<transaction::Methods> trxMock;
transaction::Methods& trx = trxMock.get();
std::vector<TestDate> testees = {
#include "DATE_COMPARE.testcases"
};
for (auto const& testee : testees) {
SmallVector<AqlValue>::allocator_type::arena_type arena;
SmallVector<AqlValue> params{arena};
testee.buildParams(params);
AqlValue res = Functions::DateCompare(&expressionContext, &trx, params);
testee.validateResult(res);
// Free input parameters
for (auto& it : params) {
it.destroy();
}
}
}
} // namespace date_compare
namespace date_diff {
class DateFunctionsTestDateDiff : public ::testing::Test {
protected:
fakeit::Mock<ExpressionContext> expressionContextMock;
ExpressionContext& expressionContext;
fakeit::Mock<transaction::Methods> trxMock;
transaction::Methods& trx;
// These dates differ by:
// 1 year
// 2 months
// 1 week
// 12 days
// 4 hours
// 5 minutes
// 6 seconds
// 123 milliseconds
std::string const earlierDate;
std::string const laterDate;
// Exact milisecond difference
double dateDiffMillis;
// Average number of days per month in the given dates
double avgDaysPerMonth;
SmallVector<AqlValue>::allocator_type::arena_type arena;
SmallVector<AqlValue> params;
VPackBuilder dateBuilder;
VPackBuilder flagBuilder;
VPackBuilder switchBuilder;
DateFunctionsTestDateDiff()
: expressionContext(expressionContextMock.get()),
trx(trxMock.get()),
earlierDate("2000-04-01T02:48:42.123"),
laterDate("2001-06-13T06:53:48.246"),
dateDiffMillis(37857906123),
avgDaysPerMonth(365.0/12.0),
params(arena) {
dateBuilder.openArray();
dateBuilder.add(VPackValue(earlierDate));
dateBuilder.add(VPackValue(laterDate));
dateBuilder.close();
}
void testCombinations(std::string const& f, double expected) {
{
double eps = 0.05;
params.clear();
flagBuilder.clear();
flagBuilder.add(VPackValue(f));
params.emplace_back(dateBuilder.slice().at(0));
params.emplace_back(dateBuilder.slice().at(1));
params.emplace_back(flagBuilder.slice());
switchBuilder.add(VPackValue(true));
params.emplace_back(switchBuilder.slice());
AqlValue res = Functions::DateDiff(&expressionContext, &trx, params);
ASSERT_TRUE(res.isNumber());
double out = res.toDouble();
ASSERT_GE(out, expected - eps);
ASSERT_LE(out, expected + eps);
for (auto& it : params) {
it.destroy();
}
}
{
params.clear();
flagBuilder.clear();
flagBuilder.add(VPackValue(f));
params.emplace_back(dateBuilder.slice().at(0));
params.emplace_back(dateBuilder.slice().at(1));
params.emplace_back(flagBuilder.slice());
switchBuilder.add(VPackValue(false));
params.emplace_back(switchBuilder.slice());
AqlValue res = Functions::DateDiff(&expressionContext, &trx, params);
ASSERT_TRUE(res.isNumber());
ASSERT_EQ(std::round(res.toDouble()), std::round(expected));
for (auto& it : params) {
it.destroy();
}
}
{
double eps = 0.05;
params.clear();
flagBuilder.clear();
flagBuilder.add(VPackValue(f));
params.emplace_back(dateBuilder.slice().at(1));
params.emplace_back(dateBuilder.slice().at(0));
params.emplace_back(flagBuilder.slice());
switchBuilder.add(VPackValue(true));
params.emplace_back(switchBuilder.slice());
AqlValue res = Functions::DateDiff(&expressionContext, &trx, params);
ASSERT_TRUE(res.isNumber());
double out = res.toDouble();
ASSERT_GE(out, -(expected + eps));
ASSERT_LE(out, -(expected - eps));
for (auto& it : params) {
it.destroy();
}
}
{
params.clear();
flagBuilder.clear();
flagBuilder.add(VPackValue(f));
params.emplace_back(dateBuilder.slice().at(1));
params.emplace_back(dateBuilder.slice().at(0));
params.emplace_back(flagBuilder.slice());
switchBuilder.add(VPackValue(false));
params.emplace_back(switchBuilder.slice());
AqlValue res = Functions::DateDiff(&expressionContext, &trx, params);
ASSERT_TRUE(res.isNumber());
ASSERT_EQ(std::round(res.toDouble()), -std::round(expected));
for (auto& it : params) {
it.destroy();
}
}
}
};
TEST_F(DateFunctionsTestDateDiff, checking_millis) {
double expectedDiff = dateDiffMillis;
auto allFlags = TestDateModifierFlagFactory::createAllFlags(
TestDateModifierFlagFactory::FLAGS::MILLI);
for (auto const& f : allFlags) {
testCombinations(f, expectedDiff);
}
}
TEST_F(DateFunctionsTestDateDiff, checking_seconds) {
double expectedDiff = dateDiffMillis / 1000;
auto allFlags = TestDateModifierFlagFactory::createAllFlags(
TestDateModifierFlagFactory::FLAGS::SECOND);
for (auto const& f : allFlags) {
testCombinations(f, expectedDiff);
}
}
TEST_F(DateFunctionsTestDateDiff, checking_minutes) {
double expectedDiff = dateDiffMillis / (1000 * 60);
auto allFlags = TestDateModifierFlagFactory::createAllFlags(
TestDateModifierFlagFactory::FLAGS::MINUTE);
for (auto const& f : allFlags) {
testCombinations(f, expectedDiff);
}
}
TEST_F(DateFunctionsTestDateDiff, checking_hours) {
double expectedDiff = dateDiffMillis / (1000 * 60 * 60);
auto allFlags = TestDateModifierFlagFactory::createAllFlags(
TestDateModifierFlagFactory::FLAGS::HOUR);
for (auto const& f : allFlags) {
testCombinations(f, expectedDiff);
}
}
TEST_F(DateFunctionsTestDateDiff, checking_days) {
double expectedDiff = dateDiffMillis / (1000 * 60 * 60 * 24);
auto allFlags = TestDateModifierFlagFactory::createAllFlags(
TestDateModifierFlagFactory::FLAGS::DAY);
for (auto const& f : allFlags) {
testCombinations(f, expectedDiff);
}
}
TEST_F(DateFunctionsTestDateDiff, checking_weeks) {
double expectedDiff = dateDiffMillis / (1000 * 60 * 60 * 24 * 7);
auto allFlags = TestDateModifierFlagFactory::createAllFlags(
TestDateModifierFlagFactory::FLAGS::WEEK);
for (auto const& f : allFlags) {
testCombinations(f, expectedDiff);
}
}
TEST_F(DateFunctionsTestDateDiff, checking_months) {
double expectedDiff = dateDiffMillis / (1000 * 60 * 60 * 24) / avgDaysPerMonth;
auto allFlags = TestDateModifierFlagFactory::createAllFlags(
TestDateModifierFlagFactory::FLAGS::MONTH);
for (auto const& f : allFlags) {
testCombinations(f, expectedDiff);
}
}
TEST_F(DateFunctionsTestDateDiff, checking_years) {
double expectedDiff = dateDiffMillis / (1000 * 60 * 60 * 24) / 365;
auto allFlags = TestDateModifierFlagFactory::createAllFlags(
TestDateModifierFlagFactory::FLAGS::YEAR);
for (auto const& f : allFlags) {
testCombinations(f, expectedDiff);
}
}
TEST_F(DateFunctionsTestDateDiff, checking_leap_days) {
// TODO!
}
} // namespace date_diff
namespace date_subtract {
struct TestDate {
public:
TestDate(std::string const& json, std::string const& v)
: _input(nullptr), _result(v) {
// Make sure to only insert valid JSON.
// We are not testing the parser here.
_input = arangodb::velocypack::Parser::fromJson(json);
}
std::string const testName() const {
return _input->toJson() + " => " + _result;
}
void buildParams(VPackFunctionParameters& input) const {
VPackSlice s = _input->slice();
for (auto const& it : VPackArrayIterator(s)) {
input.emplace_back(it);
}
}
void validateResult(AqlValue const& result) const {
ASSERT_TRUE(result.isString());
auto res = result.slice();
std::string ref = res.copyString(); // Readability in test Tool
ASSERT_TRUE(ref == _result);
}
private:
std::shared_ptr<arangodb::velocypack::Builder> _input;
std::string const _result;
};
TEST(DateFunctionsTest, DATE_SUBTRACT) {
fakeit::Mock<ExpressionContext> expressionContextMock;
ExpressionContext& expressionContext = expressionContextMock.get();
fakeit::Mock<transaction::Methods> trxMock;
transaction::Methods& trx = trxMock.get();
std::vector<TestDate> testees = {
#include "DATE_SUBTRACT.testcases"
};
for (auto const& testee : testees) {
SmallVector<AqlValue>::allocator_type::arena_type arena;
SmallVector<AqlValue> params{arena};
testee.buildParams(params);
AqlValue res = Functions::DateSubtract(&expressionContext, &trx, params);
testee.validateResult(res);
res.destroy();
// Free input parameters
for (auto& it : params) {
it.destroy();
}
}
}
} // namespace date_subtract
} // namespace date_functions_aql
} // namespace tests
} // namespace arangodb