summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2012-03-01 05:50:07 -0600
committerJohn Wiegley <johnw@newartisans.com>2012-03-01 05:50:07 -0600
commit9ec9cdf41e5176f7fcf06da5f75593d9ba3d4028 (patch)
treef9536d691ef7c5abea82349a0b58a5f5e3cf8e33
parent944e580825f0d9ce060b6dcdffe8990b6c2cca20 (diff)
downloadfork-ledger-9ec9cdf41e5176f7fcf06da5f75593d9ba3d4028.tar.gz
fork-ledger-9ec9cdf41e5176f7fcf06da5f75593d9ba3d4028.tar.bz2
fork-ledger-9ec9cdf41e5176f7fcf06da5f75593d9ba3d4028.zip
Started writing Python unit tests
-rw-r--r--src/error.h6
-rw-r--r--src/global.h2
-rw-r--r--src/main.cc7
-rw-r--r--src/py_amount.cc2
-rw-r--r--src/py_commodity.cc40
-rw-r--r--src/py_journal.cc93
-rw-r--r--src/py_post.cc2
-rw-r--r--src/py_session.cc76
-rw-r--r--src/py_xact.cc8
-rw-r--r--src/pyinterp.cc2
-rw-r--r--src/session.cc39
-rw-r--r--src/session.h4
-rw-r--r--src/system.hh.in2
-rw-r--r--src/textual.cc2
-rw-r--r--src/utils.cc3
-rw-r--r--test/python/JournalTest.py25
-rw-r--r--tools/Makefile.am1
17 files changed, 220 insertions, 94 deletions
diff --git a/src/error.h b/src/error.h
index 7630f017..86d9de76 100644
--- a/src/error.h
+++ b/src/error.h
@@ -101,6 +101,12 @@ string source_context(const path& file,
virtual ~name() throw() {} \
}
+struct error_count {
+ std::size_t count;
+ explicit error_count(std::size_t _count) : count(_count) {}
+ const char * what() const { return ""; }
+};
+
} // namespace ledger
#endif // _ERROR_H
diff --git a/src/global.h b/src/global.h
index 28bffc3a..2cb7842e 100644
--- a/src/global.h
+++ b/src/global.h
@@ -155,7 +155,7 @@ See LICENSE file included with the distribution for details and disclaimer.");
OPTION_(global_scope_t, version, DO() { // -v
parent->show_version_info(std::cout);
- throw int(0); // exit immediately
+ throw error_count(0); // exit immediately
});
};
diff --git a/src/main.cc b/src/main.cc
index 2202a5de..aafbdbcb 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -191,9 +191,10 @@ int main(int argc, char * argv[], char * envp[])
std::cerr << "Exception during initialization: " << err.what()
<< std::endl;
}
- catch (int _status) {
- status = _status; // used for a "quick" exit, and is used only
- // if help text (such as --help) was displayed
+ catch (const error_count& errors) {
+ // used for a "quick" exit, and is used only if help text (such as
+ // --help) was displayed
+ status = static_cast<int>(errors.count);
}
// If memory verification is being performed (which can be very slow), clean
diff --git a/src/py_amount.cc b/src/py_amount.cc
index f10595e8..25ec8e26 100644
--- a/src/py_amount.cc
+++ b/src/py_amount.cc
@@ -263,7 +263,7 @@ internal precision."))
.add_property("commodity",
make_function(&amount_t::commodity,
- return_value_policy<reference_existing_object>()),
+ return_internal_reference<>()),
make_function(&amount_t::set_commodity,
with_custodian_and_ward<1, 2>()))
.def("has_commodity", &amount_t::has_commodity)
diff --git a/src/py_commodity.cc b/src/py_commodity.cc
index ffa903f4..b5230850 100644
--- a/src/py_commodity.cc
+++ b/src/py_commodity.cc
@@ -269,18 +269,14 @@ void export_commodity()
.def("make_qualified_name", &commodity_pool_t::make_qualified_name)
- .def("create", py_create_1,
- return_value_policy<reference_existing_object>())
- .def("create", py_create_2,
- return_value_policy<reference_existing_object>())
+ .def("create", py_create_1, return_internal_reference<>())
+ .def("create", py_create_2, return_internal_reference<>())
- .def("find_or_create", py_find_or_create_1,
- return_value_policy<reference_existing_object>())
- .def("find_or_create", py_find_or_create_2,
- return_value_policy<reference_existing_object>())
+ .def("find_or_create", py_find_or_create_1, return_internal_reference<>())
+ .def("find_or_create", py_find_or_create_2, return_internal_reference<>())
- .def("find", py_find_1, return_value_policy<reference_existing_object>())
- .def("find", py_find_2, return_value_policy<reference_existing_object>())
+ .def("find", py_find_1, return_internal_reference<>())
+ .def("find", py_find_2, return_internal_reference<>())
.def("exchange", py_exchange_2, with_custodian_and_ward<1, 2>())
.def("exchange", py_exchange_3, with_custodian_and_ward<1, 2>())
@@ -288,23 +284,23 @@ void export_commodity()
.def("parse_price_directive", &commodity_pool_t::parse_price_directive)
.def("parse_price_expression", &commodity_pool_t::parse_price_expression,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.def("__getitem__", py_pool_getitem,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.def("keys", py_pool_keys)
.def("has_key", py_pool_contains)
.def("__contains__", py_pool_contains)
.def("__iter__",
- python::range<return_value_policy<reference_existing_object> >
+ python::range<return_internal_reference<> >
(py_pool_commodities_begin, py_pool_commodities_end))
.def("iteritems",
- python::range<return_value_policy<reference_existing_object> >
+ python::range<return_internal_reference<> >
(py_pool_commodities_begin, py_pool_commodities_end))
.def("iterkeys", python::range<>(py_pool_commodities_keys_begin,
py_pool_commodities_keys_end))
.def("itervalues",
- python::range<return_value_policy<reference_existing_object> >
+ python::range<return_internal_reference<> >
(py_pool_commodities_values_begin, py_pool_commodities_values_end))
;
@@ -349,17 +345,17 @@ void export_commodity()
.add_property("referent",
make_function(py_commodity_referent,
- return_value_policy<reference_existing_object>()))
+ return_internal_reference<>()))
.def("has_annotation", &commodity_t::has_annotation)
.def("strip_annotations", py_strip_annotations_0,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.def("strip_annotations", py_strip_annotations_1,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.def("write_annotations", &commodity_t::write_annotations)
.def("pool", &commodity_t::pool,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.add_property("base_symbol", &commodity_t::base_symbol)
.add_property("symbol", &commodity_t::symbol)
@@ -441,12 +437,12 @@ void export_commodity()
.add_property("referent",
make_function(py_annotated_commodity_referent,
- return_value_policy<reference_existing_object>()))
+ return_internal_reference<>()))
.def("strip_annotations", py_strip_ann_annotations_0,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.def("strip_annotations", py_strip_ann_annotations_1,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.def("write_annotations", &annotated_commodity_t::write_annotations)
;
}
diff --git a/src/py_journal.cc b/src/py_journal.cc
index a72b8528..550fb14e 100644
--- a/src/py_journal.cc
+++ b/src/py_journal.cc
@@ -144,10 +144,10 @@ namespace {
struct collector_wrapper
{
- journal_t& journal;
- report_t report;
- collect_posts * posts_collector;
- post_handler_ptr chain;
+ journal_t& journal;
+ report_t report;
+
+ post_handler_ptr posts_collector;
collector_wrapper(journal_t& _journal, report_t& base)
: journal(_journal), report(base),
@@ -157,31 +157,32 @@ namespace {
}
std::size_t length() const {
- return posts_collector->length();
+ return dynamic_cast<collect_posts *>(posts_collector.get())->length();
}
std::vector<post_t *>::iterator begin() {
- return posts_collector->begin();
+ return dynamic_cast<collect_posts *>(posts_collector.get())->begin();
}
std::vector<post_t *>::iterator end() {
- return posts_collector->end();
+ return dynamic_cast<collect_posts *>(posts_collector.get())->end();
}
};
- shared_ptr<collector_wrapper>
- py_collect(journal_t& journal, const string& query)
+ shared_ptr<collector_wrapper> py_query(journal_t& journal,
+ const string& query)
{
if (journal.has_xdata()) {
PyErr_SetString(PyExc_RuntimeError,
- _("Cannot have multiple journal collections open at once"));
+ _("Cannot have more than one active journal query"));
throw_error_already_set();
}
report_t& current_report(downcast<report_t>(*scope_t::default_scope));
- shared_ptr<collector_wrapper> coll(new collector_wrapper(journal,
- current_report));
- unique_ptr<journal_t> save_journal(current_report.session.journal.release());
- current_report.session.journal.reset(&journal);
+ shared_ptr<collector_wrapper>
+ coll(new collector_wrapper(journal, current_report));
+
+ unique_ptr<journal_t> save_journal(coll->report.session.journal.release());
+ coll->report.session.journal.reset(&coll->journal);
try {
strings_list remaining =
@@ -191,60 +192,48 @@ namespace {
value_t args;
foreach (const string& arg, remaining)
args.push_back(string_value(arg));
- coll->report.parse_query_args(args, "@Journal.collect");
+ coll->report.parse_query_args(args, "@Journal.query");
- journal_posts_iterator walker(coll->journal);
- coll->chain =
- chain_post_handlers(post_handler_ptr(coll->posts_collector),
- coll->report);
- pass_down_posts<journal_posts_iterator>(coll->chain, walker);
+ coll->report.posts_report(coll->posts_collector);
}
catch (...) {
- current_report.session.journal.release();
- current_report.session.journal.reset(save_journal.release());
+ coll->report.session.journal.release();
+ coll->report.session.journal.reset(save_journal.release());
throw;
}
- current_report.session.journal.release();
- current_report.session.journal.reset(save_journal.release());
+ coll->report.session.journal.release();
+ coll->report.session.journal.reset(save_journal.release());
return coll;
}
post_t * posts_getitem(collector_wrapper& collector, long i)
{
- post_t * post =
- collector.posts_collector->posts[static_cast<std::string::size_type>(i)];
- std::cerr << typeid(post).name() << std::endl;
- std::cerr << typeid(*post).name() << std::endl;
- std::cerr << typeid(post->account).name() << std::endl;
- std::cerr << typeid(*post->account).name() << std::endl;
- return post;
+ return dynamic_cast<collect_posts *>(collector.posts_collector.get())
+ ->posts[static_cast<std::size_t>(i)];
}
} // unnamed namespace
+#define EXC_TRANSLATOR(type) \
+ void exc_translate_ ## type(const type& err) { \
+ PyErr_SetString(PyExc_RuntimeError, err.what()); \
+ }
+
+EXC_TRANSLATOR(parse_error)
+EXC_TRANSLATOR(error_count)
+
void export_journal()
{
class_< item_handler<post_t>, shared_ptr<item_handler<post_t> >,
boost::noncopyable >("PostHandler")
;
- class_< collect_posts, bases<item_handler<post_t> >,
- shared_ptr<collect_posts>, boost::noncopyable >("PostCollector")
- .def("__len__", &collect_posts::length)
- .def("__iter__", python::range<return_internal_reference<1,
- with_custodian_and_ward_postcall<1, 0> > >
- (&collect_posts::begin, &collect_posts::end))
- ;
-
class_< collector_wrapper, shared_ptr<collector_wrapper>,
boost::noncopyable >("PostCollectorWrapper", no_init)
.def("__len__", &collector_wrapper::length)
- .def("__getitem__", posts_getitem, return_internal_reference<1,
- with_custodian_and_ward_postcall<0, 1> >())
- .def("__iter__",
- python::range<return_value_policy<reference_existing_object,
- with_custodian_and_ward_postcall<0, 1> > >
+ .def("__getitem__", posts_getitem, return_internal_reference<>())
+ .def("__iter__", python::range<return_internal_reference<> >
(&collector_wrapper::begin, &collector_wrapper::end))
;
@@ -286,13 +275,13 @@ void export_journal()
.def("find_account", py_find_account_1,
return_internal_reference<1,
- with_custodian_and_ward_postcall<0, 1> >())
+ with_custodian_and_ward_postcall<1, 0> >())
.def("find_account", py_find_account_2,
return_internal_reference<1,
- with_custodian_and_ward_postcall<0, 1> >())
+ with_custodian_and_ward_postcall<1, 0> >())
.def("find_account_re", &journal_t::find_account_re,
return_internal_reference<1,
- with_custodian_and_ward_postcall<0, 1> >())
+ with_custodian_and_ward_postcall<1, 0> >())
.def("add_xact", &journal_t::add_xact)
.def("remove_xact", &journal_t::remove_xact)
@@ -301,7 +290,7 @@ void export_journal()
#if 0
.def("__getitem__", xacts_getitem,
return_internal_reference<1,
- with_custodian_and_ward_postcall<0, 1> >())
+ with_custodian_and_ward_postcall<1, 0> >())
#endif
.def("__iter__", python::range<return_internal_reference<> >
@@ -320,10 +309,16 @@ void export_journal()
.def("has_xdata", &journal_t::has_xdata)
.def("clear_xdata", &journal_t::clear_xdata)
- .def("collect", py_collect, with_custodian_and_ward_postcall<0, 1>())
+ .def("query", py_query)
.def("valid", &journal_t::valid)
;
+
+#define EXC_TRANSLATE(type) \
+ register_exception_translator<type>(&exc_translate_ ## type);
+
+ EXC_TRANSLATE(parse_error);
+ EXC_TRANSLATE(error_count);
}
} // namespace ledger
diff --git a/src/py_post.cc b/src/py_post.cc
index bd599604..2789082e 100644
--- a/src/py_post.cc
+++ b/src/py_post.cc
@@ -116,7 +116,7 @@ void export_post()
make_setter(&post_t::xdata_t::datetime))
.add_property("account",
make_getter(&post_t::xdata_t::account,
- return_value_policy<reference_existing_object>()),
+ return_internal_reference<>()),
make_setter(&post_t::xdata_t::account,
with_custodian_and_ward<1, 2>()))
.add_property("sort_values",
diff --git a/src/py_session.cc b/src/py_session.cc
new file mode 100644
index 00000000..f411d5e1
--- /dev/null
+++ b/src/py_session.cc
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2003-2012, 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 <system.hh>
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "session.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+namespace {
+ journal_t * py_read_journal(const string& pathname)
+ {
+ return python_session->read_journal(path(pathname));
+ }
+
+ journal_t * py_read_journal_from_string(const string& data)
+ {
+ return python_session->read_journal_from_string(data);
+ }
+}
+
+void export_session()
+{
+ class_< session_t, boost::noncopyable > ("Session")
+ .def("read_journal", &session_t::read_journal,
+ return_internal_reference<>())
+ .def("read_journal_from_string", &session_t::read_journal_from_string,
+ return_internal_reference<>())
+ .def("read_journal_files", &session_t::read_journal_files,
+ return_internal_reference<>())
+ .def("close_journal_files", &session_t::close_journal_files)
+ ;
+
+ scope().attr("session") =
+ object(ptr(static_cast<session_t *>(python_session.get())));
+ scope().attr("read_journal") =
+ python::make_function(&py_read_journal,
+ return_internal_reference<>());
+ scope().attr("read_journal_from_string") =
+ python::make_function(&py_read_journal_from_string,
+ return_internal_reference<>());
+}
+
+} // namespace ledger
diff --git a/src/py_xact.cc b/src/py_xact.cc
index 97d5df47..3d792c7b 100644
--- a/src/py_xact.cc
+++ b/src/py_xact.cc
@@ -76,6 +76,12 @@ namespace {
return **elem;
}
+ string py_xact_to_string(xact_t&)
+ {
+ // jww (2012-03-01): TODO
+ return empty_string;
+ }
+
} // unnamed namespace
using namespace boost::python;
@@ -110,6 +116,8 @@ void export_xact()
.def("id", &xact_t::id)
.def("seq", &xact_t::seq)
+ .def("__str__", py_xact_to_string)
+
.add_property("code",
make_getter(&xact_t::code),
make_setter(&xact_t::code))
diff --git a/src/pyinterp.cc b/src/pyinterp.cc
index de9c94cb..4dbb7134 100644
--- a/src/pyinterp.cc
+++ b/src/pyinterp.cc
@@ -51,6 +51,7 @@ void export_commodity();
void export_expr();
void export_format();
void export_item();
+void export_session();
void export_journal();
void export_post();
void export_times();
@@ -72,6 +73,7 @@ void initialize_for_python()
export_item();
export_post();
export_xact();
+ export_session();
export_journal();
}
diff --git a/src/session.cc b/src/session.cc
index e07026b6..db01fbf6 100644
--- a/src/session.cc
+++ b/src/session.cc
@@ -89,8 +89,10 @@ std::size_t session_t::read_data(const string& master_account)
std::size_t xact_count = 0;
- account_t * acct = journal->master;
- if (! master_account.empty())
+ account_t * acct;
+ if (master_account.empty())
+ acct = journal->master;
+ else
acct = journal->find_account(master_account);
optional<path> price_db_path;
@@ -185,7 +187,7 @@ std::size_t session_t::read_data(const string& master_account)
return journal->xacts.size();
}
-void session_t::read_journal_files()
+journal_t * session_t::read_journal_files()
{
INFO_START(journal, "Read journal file");
@@ -203,6 +205,37 @@ void session_t::read_journal_files()
#if defined(DEBUG_ON)
INFO("Found " << count << " transactions");
#endif
+
+ return journal.get();
+}
+
+journal_t * session_t::read_journal(const path& pathname)
+{
+ HANDLER(file_).data_files.clear();
+ HANDLER(file_).data_files.push_back(pathname);
+
+ return read_journal_files();
+}
+
+journal_t * session_t::read_journal_from_string(const string& data)
+{
+ HANDLER(file_).data_files.clear();
+
+ shared_ptr<std::istream> stream(new std::istringstream(data));
+ parsing_context.push(stream);
+
+ parsing_context.get_current().journal = journal.get();
+ parsing_context.get_current().master = journal->master;
+ try {
+ journal->read(parsing_context);
+ }
+ catch (...) {
+ parsing_context.pop();
+ throw;
+ }
+ parsing_context.pop();
+
+ return journal.get();
}
void session_t::close_journal_files()
diff --git a/src/session.h b/src/session.h
index 54b9912a..38062b78 100644
--- a/src/session.h
+++ b/src/session.h
@@ -75,9 +75,11 @@ public:
flush_on_next_data_file = truth;
}
+ journal_t * read_journal(const path& pathname);
+ journal_t * read_journal_from_string(const string& data);
std::size_t read_data(const string& master_account = "");
- void read_journal_files();
+ journal_t * read_journal_files();
void close_journal_files();
value_t fn_account(call_scope_t& scope);
diff --git a/src/system.hh.in b/src/system.hh.in
index e14166b2..3aa60f71 100644
--- a/src/system.hh.in
+++ b/src/system.hh.in
@@ -257,4 +257,6 @@ void serialize(Archive& ar, istream_pos_type& pos, const unsigned int)
#include <boost/python/module_init.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+#include <boost/iterator/indirect_iterator.hpp>
+
#endif // HAVE_BOOST_PYTHON
diff --git a/src/textual.cc b/src/textual.cc
index 97c80e4f..2f09c063 100644
--- a/src/textual.cc
+++ b/src/textual.cc
@@ -1746,7 +1746,7 @@ std::size_t journal_t::read_textual(parse_context_stack_t& context_stack)
TRACE_FINISH(parsing_total, 1);
if (context_stack.get_current().errors > 0)
- throw static_cast<int>(context_stack.get_current().errors);
+ throw error_count(context_stack.get_current().errors);
return context_stack.get_current().count;
}
diff --git a/src/utils.cc b/src/utils.cc
index 09526267..eb1d8009 100644
--- a/src/utils.cc
+++ b/src/utils.cc
@@ -612,8 +612,7 @@ optional<boost::regex> _log_category_re;
struct __maybe_enable_debugging {
__maybe_enable_debugging() {
- const char * p = std::getenv("LEDGER_DEBUG");
- if (p != NULL) {
+ if (const char * p = std::getenv("LEDGER_DEBUG")) {
_log_level = LOG_DEBUG;
_log_category = p;
}
diff --git a/test/python/JournalTest.py b/test/python/JournalTest.py
index 66447f87..e65c671d 100644
--- a/test/python/JournalTest.py
+++ b/test/python/JournalTest.py
@@ -1,22 +1,27 @@
# -*- coding: utf-8 -*-
import unittest
-import exceptions
-import operator
from ledger import *
-from StringIO import *
-from datetime import *
class JournalTestCase(unittest.TestCase):
- def setUp(self):
- pass
-
def tearDown(self):
- pass
+ session.close_journal_files()
+
+ def testBasicRead(self):
+ journal = read_journal_from_string("""
+2012-03-01 KFC
+ Expenses:Food $21.34
+ Assets:Cash
+""")
+ self.assertEqual(type(journal), Journal)
+
+ for xact in journal:
+ self.assertEqual(xact.payee, "KFC")
- def test_(self):
- pass
+ for post in journal.query("food"):
+ self.assertEqual(str(post.account), "Expenses:Food")
+ self.assertEqual(post.amount, Amount("$21.34"))
def suite():
return unittest.TestLoader().loadTestsFromTestCase(JournalTestCase)
diff --git a/tools/Makefile.am b/tools/Makefile.am
index fe0681e5..4fdd8393 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -226,6 +226,7 @@ libledger_python_la_SOURCES = \
src/py_expr.cc \
src/py_format.cc \
src/py_item.cc \
+ src/py_session.cc \
src/py_journal.cc \
src/py_post.cc \
src/py_times.cc \