summaryrefslogtreecommitdiff
path: root/src/generate.cc
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/generate.cc
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/generate.cc')
-rw-r--r--src/generate.cc384
1 files changed, 384 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