diff --git a/libgnucash/app-utils/test/CMakeLists.txt b/libgnucash/app-utils/test/CMakeLists.txt index b738202fd6..85b85679ef 100644 --- a/libgnucash/app-utils/test/CMakeLists.txt +++ b/libgnucash/app-utils/test/CMakeLists.txt @@ -85,3 +85,21 @@ set_dist_list(test_app_utils_DIST ${test_app_utils_scheme_SOURCES} ${test_app_utils_SOURCES} ) + + +set(test_autoclear_SOURCES + test-autoclear.cpp +) +set(test_autoclear_INCLUDE_DIRS + ${APP_UTILS_TEST_INCLUDE_DIRS} + ${GTEST_INCLUDE_DIR} +) +set(test_autoclear_LIBS + ${APP_UTILS_TEST_LIBS} + gtest +) + +gnc_add_test(test-autoclear "${test_autoclear_SOURCES}" + test_autoclear_INCLUDE_DIRS + test_autoclear_LIBS +) diff --git a/libgnucash/app-utils/test/test-autoclear.cpp b/libgnucash/app-utils/test/test-autoclear.cpp new file mode 100644 index 0000000000..abb84e1c56 --- /dev/null +++ b/libgnucash/app-utils/test/test-autoclear.cpp @@ -0,0 +1,174 @@ +/******************************************************************** + * test-autoclear.c: test suite for Auto-Clear * + * Copyright 2020 Cristian Klein * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, you can retrieve it from * + * https://www.gnu.org/licenses/old-licenses/gpl-2.0.html * + * or contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * + ********************************************************************/ +#include "config.h" + +// GoogleTest is written in C++, however, the function we test in C. +extern "C" { +#include "../gnc-ui-balances.h" +} +#include +#include +#include + +static const int64_t DENOM = 100; //< Denominator is always 100 for simplicity. + +struct SplitDatum { + const char *memo; + gint64 amount; //< Numerator of amount. + bool cleared; +}; + +struct Tests { + gint64 amount; + const char *expectedErr; +}; + +struct TestCase { + std::vector splits; + std::vector tests; +}; + +TestCase easyTestCase = { + .splits = { + { "Memo 01", - 8234, true }, + { "Memo 02", -156326, true }, + { "Memo 03", - 4500, true }, + { "Memo 04", -694056, true }, + { "Memo 05", - 7358, true }, + { "Memo 06", - 11700, true }, + { "Memo 07", - 20497, true }, + { "Memo 08", - 11900, true }, + { "Memo 09", - 8275, true }, + { "Memo 10", - 58700, true }, + { "Memo 11", +100000, true }, + { "Memo 12", - 13881, true }, + { "Memo 13", - 5000, true }, + { "Memo 14", +200000, true }, + { "Memo 15", - 16800, true }, + { "Memo 16", -152000, true }, + { "Memo 17", +160000, false }, + { "Memo 18", - 63610, false }, + { "Memo 19", - 2702, false }, + { "Memo 20", - 15400, false }, + { "Memo 21", - 3900, false }, + { "Memo 22", - 22042, false }, + { "Memo 23", - 2900, false }, + { "Memo 24", - 10900, false }, + { "Memo 25", - 44400, false }, + { "Memo 26", - 9200, false }, + { "Memo 27", - 7900, false }, + { "Memo 28", - 1990, false }, + { "Memo 29", - 7901, false }, + { "Memo 30", - 61200, false }, + }, + .tests = { + { 0, "The selected amount cannot be cleared.", }, + { -869227, "Account is already at Auto-Clear Balance." }, // No splits need to be cleared. + { -869300, "The selected amount cannot be cleared." }, + { -869230, NULL }, + { -963272, NULL }, // All splits need to be cleared. + }, +}; + +TestCase ambiguousTestCase = { + .splits = { + { "Memo 01", -10, false }, + { "Memo 02", -10, false }, + { "Memo 03", -10, false }, + }, + .tests = { + { -10, "Cannot uniquely clear splits. Found multiple possibilities." }, + { -20, "Cannot uniquely clear splits. Found multiple possibilities." }, + + // Forbid auto-clear to be too smart. We expect the user to manually deal + // with such situations. + { -30, "Cannot uniquely clear splits. Found multiple possibilities." }, + }, +}; + +class AutoClearTest : public ::testing::TestWithParam { +protected: + std::shared_ptr m_book; + Account *m_account; // owned by m_book + TestCase &m_testCase; + +public: + AutoClearTest() : + m_book(qof_book_new(), qof_book_destroy), + m_account(xaccMallocAccount(m_book.get())), + m_testCase(*GetParam()) + { + xaccAccountSetName(m_account, "Test Account"); + xaccAccountBeginEdit(m_account); + for (auto &d : m_testCase.splits) { + Split *split = xaccMallocSplit(m_book.get()); + xaccSplitSetMemo(split, d.memo); + xaccSplitSetAmount(split, gnc_numeric_create(d.amount, DENOM)); + xaccSplitSetReconcile(split, d.cleared ? CREC : NREC); + xaccSplitSetAccount(split, m_account); + + gnc_account_insert_split(m_account, split); + } + xaccAccountCommitEdit(m_account); + } +}; + +TEST_P(AutoClearTest, DoesAutoClear) { + for (auto &t : m_testCase.tests) { + gnc_numeric amount_to_clear = gnc_numeric_create(t.amount, DENOM); + char *err; + + GList *splits_to_clear = gnc_account_get_autoclear_splits(m_account, amount_to_clear, &err); + + // Actually clear splits + for (GList *node = splits_to_clear; node; node = node->next) { + Split *split = (Split *)node->data; + xaccSplitSetReconcile(split, CREC); + } + + ASSERT_STREQ(err, t.expectedErr); + if (t.expectedErr == NULL) { + gnc_numeric c = xaccAccountGetClearedBalance(m_account); + ASSERT_EQ(c.num, t.amount); + ASSERT_EQ(c.denom, DENOM); + } + } +} + +#ifndef INSTANTIATE_TEST_SUITE_P +// Silence "no previous declaration for" which is treated as error, due to -Werror +testing::internal::ParamGenerator gtest_InstantiationAutoClearTestAutoClearTest_EvalGenerator_(); +std::string gtest_InstantiationAutoClearTestAutoClearTest_EvalGenerateName_(const testing::TestParamInfo&); + +INSTANTIATE_TEST_CASE_P( +#else // INSTANTIATE_TEST_SUITE_P +INSTANTIATE_TEST_SUITE_P( +#endif // INSTANTIATE_TEST_SUITE_P + InstantiationAutoClearTest, + AutoClearTest, + ::testing::Values( + &easyTestCase, + &ambiguousTestCase + ) +);