summaryrefslogtreecommitdiff
path: root/src
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 /src
parent645e43ef75b1af78ef6a4013684d76fd7d6e7118 (diff)
downloadfork-ledger-6b62be59fbb8a0b8fd4274fa46ca7295f1be0919.tar.gz
fork-ledger-6b62be59fbb8a0b8fd4274fa46ca7295f1be0919.tar.bz2
fork-ledger-6b62be59fbb8a0b8fd4274fa46ca7295f1be0919.zip
Added generate command, --seed, and GenerateTests
Diffstat (limited to 'src')
-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
5 files changed, 537 insertions, 0 deletions
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>