The -B, -G, -V reports now show rounding amounts
This way, if the running total is off by a penny or two due to rounding of one or more commodities in the account, the user will see it. This commit also reorganizes the testing code a bit, which I did after adding the ninth test series (ConfirmTests), to validate the new rounding code.
This commit is contained in:
parent
74e569e220
commit
d58797e98c
22 changed files with 294 additions and 423 deletions
94
Makefile.am
94
Makefile.am
|
|
@ -240,16 +240,16 @@ endif
|
|||
TESTS =
|
||||
|
||||
if HAVE_PYTHON
|
||||
TESTS += RegressionTests BaselineTests
|
||||
TESTS += RegressTests BaselineTests ConfirmTests
|
||||
endif
|
||||
|
||||
if HAVE_CPPUNIT
|
||||
TESTS += \
|
||||
util_tests \
|
||||
math_tests \
|
||||
expr_tests \
|
||||
data_tests \
|
||||
report_tests
|
||||
UtilTests \
|
||||
MathTests \
|
||||
ExprTests \
|
||||
DataTests \
|
||||
ReportTests
|
||||
endif
|
||||
|
||||
if HAVE_BOOST_PYTHON
|
||||
|
|
@ -258,22 +258,22 @@ endif
|
|||
|
||||
check_PROGRAMS = $(TESTS)
|
||||
|
||||
util_tests_SOURCES = \
|
||||
UtilTests_SOURCES = \
|
||||
test/UnitTests.cc \
|
||||
test/UnitTests.h \
|
||||
test/util_tests.cc \
|
||||
test/UtilTests.cc \
|
||||
test/unit/t_utils.cc \
|
||||
test/unit/t_utils.h \
|
||||
test/unit/t_times.cc \
|
||||
test/unit/t_times.h
|
||||
|
||||
util_tests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags)
|
||||
util_tests_LDADD = libledger_util.la -lcppunit
|
||||
UtilTests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags)
|
||||
UtilTests_LDADD = libledger_util.la -lcppunit
|
||||
|
||||
math_tests_SOURCES = \
|
||||
MathTests_SOURCES = \
|
||||
test/UnitTests.cc \
|
||||
test/UnitTests.h \
|
||||
test/math_tests.cc \
|
||||
test/MathTests.cc \
|
||||
test/unit/t_commodity.cc \
|
||||
test/unit/t_commodity.h \
|
||||
test/unit/t_amount.cc \
|
||||
|
|
@ -281,41 +281,41 @@ math_tests_SOURCES = \
|
|||
test/unit/t_balance.cc \
|
||||
test/unit/t_balance.h
|
||||
|
||||
math_tests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags)
|
||||
math_tests_LDADD = libledger_math.la $(util_tests_LDADD)
|
||||
MathTests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags)
|
||||
MathTests_LDADD = libledger_math.la $(UtilTests_LDADD)
|
||||
|
||||
expr_tests_SOURCES = \
|
||||
ExprTests_SOURCES = \
|
||||
test/UnitTests.cc \
|
||||
test/UnitTests.h \
|
||||
test/expr_tests.cc \
|
||||
test/ExprTests.cc \
|
||||
test/unit/t_expr.cc \
|
||||
test/unit/t_expr.h
|
||||
|
||||
expr_tests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags)
|
||||
expr_tests_LDADD = libledger_expr.la $(math_tests_LDADD)
|
||||
ExprTests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags)
|
||||
ExprTests_LDADD = libledger_expr.la $(MathTests_LDADD)
|
||||
|
||||
data_tests_SOURCES = \
|
||||
DataTests_SOURCES = \
|
||||
test/UnitTests.cc \
|
||||
test/UnitTests.h \
|
||||
test/data_tests.cc
|
||||
test/DataTests.cc
|
||||
|
||||
data_tests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags)
|
||||
data_tests_LDADD = libledger_data.la $(expr_tests_LDADD)
|
||||
DataTests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags)
|
||||
DataTests_LDADD = libledger_data.la $(ExprTests_LDADD)
|
||||
|
||||
report_tests_SOURCES = \
|
||||
ReportTests_SOURCES = \
|
||||
test/UnitTests.cc \
|
||||
test/UnitTests.h \
|
||||
test/report_tests.cc
|
||||
test/ReportTests.cc
|
||||
|
||||
report_tests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags)
|
||||
report_tests_LDADD = libledger_report.la $(data_tests_LDADD)
|
||||
ReportTests_CPPFLAGS = -I$(srcdir)/test $(lib_cppflags)
|
||||
ReportTests_LDADD = libledger_report.la $(DataTests_LDADD)
|
||||
|
||||
all_tests_sources = \
|
||||
$(util_tests_SOURCES) \
|
||||
$(math_tests_SOURCES) \
|
||||
$(expr_tests_SOURCES) \
|
||||
$(data_tests_SOURCES) \
|
||||
$(report_tests_SOURCES)
|
||||
$(UtilTests_SOURCES) \
|
||||
$(MathTests_SOURCES) \
|
||||
$(ExprTests_SOURCES) \
|
||||
$(DataTests_SOURCES) \
|
||||
$(ReportTests_SOURCES)
|
||||
|
||||
PyUnitTests_SOURCES = test/PyUnitTests.py
|
||||
|
||||
|
|
@ -358,31 +358,39 @@ PyUnitTests: $(srcdir)/test/PyUnitTests.py \
|
|||
| sed "s/%builddir%/$(ESC_builddir)/g" > $@
|
||||
chmod 755 $@
|
||||
|
||||
RegressionTests_SOURCES = test/regress.py
|
||||
RegressTests_SOURCES = test/RegressTests.py
|
||||
|
||||
EXTRA_DIST += test/regress test/convert.py
|
||||
|
||||
RegressionTests: $(srcdir)/test/regress.py
|
||||
echo "$(PYTHON) $(srcdir)/test/regress.py $(top_builddir)/ledger$(EXEEXT) $(srcdir)/test/regress" > $@
|
||||
RegressTests: $(srcdir)/test/RegressTests.py
|
||||
echo "$(PYTHON) $(srcdir)/test/RegressTests.py $(top_builddir)/ledger$(EXEEXT) $(srcdir)/test/regress" > $@
|
||||
chmod 755 $@
|
||||
|
||||
BaselineTests_SOURCES = test/regress.py
|
||||
BaselineTests_SOURCES = test/RegressTests.py
|
||||
|
||||
EXTRA_DIST += test/baseline
|
||||
|
||||
BaselineTests: $(srcdir)/test/regress.py
|
||||
echo "$(PYTHON) $(srcdir)/test/regress.py $(top_builddir)/ledger$(EXEEXT) $(srcdir)/test/baseline" > $@
|
||||
BaselineTests: $(srcdir)/test/RegressTests.py
|
||||
echo "$(PYTHON) $(srcdir)/test/RegressTests.py $(top_builddir)/ledger$(EXEEXT) $(srcdir)/test/baseline" > $@
|
||||
chmod 755 $@
|
||||
|
||||
FULLCHECK=$(srcdir)/tools/fullcheck
|
||||
ConfirmTests_SOURCES = test/ConfirmTests.py
|
||||
|
||||
EXTRA_DIST += test/input
|
||||
|
||||
ConfirmTests: $(srcdir)/test/ConfirmTests.py
|
||||
echo "$(PYTHON) $(srcdir)/test/ConfirmTests.py $(top_builddir)/ledger$(EXEEXT) $(srcdir)/test/input" > $@
|
||||
chmod 755 $@
|
||||
|
||||
FULLCHECK=$(srcdir)/test/fullcheck.sh
|
||||
|
||||
if HAVE_CPPUNIT
|
||||
fullcheck: $(TESTS)
|
||||
sh $(FULLCHECK) $(top_builddir)/util_tests$(EXEEXT) --verify
|
||||
sh $(FULLCHECK) $(top_builddir)/math_tests$(EXEEXT) --verify
|
||||
sh $(FULLCHECK) $(top_builddir)/expr_tests$(EXEEXT) --verify
|
||||
sh $(FULLCHECK) $(top_builddir)/data_tests$(EXEEXT) --verify
|
||||
sh $(FULLCHECK) $(top_builddir)/report_tests$(EXEEXT) --verify
|
||||
sh $(FULLCHECK) $(top_builddir)/UtilTests$(EXEEXT) --verify
|
||||
sh $(FULLCHECK) $(top_builddir)/MathTests$(EXEEXT) --verify
|
||||
sh $(FULLCHECK) $(top_builddir)/ExprTests$(EXEEXT) --verify
|
||||
sh $(FULLCHECK) $(top_builddir)/DataTests$(EXEEXT) --verify
|
||||
sh $(FULLCHECK) $(top_builddir)/ReportTests$(EXEEXT) --verify
|
||||
else
|
||||
fullcheck: check
|
||||
@test 1 -eq 1
|
||||
|
|
|
|||
|
|
@ -196,6 +196,10 @@ namespace {
|
|||
return long(account.depth);
|
||||
}
|
||||
|
||||
value_t ignore(account_t&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value_t get_depth_spacer(account_t& account)
|
||||
{
|
||||
std::size_t depth = 0;
|
||||
|
|
@ -259,6 +263,11 @@ expr_t::ptr_op_t account_t::lookup(const string& name)
|
|||
if (name == "total")
|
||||
return WRAP_FUNCTOR(get_wrapper<&get_total>);
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
if (name == "use_direct_amount")
|
||||
return WRAP_FUNCTOR(get_wrapper<&ignore>);
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
|
|
|||
|
|
@ -79,9 +79,12 @@ post_handler_ptr chain_post_handlers(report_t& report,
|
|||
// the running total unpredictably.
|
||||
if (report.HANDLED(revalued))
|
||||
handler.reset(new changed_value_posts
|
||||
(handler, report.HANDLED(revalued_total_) ?
|
||||
(handler,
|
||||
report.HANDLER(display_amount_).expr,
|
||||
report.HANDLED(revalued_total_) ?
|
||||
report.HANDLER(revalued_total_).expr :
|
||||
report.HANDLER(display_total_).expr,
|
||||
report.HANDLER(display_total_).expr,
|
||||
report, report.HANDLED(revalued_only)));
|
||||
|
||||
// calc_posts computes the running total. When this appears will
|
||||
|
|
|
|||
112
src/filters.cc
112
src/filters.cc
|
|
@ -213,14 +213,17 @@ void calc_posts::operator()(post_t& post)
|
|||
}
|
||||
|
||||
namespace {
|
||||
void handle_value(const value_t& value,
|
||||
account_t * account,
|
||||
xact_t * xact,
|
||||
unsigned int flags,
|
||||
std::list<post_t>& temps,
|
||||
item_handler<post_t>& handler,
|
||||
const date_t& date = date_t(),
|
||||
const value_t& total = value_t())
|
||||
typedef function<void (post_t *)> post_functor_t;
|
||||
|
||||
void handle_value(const value_t& value,
|
||||
account_t * account,
|
||||
xact_t * xact,
|
||||
std::list<post_t>& temps,
|
||||
item_handler<post_t>& handler,
|
||||
const date_t& date = date_t(),
|
||||
const value_t& total = value_t(),
|
||||
const bool direct_amount = false,
|
||||
const optional<post_functor_t>& functor = none)
|
||||
{
|
||||
temps.push_back(post_t(account));
|
||||
post_t& post(temps.back());
|
||||
|
|
@ -259,7 +262,7 @@ namespace {
|
|||
case value_t::BALANCE:
|
||||
case value_t::SEQUENCE:
|
||||
xdata.value = temp;
|
||||
flags |= POST_EXT_COMPOUND;
|
||||
xdata.add_flags(POST_EXT_COMPOUND);
|
||||
break;
|
||||
|
||||
case value_t::DATETIME:
|
||||
|
|
@ -272,8 +275,13 @@ namespace {
|
|||
if (! total.is_null())
|
||||
xdata.total = total;
|
||||
|
||||
if (flags)
|
||||
xdata.add_flags(flags);
|
||||
if (direct_amount)
|
||||
xdata.add_flags(POST_EXT_DIRECT_AMT);
|
||||
|
||||
if (functor)
|
||||
(*functor)(&post);
|
||||
|
||||
DEBUG("filter.changed_value.rounding", "post.amount = " << post.amount);
|
||||
|
||||
handler(post);
|
||||
}
|
||||
|
|
@ -314,7 +322,7 @@ void collapse_posts::report_subtotal()
|
|||
earliest_date : last_xact->_date);
|
||||
DEBUG("filter.collapse", "Pseudo-xact date = " << *xact._date);
|
||||
|
||||
handle_value(subtotal, &totals_account, &xact, 0, post_temps, *handler);
|
||||
handle_value(subtotal, &totals_account, &xact, post_temps, *handler);
|
||||
}
|
||||
|
||||
component_posts.clear();
|
||||
|
|
@ -374,7 +382,7 @@ void related_posts::flush()
|
|||
item_handler<post_t>::flush();
|
||||
}
|
||||
|
||||
void changed_value_posts::output_diff(post_t * post, const date_t& date)
|
||||
void changed_value_posts::output_revaluation(post_t * post, const date_t& date)
|
||||
{
|
||||
if (is_valid(date))
|
||||
post->xdata().date = date;
|
||||
|
|
@ -391,36 +399,80 @@ void changed_value_posts::output_diff(post_t * post, const date_t& date)
|
|||
post->xdata().date = date_t();
|
||||
|
||||
DEBUG("filter.changed_value",
|
||||
"output_diff(last_balance) = " << last_balance);
|
||||
"output_revaluation(last_balance) = " << last_total);
|
||||
DEBUG("filter.changed_value",
|
||||
"output_diff(repriced_total) = " << repriced_total);
|
||||
"output_revaluation(repriced_total) = " << repriced_total);
|
||||
|
||||
if (value_t diff = repriced_total - last_balance) {
|
||||
DEBUG("filter.changed_value", "output_diff(strip(diff)) = "
|
||||
<< diff.strip_annotations(report.what_to_keep()));
|
||||
if (! last_total.is_null()) {
|
||||
if (value_t diff = repriced_total - last_total) {
|
||||
DEBUG("filter.changed_value", "output_revaluation(strip(diff)) = "
|
||||
<< diff.strip_annotations(report.what_to_keep()));
|
||||
|
||||
xact_temps.push_back(xact_t());
|
||||
xact_t& xact = xact_temps.back();
|
||||
xact.payee = _("Commodities revalued");
|
||||
xact._date = is_valid(date) ? date : post->date();
|
||||
xact_temps.push_back(xact_t());
|
||||
xact_t& xact = xact_temps.back();
|
||||
xact.payee = _("Commodities revalued");
|
||||
xact._date = is_valid(date) ? date : post->date();
|
||||
|
||||
handle_value(diff, &revalued_account, &xact, POST_EXT_NO_TOTAL,
|
||||
post_temps, *handler, *xact._date, repriced_total);
|
||||
handle_value(diff, &revalued_account, &xact, post_temps, *handler,
|
||||
*xact._date, repriced_total, false,
|
||||
optional<post_functor_t>
|
||||
(bind(&changed_value_posts::output_rounding, this, _1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void changed_value_posts::output_rounding(post_t * post)
|
||||
{
|
||||
bind_scope_t bound_scope(report, *post);
|
||||
value_t new_display_total(display_total_expr.calc(bound_scope));
|
||||
|
||||
DEBUG("filter.changed_value.rounding",
|
||||
"rounding.new_display_total = " << new_display_total);
|
||||
|
||||
if (! last_display_total.is_null()) {
|
||||
if (value_t repriced_amount = display_amount_expr.calc(bound_scope)) {
|
||||
DEBUG("filter.changed_value.rounding",
|
||||
"rounding.repriced_amount = " << repriced_amount);
|
||||
|
||||
value_t precise_display_total(new_display_total.truncated() -
|
||||
repriced_amount.truncated());
|
||||
|
||||
DEBUG("filter.changed_value.rounding",
|
||||
"rounding.precise_display_total = " << precise_display_total);
|
||||
DEBUG("filter.changed_value.rounding",
|
||||
"rounding.last_display_total = " << last_display_total);
|
||||
|
||||
if (value_t diff = precise_display_total - last_display_total) {
|
||||
DEBUG("filter.changed_value.rounding",
|
||||
"rounding.diff = " << diff);
|
||||
|
||||
xact_temps.push_back(xact_t());
|
||||
xact_t& xact = xact_temps.back();
|
||||
xact.payee = _("Commodity rounding");
|
||||
xact._date = post->date();
|
||||
|
||||
handle_value(diff, &rounding_account, &xact, post_temps,
|
||||
*handler, *xact._date, precise_display_total, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
last_display_total = new_display_total;
|
||||
}
|
||||
|
||||
void changed_value_posts::operator()(post_t& post)
|
||||
{
|
||||
if (last_post)
|
||||
output_diff(last_post, post.date());
|
||||
output_revaluation(last_post, post.date());
|
||||
|
||||
if (changed_values_only)
|
||||
post.xdata().add_flags(POST_EXT_DISPLAYED);
|
||||
|
||||
output_rounding(&post);
|
||||
|
||||
item_handler<post_t>::operator()(post);
|
||||
|
||||
bind_scope_t bound_scope(report, post);
|
||||
last_balance = total_expr.calc(bound_scope);
|
||||
last_total = total_expr.calc(bound_scope);
|
||||
|
||||
last_post = &post;
|
||||
}
|
||||
|
|
@ -462,8 +514,8 @@ void subtotal_posts::report_subtotal(const char * spec_fmt,
|
|||
xact._date = range_start;
|
||||
|
||||
foreach (values_map::value_type& pair, values)
|
||||
handle_value(pair.second.value, pair.second.account, &xact, 0,
|
||||
post_temps, *handler);
|
||||
handle_value(pair.second.value, pair.second.account, &xact, post_temps,
|
||||
*handler);
|
||||
|
||||
values.clear();
|
||||
}
|
||||
|
|
@ -581,8 +633,8 @@ void posts_as_equity::report_subtotal()
|
|||
|
||||
value_t total = 0L;
|
||||
foreach (values_map::value_type& pair, values) {
|
||||
handle_value(pair.second.value, pair.second.account, &xact, 0,
|
||||
post_temps, *handler);
|
||||
handle_value(pair.second.value, pair.second.account, &xact, post_temps,
|
||||
*handler);
|
||||
total += pair.second.value;
|
||||
}
|
||||
values.clear();
|
||||
|
|
|
|||
|
|
@ -449,12 +449,16 @@ class changed_value_posts : public item_handler<post_t>
|
|||
// This filter requires that calc_posts be used at some point
|
||||
// later in the chain.
|
||||
|
||||
expr_t display_amount_expr;
|
||||
expr_t total_expr;
|
||||
expr_t display_total_expr;
|
||||
report_t& report;
|
||||
bool changed_values_only;
|
||||
post_t * last_post;
|
||||
value_t last_balance;
|
||||
value_t last_total;
|
||||
value_t last_display_total;
|
||||
account_t revalued_account;
|
||||
account_t rounding_account;
|
||||
|
||||
std::list<xact_t> xact_temps;
|
||||
std::list<post_t> post_temps;
|
||||
|
|
@ -463,14 +467,19 @@ class changed_value_posts : public item_handler<post_t>
|
|||
|
||||
public:
|
||||
changed_value_posts(post_handler_ptr handler,
|
||||
const expr_t& _display_amount_expr,
|
||||
const expr_t& _total_expr,
|
||||
const expr_t& _display_total_expr,
|
||||
report_t& _report,
|
||||
bool _changed_values_only)
|
||||
: item_handler<post_t>(handler), total_expr(_total_expr),
|
||||
report(_report), changed_values_only(_changed_values_only),
|
||||
last_post(NULL), revalued_account(NULL, _("<Revalued>")) {
|
||||
: item_handler<post_t>(handler),
|
||||
display_amount_expr(_display_amount_expr), total_expr(_total_expr),
|
||||
display_total_expr(_display_total_expr), report(_report),
|
||||
changed_values_only(_changed_values_only), last_post(NULL),
|
||||
revalued_account(NULL, _("<Revalued>")),
|
||||
rounding_account(NULL, _("<Rounding>")){
|
||||
TRACE_CTOR(changed_value_posts,
|
||||
"post_handler_ptr, const expr_t&, report_t&, bool");
|
||||
"post_handler_ptr, const expr_t&, const expr_t&, report_t&, bool");
|
||||
}
|
||||
virtual ~changed_value_posts() {
|
||||
TRACE_DTOR(changed_value_posts);
|
||||
|
|
@ -479,13 +488,14 @@ public:
|
|||
|
||||
virtual void flush() {
|
||||
if (last_post && last_post->date() <= CURRENT_DATE()) {
|
||||
output_diff(last_post, CURRENT_DATE());
|
||||
output_revaluation(last_post, CURRENT_DATE());
|
||||
last_post = NULL;
|
||||
}
|
||||
item_handler<post_t>::flush();
|
||||
}
|
||||
|
||||
void output_diff(post_t * post, const date_t& current);
|
||||
void output_revaluation(post_t * post, const date_t& current);
|
||||
void output_rounding(post_t * post);
|
||||
|
||||
virtual void operator()(post_t& post);
|
||||
};
|
||||
|
|
|
|||
12
src/post.cc
12
src/post.cc
|
|
@ -158,6 +158,10 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
value_t get_use_direct_amount(post_t& post) {
|
||||
return post.has_xdata() && post.xdata().has_flags(POST_EXT_DIRECT_AMT);
|
||||
}
|
||||
|
||||
value_t get_commodity(post_t& post) {
|
||||
return string_value(post.amount.commodity().symbol());
|
||||
}
|
||||
|
|
@ -282,6 +286,11 @@ expr_t::ptr_op_t post_t::lookup(const string& name)
|
|||
return WRAP_FUNCTOR(get_wrapper<&get_total>);
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
if (name == "use_direct_amount")
|
||||
return WRAP_FUNCTOR(get_wrapper<&get_use_direct_amount>);
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
if (name == "virtual")
|
||||
return WRAP_FUNCTOR(get_wrapper<&get_virtual>);
|
||||
|
|
@ -333,8 +342,7 @@ void post_t::add_to_value(value_t& value, expr_t& expr)
|
|||
{
|
||||
if (xdata_ && xdata_->has_flags(POST_EXT_COMPOUND)) {
|
||||
add_or_set_value(value, xdata_->value);
|
||||
}
|
||||
else if (! xdata_ || ! xdata_->has_flags(POST_EXT_NO_TOTAL)) {
|
||||
} else {
|
||||
bind_scope_t bound_scope(*expr.get_context(), *this);
|
||||
add_or_set_value(value, expr.calc(bound_scope));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ public:
|
|||
#define POST_EXT_HANDLED 0x02
|
||||
#define POST_EXT_TO_DISPLAY 0x04
|
||||
#define POST_EXT_DISPLAYED 0x08
|
||||
#define POST_EXT_NO_TOTAL 0x10
|
||||
#define POST_EXT_DIRECT_AMT 0x10
|
||||
#define POST_EXT_SORT_CALC 0x20
|
||||
#define POST_EXT_COMPOUND 0x40
|
||||
#define POST_EXT_MATCHES 0x80
|
||||
|
|
|
|||
14
src/report.h
14
src/report.h
|
|
@ -245,7 +245,7 @@ public:
|
|||
OPTION(report_t, base);
|
||||
|
||||
OPTION_(report_t, basis, DO() { // -B
|
||||
parent->HANDLER(revalued).off();
|
||||
parent->HANDLER(revalued).on_only();
|
||||
parent->HANDLER(amount_).set_expr("rounded(cost)");
|
||||
});
|
||||
|
||||
|
|
@ -401,15 +401,17 @@ public:
|
|||
// Since we are displaying the amounts of revalued postings, they
|
||||
// will end up being composite totals, and hence a pair of pairs.
|
||||
parent->HANDLER(display_amount_)
|
||||
.set_expr("is_seq(get_at(amount_expr, 0)) ?"
|
||||
" get_at(get_at(amount_expr, 0), 0) :"
|
||||
" market(get_at(amount_expr, 0), date, exchange) -"
|
||||
" get_at(amount_expr, 1)");
|
||||
.set_expr("use_direct_amount ? amount :"
|
||||
" (is_seq(get_at(amount_expr, 0)) ?"
|
||||
" get_at(get_at(amount_expr, 0), 0) :"
|
||||
" market(get_at(amount_expr, 0), date, exchange)"
|
||||
" - get_at(amount_expr, 1))");
|
||||
parent->HANDLER(revalued_total_)
|
||||
.set_expr("(market(get_at(total_expr, 0), date, exchange), "
|
||||
"get_at(total_expr, 1))");
|
||||
parent->HANDLER(display_total_)
|
||||
.set_expr("market(get_at(total_expr, 0), date, exchange)"
|
||||
.set_expr("use_direct_amount ? total_expr :"
|
||||
" market(get_at(total_expr, 0), date, exchange)"
|
||||
" - get_at(total_expr, 1)");
|
||||
});
|
||||
|
||||
|
|
|
|||
110
test/ConfirmTests.py
Executable file
110
test/ConfirmTests.py
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# This script confirms both that the register report "adds up", and that its
|
||||
# final balance is the same as what the balance report shows.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
commands = [
|
||||
"-f '$tests/standard.dat' -O 0ecbb1b15e2cf3e515cc0f8533e5bb0fb2326728",
|
||||
"-f '$tests/standard.dat' -B c56a21d23a6535184e7152ee138c28974f14280c",
|
||||
"-f '$tests/standard.dat' -V c56a21d23a6535184e7152ee138c28974f14280c",
|
||||
"-f '$tests/standard.dat' -G c56a21d23a6535184e7152ee138c28974f14280c",
|
||||
"-f '$tests/standard.dat' -B c0226fafdf9e6711ac9121cf263e2d50791859cb",
|
||||
"-f '$tests/standard.dat' -V c0226fafdf9e6711ac9121cf263e2d50791859cb",
|
||||
"-f '$tests/standard.dat' -G c0226fafdf9e6711ac9121cf263e2d50791859cb"
|
||||
]
|
||||
|
||||
ledger = sys.argv[1]
|
||||
tests = sys.argv[2]
|
||||
succeeded = 0
|
||||
failed = 0
|
||||
|
||||
if not os.path.isfile(ledger):
|
||||
sys.exit(1)
|
||||
if not os.path.isdir(tests) and not os.path.isfile(tests):
|
||||
sys.exit(1)
|
||||
|
||||
def clean(num):
|
||||
num = re.sub("(\s+|\$|,)","", num)
|
||||
m = re.search("([-0-9.]+)", num)
|
||||
if m:
|
||||
return float(m.group(1))
|
||||
else:
|
||||
return float(num)
|
||||
|
||||
def confirm_report(args):
|
||||
index = 1
|
||||
last_line = ""
|
||||
failure = False
|
||||
running_total = 0.0
|
||||
|
||||
p = Popen(re.sub('\$cmd', 'reg', args), shell=True,
|
||||
stdin=PIPE, stdout=PIPE, stderr=PIPE,
|
||||
close_fds=True)
|
||||
|
||||
for line in p.stdout.readlines():
|
||||
match = re.match("\\s*([-$,0-9.]+)\\s+([-$,0-9.]+)", line[54:])
|
||||
if not match:
|
||||
continue
|
||||
value = clean(match.group(1))
|
||||
total = clean(match.group(2))
|
||||
|
||||
running_total += value
|
||||
diff = abs(running_total - total)
|
||||
if re.search(' -[VGB] ', args) and diff < 0.015:
|
||||
diff = 0.0
|
||||
if diff > 0.001:
|
||||
print "DISCREPANCY: %.3f (%.3f - %.3f) at line %d:" % \
|
||||
(running_total - total, running_total, total, index)
|
||||
print line,
|
||||
running_total = total
|
||||
failure = True
|
||||
|
||||
index += 1
|
||||
last_line = line
|
||||
|
||||
balance_total = 0.0
|
||||
|
||||
p = Popen(re.sub('\$cmd', 'bal', args), shell=True,
|
||||
stdin=PIPE, stdout=PIPE, stderr=PIPE,
|
||||
close_fds=True)
|
||||
|
||||
for line in p.stdout.readlines():
|
||||
if line[0] != '-':
|
||||
balance_total = clean(line[:20])
|
||||
|
||||
diff = abs(balance_total - running_total)
|
||||
if re.search(' -[VGB] ', args) and diff < 0.015:
|
||||
diff = 0.0
|
||||
if diff > 0.001:
|
||||
print
|
||||
print "DISCREPANCY: %.3f (%.3f - %.3f) between register and balance" % \
|
||||
(balance_total - running_total, balance_total, running_total)
|
||||
print last_line,
|
||||
failure = True
|
||||
|
||||
return not failure
|
||||
|
||||
for cmd in commands:
|
||||
if confirm_report("%s --args-only --columns=80 %s" %
|
||||
(ledger, re.sub('\$tests', tests, cmd))):
|
||||
print ".",
|
||||
succeeded += 1
|
||||
else:
|
||||
print "E",
|
||||
failed += 1
|
||||
|
||||
print
|
||||
if succeeded > 0:
|
||||
print "OK (%d) " % succeeded,
|
||||
if failed > 0:
|
||||
print "FAILED (%d)" % failed,
|
||||
print
|
||||
|
||||
sys.exit(failed)
|
||||
|
||||
|
|
@ -125,6 +125,8 @@ def test_regression(test_file):
|
|||
if success:
|
||||
succeeded += 1
|
||||
print ".",
|
||||
else:
|
||||
print "E",
|
||||
|
||||
if not use_stdin:
|
||||
os.remove(tempdata[1])
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
CPPUNIT_REGISTRY_ADD_TO_DEFAULT("extra");
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# This script confirms what ledger tells you.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
def clean(num):
|
||||
num = re.sub("(\s+|\$|,)","", num)
|
||||
m = re.search("([-0-9.]+)", num)
|
||||
if m:
|
||||
return float(m.group(1))
|
||||
else:
|
||||
return float(num)
|
||||
|
||||
running_total = 0.0
|
||||
index = 1
|
||||
last_line = ""
|
||||
errors = 0
|
||||
|
||||
args = sys.argv[1]
|
||||
for line in os.popen(re.sub('\$cmd', 'reg', args)):
|
||||
match = re.match("\\s*([-$,0-9.]+)\\s+([-$,0-9.]+)", line[54:])
|
||||
if not match:
|
||||
continue
|
||||
value = clean(match.group(1))
|
||||
total = clean(match.group(2))
|
||||
|
||||
running_total += value
|
||||
diff = abs(running_total - total)
|
||||
if re.search(' -[VGB] ', args) and diff < 0.015:
|
||||
diff = 0.0
|
||||
if diff > 0.001:
|
||||
print "DISCREPANCY: %.3f (%.3f - %.3f) at line %d:" % \
|
||||
(running_total - total, running_total, total, index)
|
||||
print line,
|
||||
running_total = total
|
||||
errors += 1
|
||||
|
||||
index += 1
|
||||
last_line = line
|
||||
|
||||
balance_total = 0.0
|
||||
|
||||
for line in os.popen(re.sub('\$cmd', 'bal', args)):
|
||||
if line[0] != '-':
|
||||
balance_total = clean(line[:20])
|
||||
|
||||
diff = abs(balance_total - running_total)
|
||||
if re.search(' -[VGB] ', args) and diff < 0.015:
|
||||
diff = 0.0
|
||||
if diff > 0.001:
|
||||
print
|
||||
print "DISCREPANCY: %.3f (%.3f - %.3f) between register and balance" % \
|
||||
(balance_total - running_total, balance_total, running_total)
|
||||
print last_line,
|
||||
errors += 1
|
||||
|
||||
sys.exit(errors)
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
errors=0
|
||||
|
||||
for test in \
|
||||
"-O \$cmd 0ecbb1b15e2cf3e515cc0f8533e5bb0fb2326728" \
|
||||
"-B \$cmd c56a21d23a6535184e7152ee138c28974f14280c" \
|
||||
"-V \$cmd c56a21d23a6535184e7152ee138c28974f14280c" \
|
||||
"-G \$cmd c56a21d23a6535184e7152ee138c28974f14280c" \
|
||||
"-B \$cmd c0226fafdf9e6711ac9121cf263e2d50791859cb" \
|
||||
"-V \$cmd c0226fafdf9e6711ac9121cf263e2d50791859cb" \
|
||||
"-G \$cmd c0226fafdf9e6711ac9121cf263e2d50791859cb"
|
||||
do
|
||||
echo testing: $test
|
||||
python tools/confirm.py "ledger -f tools/standard.dat --args-only --columns=80 $test"
|
||||
errors=`expr $errors + $?`
|
||||
done
|
||||
|
||||
if [ $errors = 0 ]; then
|
||||
echo All tests completed successfully.
|
||||
fi
|
||||
exit $errors
|
||||
248
tools/template.h
248
tools/template.h
|
|
@ -1,248 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2003-2009, John Wiegley. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of New Artisans LLC nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup the_group Group Title
|
||||
* @addtogroup the_group
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file template.h
|
||||
* @author John Wiegley
|
||||
*
|
||||
* @ingroup the_group
|
||||
*
|
||||
* @brief Brief description
|
||||
*
|
||||
* Full description.
|
||||
*/
|
||||
#ifndef _TEMPLATE_H
|
||||
#define _TEMPLATE_H
|
||||
|
||||
namespace template {
|
||||
|
||||
/**
|
||||
* @brief Brief description
|
||||
*
|
||||
* Full description.
|
||||
*/
|
||||
class template_t : public ordered_field_operators<template_t>
|
||||
#if 0
|
||||
public boost::noncopyable
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @name Class statics
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
static bool stream_fullstrings;
|
||||
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
* @name Constructors
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
/**
|
||||
* @see other_function
|
||||
* @warning Watch out!
|
||||
*/
|
||||
template_t() {
|
||||
TRACE_CTOR(template_t, "");
|
||||
}
|
||||
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
* @name Destructor
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
~template_t() {
|
||||
TRACE_DTOR(template_t);
|
||||
}
|
||||
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
* @name Assignment and copy
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
template_t(const template_t& other) {
|
||||
TRACE_CTOR(template_t, "copy");
|
||||
}
|
||||
|
||||
template_t& operator=(const template_t& other);
|
||||
|
||||
/*@}*/
|
||||
|
||||
private:
|
||||
template_t(const template_t& other);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @name Comparison
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
/**
|
||||
* Compare two template objects.
|
||||
* @param other
|
||||
* @return An integer <0, 0 or >0.
|
||||
*/
|
||||
int compare(const template_t& other) const;
|
||||
|
||||
template <typename T>
|
||||
bool operator==(const T& val) const {
|
||||
return compare(val) == 0;
|
||||
}
|
||||
template <typename T>
|
||||
bool operator<(const T& other) const {
|
||||
return compare(other) < 0;
|
||||
}
|
||||
template <typename T>
|
||||
bool operator>(const T& other) const {
|
||||
return compare(other) > 0;
|
||||
}
|
||||
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
* @name Binary arithmetic
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
template_t& operator+=(const template_t& other);
|
||||
template_t& operator-=(const template_t& other);
|
||||
template_t& operator*=(const template_t& other);
|
||||
template_t& operator/=(const template_t& other);
|
||||
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
* @name Unary arithmetic
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
template_t operator-() const {
|
||||
return template_t();
|
||||
}
|
||||
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
* @name Truth tests
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
operator bool() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
* @name Conversion
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
operator int() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
* @name Parsing
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
bool parse(std::istream& in);
|
||||
bool parse(const string& str) {
|
||||
std::istringstream stream(str);
|
||||
bool result = parse(stream, flags);
|
||||
assert(stream.eof());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
* @name Printing
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
void print(std::ostream& out) const;
|
||||
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
* @name Serialization
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
void read(std::istream& in);
|
||||
void read(const char *& data);
|
||||
void write(std::ostream& out) const;
|
||||
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
* @name XML Serialization
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
void read_xml(std::istream& in);
|
||||
void write_xml(std::ostream& out, const int depth = 0) const;
|
||||
|
||||
/*@}*/
|
||||
|
||||
/**
|
||||
* @name Debugging
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
void dump(std::ostream& out) const {
|
||||
print(out);
|
||||
}
|
||||
|
||||
bool valid() const;
|
||||
|
||||
/*@}*/
|
||||
};
|
||||
|
||||
} // namespace template
|
||||
|
||||
#endif // _TEMPLATE_H
|
||||
Loading…
Add table
Reference in a new issue