summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2009-02-27 03:58:43 -0400
committerJohn Wiegley <johnw@newartisans.com>2009-02-27 03:58:43 -0400
commit6b62be59fbb8a0b8fd4274fa46ca7295f1be0919 (patch)
treea14d7a1c3962d901cc26510fbb124380c0181a56
parent645e43ef75b1af78ef6a4013684d76fd7d6e7118 (diff)
downloadfork-ledger-6b62be59fbb8a0b8fd4274fa46ca7295f1be0919.tar.gz
fork-ledger-6b62be59fbb8a0b8fd4274fa46ca7295f1be0919.tar.bz2
fork-ledger-6b62be59fbb8a0b8fd4274fa46ca7295f1be0919.zip
Added generate command, --seed, and GenerateTests
-rw-r--r--Makefile.am12
-rw-r--r--doc/ledger.12
-rw-r--r--src/generate.cc384
-rw-r--r--src/generate.h131
-rw-r--r--src/report.cc16
-rw-r--r--src/report.h2
-rw-r--r--src/system.hh4
-rwxr-xr-xtest/GenerateTests.py162
-rwxr-xr-xtools/prove.sh11
9 files changed, 723 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am
index 403a0511..c3bffeab 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -65,6 +65,7 @@ libledger_data_la_LDFLAGS = -release $(VERSION).0
libledger_report_la_SOURCES = \
src/quotes.cc \
+ src/generate.cc \
src/derive.cc \
src/emacs.cc \
src/output.cc \
@@ -121,6 +122,7 @@ pkginclude_HEADERS = \
src/chain.h \
src/precmd.h \
src/derive.h \
+ src/generate.h \
src/output.h \
src/emacs.h \
src/quotes.h \
@@ -240,7 +242,7 @@ endif
TESTS =
if HAVE_PYTHON
-TESTS += RegressTests BaselineTests ConfirmTests
+TESTS += RegressTests BaselineTests ConfirmTests GenerateTests
endif
if HAVE_CPPUNIT
@@ -382,6 +384,14 @@ ConfirmTests: $(srcdir)/test/ConfirmTests.py
echo "$(PYTHON) $(srcdir)/test/ConfirmTests.py $(top_builddir)/ledger$(EXEEXT) $(srcdir)/test/input" > $@
chmod 755 $@
+GenerateTests_SOURCES = test/GenerateTests.py
+
+EXTRA_DIST += test/input
+
+GenerateTests: $(srcdir)/test/GenerateTests.py
+ echo "$(PYTHON) $(srcdir)/test/GenerateTests.py $(top_builddir)/ledger$(EXEEXT) $(srcdir)/test/input" > $@
+ chmod 755 $@
+
FULLCHECK=$(srcdir)/test/fullcheck.sh
if HAVE_CPPUNIT
diff --git a/doc/ledger.1 b/doc/ledger.1
index 1c6d6d62..aa7ab24e 100644
--- a/doc/ledger.1
+++ b/doc/ledger.1
@@ -61,6 +61,7 @@ The synonym
.Nm lisp
is also accepted.
.It Nm equity Oo Ar query Oc
+.It Nm generate
.It Nm prices Oo Ar query Oc
.It Nm pricesdb Oo Ar query Oc
.It Nm print Oo Ar query Oc
@@ -188,6 +189,7 @@ appeared in the original journal file.
.It Fl \-revalued
.It Fl \-revalued-only
.It Fl \-revalued-total Ar EXPR
+.It Fl \-seed Ar INT
.It Fl \-script
.It Fl \-set-account Ar EXPR
.It Fl \-set-payee Ar EXPR
diff --git a/src/generate.cc b/src/generate.cc
new file mode 100644
index 00000000..cb4b5f43
--- /dev/null
+++ b/src/generate.cc
@@ -0,0 +1,384 @@
+/*
+ * 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.
+ */
+
+#include "generate.h"
+#include "session.h"
+
+namespace ledger {
+
+generate_posts_iterator::generate_posts_iterator
+ (session_t& _session,
+ unsigned int _seed,
+ std::size_t _quantity,
+ bool _allow_invalid)
+ : session(_session), seed(_seed), quantity(_quantity),
+ allow_invalid(_allow_invalid),
+
+ rnd_gen(seed == 0 ? static_cast<unsigned int>(std::time(0)) : seed),
+
+ year_range(1900, 2300), year_gen(rnd_gen, year_range),
+ mon_range(1, 12), mon_gen(rnd_gen, mon_range),
+ day_range(1, 28), day_gen(rnd_gen, day_range),
+
+ upchar_range('A', 'Z'), upchar_gen(rnd_gen, upchar_range),
+ downchar_range('a', 'z'), downchar_gen(rnd_gen, downchar_range),
+ numchar_range('0', '9'), numchar_gen(rnd_gen, numchar_range),
+
+ truth_range(0, 1), truth_gen(rnd_gen, truth_range),
+ three_range(1, 3), three_gen(rnd_gen, three_range),
+ six_range(1, 6), six_gen(rnd_gen, six_range),
+ two_six_range(2, 6), two_six_gen(rnd_gen, two_six_range),
+ strlen_range(1, 40), strlen_gen(rnd_gen, strlen_range),
+
+ neg_number_range(-1000000, -1), neg_number_gen(rnd_gen, neg_number_range),
+ pos_number_range(1, 1000000), pos_number_gen(rnd_gen, pos_number_range)
+{
+ TRACE_CTOR(generate_posts_iterator, "bool");
+
+ std::ostringstream next_date_buf;
+ generate_date(next_date_buf);
+ next_date = parse_date(next_date_buf.str());
+
+ std::ostringstream next_eff_date_buf;
+ generate_date(next_eff_date_buf);
+ next_eff_date = parse_date(next_eff_date_buf.str());
+
+}
+
+void generate_posts_iterator::generate_string(std::ostream& out, int len,
+ bool only_alpha)
+{
+ DEBUG("generate.post.string",
+ "Generating string of length " << len << ", only alpha " << only_alpha);
+
+ int last;
+ bool first = true;
+ for (int i = 0; i < len; i++) {
+ int next = only_alpha ? 3 : three_gen();
+ bool output = true;
+ switch (next) {
+ case 1: // colon
+ if (! first && last == 3 && strlen_gen() % 10 == 0 && i + 1 != len)
+ out << ':';
+ else {
+ i--;
+ output = false;
+ }
+ break;
+ case 2: // space
+ if (! first && last == 3 && strlen_gen() % 20 == 0 && i + 1 != len)
+ out << ' ';
+ else {
+ i--;
+ output = false;
+ }
+ break;
+ case 3: // character
+ switch (three_gen()) {
+ case 1: // uppercase
+ out << char(upchar_gen());
+ break;
+ case 2: // lowercase
+ out << char(downchar_gen());
+ break;
+ case 3: // number
+ if (! only_alpha && ! first)
+ out << char(numchar_gen());
+ else {
+ i--;
+ output = false;
+ }
+ break;
+ }
+ break;
+ }
+ if (output) {
+ last = next;
+ first = false;
+ }
+ }
+}
+
+bool generate_posts_iterator::generate_account(std::ostream& out,
+ bool no_virtual)
+{
+ bool must_balance = true;
+ bool is_virtual = false;
+
+ if (! no_virtual) {
+ switch (three_gen()) {
+ case 1:
+ out << '[';
+ is_virtual = true;
+ break;
+ case 2:
+ out << '(';
+ must_balance = false;
+ is_virtual = true;
+ break;
+ case 3:
+ break;
+ }
+ }
+
+ generate_string(out, strlen_gen());
+
+ if (is_virtual) {
+ if (must_balance)
+ out << ']';
+ else
+ out << ')';
+ }
+
+ return must_balance;
+}
+
+void generate_posts_iterator::generate_commodity(std::ostream& out)
+{
+ string comm;
+ do {
+ std::ostringstream buf;
+ generate_string(buf, six_gen(), true);
+ comm = buf.str();
+ }
+ while (comm == "h" || comm == "m" || comm == "s" ||
+ comm == "and" || comm == "div" || comm == "false" ||
+ comm == "or" || comm == "not" || comm == "true");
+
+ out << comm;
+}
+
+string generate_posts_iterator::generate_amount(std::ostream& out,
+ value_t not_this_amount,
+ bool no_negative)
+{
+ std::ostringstream buf;
+
+ if (truth_gen()) { // commodity goes in front
+ generate_commodity(buf);
+ if (truth_gen())
+ buf << ' ';
+ if (no_negative || truth_gen())
+ buf << pos_number_gen();
+ else
+ buf << neg_number_gen();
+ } else {
+ if (no_negative || truth_gen())
+ buf << pos_number_gen();
+ else
+ buf << neg_number_gen();
+ if (truth_gen())
+ buf << ' ';
+ generate_commodity(buf);
+ }
+
+#if 0
+ // Possibly generate an annotized commodity, but make it rarer
+ if (! no_negative && three_gen() == 1) {
+ if (truth_gen()) {
+ out << " {";
+ generate_amount(out, value_t(), true);
+ out << '}';
+ }
+ if (truth_gen()) {
+ out << " [";
+ generate_date(out);
+ out << ']';
+ }
+ if (truth_gen()) {
+ out << " (";
+ generate_string(out, strlen_gen());
+ out << ')';
+ }
+ }
+#endif
+
+ if (! not_this_amount.is_null() &&
+ value_t(buf.str()).as_amount().commodity() ==
+ not_this_amount.as_amount().commodity())
+ return "";
+
+ out << buf.str();
+
+ return buf.str();
+}
+
+bool generate_posts_iterator::generate_post(std::ostream& out, bool no_amount)
+{
+ out << " ";
+ bool must_balance = generate_account(out, no_amount);
+ out << " ";
+
+ if (! no_amount) {
+ value_t amount(generate_amount(out));
+ if (truth_gen())
+ generate_cost(out, amount);
+ }
+ if (truth_gen())
+ generate_note(out);
+ out << '\n';
+
+ return must_balance;
+}
+
+void generate_posts_iterator::generate_cost(std::ostream& out, value_t amount)
+{
+ std::ostringstream buf;
+
+ if (truth_gen())
+ buf << " @ ";
+ else
+ buf << " @@ ";
+
+ if (! generate_amount(buf, amount, true).empty())
+ out << buf.str();
+}
+
+void generate_posts_iterator::generate_date(std::ostream& out)
+{
+ out.width(4);
+ out.fill('0');
+ out << year_gen();
+
+ out.width(1);
+ out << '/';
+
+ out.width(2);
+ out.fill('0');
+ out << mon_gen();
+
+ out.width(1);
+ out << '/';
+
+ out.width(2);
+ out.fill('0');
+ out << day_gen();
+}
+
+void generate_posts_iterator::generate_state(std::ostream& out)
+{
+ switch (three_gen()) {
+ case 1:
+ out << "* ";
+ break;
+ case 2:
+ out << "! ";
+ break;
+ case 3:
+ out << "";
+ break;
+ }
+}
+
+void generate_posts_iterator::generate_code(std::ostream& out)
+{
+ out << '(';
+ generate_string(out, six_gen());
+ out << ") ";
+}
+
+void generate_posts_iterator::generate_payee(std::ostream& out)
+{
+ generate_string(out, strlen_gen());
+}
+
+void generate_posts_iterator::generate_note(std::ostream& out)
+{
+ out << "\n ; ";
+ generate_string(out, strlen_gen());
+}
+
+void generate_posts_iterator::generate_xact(std::ostream& out)
+{
+ out << format_date(next_date, string("%Y/%m/%d"));
+ next_date += gregorian::days(six_gen());
+ if (truth_gen()) {
+ out << '=';
+ out << format_date(next_eff_date, string("%Y/%m/%d"));
+ next_eff_date += gregorian::days(six_gen());
+ }
+ out << ' ';
+
+ generate_state(out);
+ generate_code(out);
+ generate_payee(out);
+ if (truth_gen())
+ generate_note(out);
+ out << '\n';
+
+ int count = three_gen() * 2;
+ bool has_must_balance = false;
+ for (int i = 0; i < count; i++) {
+ if (generate_post(out))
+ has_must_balance = true;
+ }
+ if (has_must_balance)
+ generate_post(out, true);
+
+ out << '\n';
+}
+
+post_t * generate_posts_iterator::operator()()
+{
+ post_t * post = posts();
+ if (post == NULL && quantity > 0) {
+ std::ostringstream buf;
+ generate_xact(buf);
+
+ DEBUG("generate.post", "The post we intend to parse:\n" << buf.str());
+
+ std::istringstream in(buf.str());
+ try {
+ if (session.journal->parse(in, session) != 0) {
+ VERIFY(session.journal->xacts.back()->valid());
+ posts.reset(*session.journal->xacts.back());
+ post = posts();
+ }
+ }
+ catch (std::exception& err) {
+ add_error_context(_("While parsing generated transaction (seed %1):"
+ << seed));
+ add_error_context(buf.str());
+ throw;
+ }
+ catch (int status) {
+ add_error_context(_("While parsing generated transaction (seed %1):"
+ << seed));
+ add_error_context(buf.str());
+ throw;
+ }
+
+ quantity--;
+ }
+ return post;
+}
+
+} // namespace ledger
diff --git a/src/generate.h b/src/generate.h
new file mode 100644
index 00000000..7a837bfb
--- /dev/null
+++ b/src/generate.h
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+
+/**
+ * @addtogroup generate
+ */
+
+/**
+ * @file generate.h
+ * @author John Wiegley
+ *
+ * @ingroup report
+ *
+ * @brief Brief
+ *
+ * Long.
+ */
+#ifndef _GENERATE_H
+#define _GENERATE_H
+
+#include "iterators.h"
+
+namespace ledger {
+
+class generate_posts_iterator : public posts_iterator
+{
+ session_t& session;
+ unsigned int seed;
+ std::size_t quantity;
+ bool allow_invalid;
+ date_t next_date;
+ date_t next_eff_date;
+
+ mt19937 rnd_gen;
+
+ typedef variate_generator<mt19937&, uniform_int<> > int_generator_t;
+ typedef variate_generator<mt19937&, uniform_real<> > real_generator_t;
+
+ uniform_int<> year_range;
+ int_generator_t year_gen;
+ uniform_int<> mon_range;
+ int_generator_t mon_gen;
+ uniform_int<> day_range;
+ int_generator_t day_gen;
+
+ uniform_int<> upchar_range;
+ int_generator_t upchar_gen;
+ uniform_int<> downchar_range;
+ int_generator_t downchar_gen;
+ uniform_int<> numchar_range;
+ int_generator_t numchar_gen;
+
+ uniform_int<> truth_range;
+ int_generator_t truth_gen;
+ uniform_int<> three_range;
+ int_generator_t three_gen;
+ uniform_int<> six_range;
+ int_generator_t six_gen;
+ uniform_int<> two_six_range;
+ int_generator_t two_six_gen;
+
+ uniform_int<> strlen_range;
+ int_generator_t strlen_gen;
+
+ uniform_real<> neg_number_range;
+ real_generator_t neg_number_gen;
+ uniform_real<> pos_number_range;
+ real_generator_t pos_number_gen;
+
+ xact_posts_iterator posts;
+
+public:
+ generate_posts_iterator(session_t& _session,
+ unsigned int _seed = 0,
+ std::size_t _quantity = 100,
+ bool _allow_invalid = false);
+
+ virtual ~generate_posts_iterator() throw() {
+ TRACE_DTOR(generate_posts_iterator);
+ }
+
+ virtual post_t * operator()();
+
+protected:
+ void generate_string(std::ostream& out, int len, bool only_alpha = false);
+ bool generate_account(std::ostream& out, bool no_virtual = false);
+ void generate_commodity(std::ostream& out);
+ string generate_amount(std::ostream& out,
+ value_t not_this_amount = NULL_VALUE,
+ bool no_negative = false);
+ bool generate_post(std::ostream& out, bool no_amount = false);
+ void generate_cost(std::ostream& out, value_t amount);
+ void generate_date(std::ostream& out);
+ void generate_state(std::ostream& out);
+ void generate_code(std::ostream& out);
+ void generate_payee(std::ostream& out);
+ void generate_note(std::ostream& out);
+ void generate_xact(std::ostream& out);
+};
+
+} // namespace ledger
+
+#endif // _GENERATE_H
diff --git a/src/report.cc b/src/report.cc
index a969a2b8..5e4ba48d 100644
--- a/src/report.cc
+++ b/src/report.cc
@@ -32,6 +32,7 @@
#include "report.h"
#include "interactive.h"
#include "iterators.h"
+#include "generate.h"
#include "filters.h"
#include "chain.h"
#include "output.h"
@@ -48,6 +49,14 @@ void report_t::posts_report(post_handler_ptr handler)
session.clean_posts();
}
+void report_t::generate_report(post_handler_ptr handler)
+{
+ generate_posts_iterator walker
+ (session, HANDLED(seed_) ?
+ static_cast<unsigned int>(HANDLER(seed_).value.to_long()) : 0);
+ pass_down_posts(chain_post_handlers(*this, handler), walker);
+}
+
void report_t::xact_report(post_handler_ptr handler, xact_t& xact)
{
xact_posts_iterator walker(xact);
@@ -536,6 +545,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(sort_xacts_);
else OPT_(subtotal);
else OPT(start_of_week_);
+ else OPT(seed_);
break;
case 't':
OPT_CH(amount_);
@@ -709,6 +719,12 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
if (is_eq(q, "format"))
return WRAP_FUNCTOR(format_command);
break;
+ case 'g':
+ if (is_eq(q, "generate"))
+ return expr_t::op_t::wrap_functor
+ (reporter<post_t, post_handler_ptr, &report_t::generate_report>
+ (new format_posts(*this, report_format(HANDLER(print_format_)),
+ false), *this));
case 'h':
if (is_eq(q, "hello") && maybe_import("ledger.hello"))
return session.lookup(string(PRECMD_PREFIX) + "hello");
diff --git a/src/report.h b/src/report.h
index 74a49052..28dd0fb4 100644
--- a/src/report.h
+++ b/src/report.h
@@ -122,6 +122,7 @@ public:
}
void posts_report(post_handler_ptr handler);
+ void generate_report(post_handler_ptr handler);
void xact_report(post_handler_ptr handler, xact_t& xact);
void accounts_report(acct_handler_ptr handler);
void commodities_report(post_handler_ptr handler);
@@ -582,6 +583,7 @@ public:
set_expr(args[0].to_string());
});
+ OPTION(report_t, seed_);
OPTION(report_t, set_account_);
OPTION(report_t, set_payee_);
OPTION(report_t, set_price_);
diff --git a/src/system.hh b/src/system.hh
index 899c2f2a..15976459 100644
--- a/src/system.hh
+++ b/src/system.hh
@@ -170,6 +170,10 @@ typedef std::ostream::pos_type ostream_pos_type;
#include <boost/operators.hpp>
#include <boost/optional.hpp>
#include <boost/ptr_container/ptr_list.hpp>
+#include <boost/random/mersenne_twister.hpp>
+#include <boost/random/uniform_int.hpp>
+#include <boost/random/uniform_real.hpp>
+#include <boost/random/variate_generator.hpp>
#include <boost/regex.hpp>
#include <boost/variant.hpp>
#include <boost/version.hpp>
diff --git a/test/GenerateTests.py b/test/GenerateTests.py
new file mode 100755
index 00000000..88c223cd
--- /dev/null
+++ b/test/GenerateTests.py
@@ -0,0 +1,162 @@
+#!/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
+from difflib import ndiff
+
+ledger = sys.argv[1]
+succeeded = 0
+failed = 0
+
+if not os.path.isfile(ledger):
+ sys.exit(1)
+
+def normalize(line):
+ match = re.match("((\s*)([A-Za-z]+)?(\s*)([-0-9.]+)(\s*)([A-Za-z]+)?)( (.+))?$", line)
+ if match:
+ if match.group(3):
+ prefix = match.group(3) + " " + match.group(5)
+ if match.group(8):
+ return prefix + match.group(8)
+ return prefix
+ elif match.group(7):
+ prefix = match.group(7) + " " + match.group(5)
+ if match.group(8):
+ return prefix + match.group(8)
+ return prefix
+ return line
+
+def generation_test(seed):
+ global succeeded, failed
+
+ p_gen = Popen("%s --args-only --actual --seed=%d generate" % (ledger, seed),
+ shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE,
+ close_fds=True)
+ cout = ""
+ cout_data = os.read(p_gen.stdout.fileno(), 8192)
+ while cout_data:
+ if cout_data:
+ cout += cout_data
+ cout_data = os.read(p_gen.stdout.fileno(), 8192)
+ if cout_data:
+ cout += cout_data
+
+ if p_gen.wait() != 0:
+ print "Generation for seed %d failed due to error:" % seed
+ print p_gen.stderr.read()
+ del p_gen
+ return False
+ del p_gen
+
+ p_print = Popen("%s --args-only --actual -f - print" % ledger, shell=True,
+ stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
+ p_print.stdin.write(cout)
+ p_print.stdin.close()
+ p_print_out = p_print.stdout.read()
+
+ if p_print.wait() != 0:
+ print "Print for seed %d failed due to error:" % seed
+ print p_print.stderr.read()
+ del p_print
+ return False
+ del p_print
+
+ #p_cerr_bal = Popen("%s --args-only -f - bal" % ledger, shell=True,
+ # stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
+ #p_cerr_bal.stdin.write(cerr)
+ #p_cerr_bal.stdin.close()
+ #
+ #cerr_lines = [normalize(line) for line in p_cerr_bal.stdout.readlines()]
+ #
+ #if p_cerr_bal.wait() != 0:
+ # print "Stderr balance for seed %d failed due to error:" % seed
+ # print p_cerr_bal.stderr.read()
+ # del p_cerr_bal
+ # return False
+ #del p_cerr_bal
+
+ p_cout_bal = Popen("%s --args-only -f - bal" % ledger, shell=True,
+ stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
+ p_cout_bal.stdin.write(cout)
+ p_cout_bal.stdin.close()
+
+ cout_lines = p_cout_bal.stdout.readlines()
+ norm_cout_lines = [normalize(line) for line in cout_lines]
+
+ if p_cout_bal.wait() != 0:
+ print "Stdout balance for seed %d failed due to error:" % seed
+ print p_cout_bal.stderr.read()
+ del p_cout_bal
+ return False
+ del p_cout_bal
+
+ p_print_bal = Popen("%s --args-only -f - bal" % ledger, shell=True,
+ stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
+ p_print_bal.stdin.write(p_print_out)
+ p_print_bal.stdin.close()
+
+ print_lines = p_print_bal.stdout.readlines()
+
+ if p_print_bal.wait() != 0:
+ print "Print balance for seed %d failed due to error:" % seed
+ print p_print_bal.stderr.read()
+ del p_print_bal
+ return False
+ del p_print_bal
+
+ success = True
+ #printed = False
+ #for line in ndiff(cerr_lines, norm_cout_lines, charjunk=None):
+ # if line[:2] == " ":
+ # continue
+ # if not printed:
+ # if success: print
+ # print "Generation failure in output from seed %d (cerr vs. cout):" % seed
+ # if success: failed += 1
+ # success = False
+ # printed = True
+ # print " ", line
+
+ printed = False
+ for line in ndiff(cout_lines, print_lines, charjunk=None):
+ if line[:2] == " ":
+ continue
+ if not printed:
+ if success: print
+ print "Generation failure in output from seed %d (cout vs. print):" % seed
+ if success: failed += 1
+ success = False
+ printed = True
+ print " ", line
+
+ return success
+
+beg_range = 1
+end_range = 20
+if len(sys.argv) > 3:
+ beg_range = int(sys.argv[2])
+ end_range = int(sys.argv[3])
+
+for i in range(beg_range, end_range):
+ if generation_test(i):
+ sys.stdout.write(".")
+ succeeded += 1
+ else:
+ sys.stdout.write("E")
+ failed += 1
+
+print
+if succeeded > 0:
+ print "OK (%d) " % succeeded,
+if failed > 0:
+ print "FAILED (%d)" % failed,
+print
+
+sys.exit(failed)
+
diff --git a/tools/prove.sh b/tools/prove.sh
new file mode 100755
index 00000000..c62c4988
--- /dev/null
+++ b/tools/prove.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+if [ -n "$1" ]; then
+ ./ledger --seed=$1 --actual --args-only generate > /tmp/cout
+else
+ ./ledger --actual --args-only generate > /tmp/cout
+fi
+
+ledger -f /tmp/cout --actual --args-only print > /tmp/print
+
+diff -w -U3 /tmp/cout /tmp/print