diff --git a/lib/mu-config.cc b/lib/mu-config.cc index 4f310c2d..23930f27 100644 --- a/lib/mu-config.cc +++ b/lib/mu-config.cc @@ -79,6 +79,16 @@ test_basic() } } +static void +test_read_only() +{ + MemDb db{true/*read-only*/}; + Config conf_db{db}; + + auto res = conf_db.set(12345); + g_assert_false(!!res); +} + int main(int argc, char* argv[]) { @@ -86,6 +96,7 @@ main(int argc, char* argv[]) g_test_add_func("/config-db/props", test_props); g_test_add_func("/config-db/basic", test_basic); + g_test_add_func("/config-db/read-only", test_read_only); return g_test_run(); } diff --git a/lib/mu-indexer.cc b/lib/mu-indexer.cc index 9ec5d4b0..52cfdedf 100644 --- a/lib/mu-indexer.cc +++ b/lib/mu-indexer.cc @@ -32,10 +32,13 @@ #include using namespace std::chrono_literals; +#include "mu-store.hh" + #include "mu-scanner.hh" #include "utils/mu-async-queue.hh" #include "utils/mu-error.hh" -#include "../mu-store.hh" + +#include "utils/mu-utils-file.hh" using namespace Mu; @@ -544,12 +547,106 @@ test_index_basic() } } + +static void +test_index_lazy() +{ + allow_warnings(); + + TempDir tdir; + auto store = Store::make_new(tdir.path(), MU_TESTMAILDIR2); + assert_valid_result(store); + g_assert_true(store->empty()); + Indexer& idx{store->indexer()}; + + Indexer::Config conf{}; + conf.lazy_check = true; + conf.ignore_noupdate = false; + + const auto start{time({})}; + g_assert_true(idx.start(conf)); + while (idx.is_running()) + g_usleep(10000); + + g_assert_false(idx.is_running()); + g_assert_true(idx.stop()); + + g_assert_cmpuint(idx.completed() - start, <, 3); + + const auto& prog{idx.progress()}; + g_assert_false(prog.running); + g_assert_cmpuint(prog.checked,==, 6); + g_assert_cmpuint(prog.updated,==, 6); + g_assert_cmpuint(prog.removed,==, 0); + + g_assert_cmpuint(store->size(),==, 6); +} + +static void +test_index_cleanup() +{ + allow_warnings(); + + TempDir tdir; + auto mdir = join_paths(tdir.path(), "Test"); + { + auto res = run_command({"cp", "-r", MU_TESTMAILDIR2, mdir}); + assert_valid_result(res); + g_assert_cmpuint(res->exit_code,==, 0); + } + + auto store = Store::make_new(tdir.path(), mdir); + assert_valid_result(store); + g_assert_true(store->empty()); + Indexer& idx{store->indexer()}; + + Indexer::Config conf{}; + conf.ignore_noupdate = true; + + g_assert_true(idx.start(conf)); + while (idx.is_running()) + g_usleep(10000); + + g_assert_false(idx.is_running()); + g_assert_true(idx.stop()); + g_assert_cmpuint(store->size(),==, 14); + + // remove a message + { + auto mpath = join_paths(mdir, "bar", "cur", "mail6"); + auto res = run_command({"rm", mpath}); + assert_valid_result(res); + g_assert_cmpuint(res->exit_code,==, 0); + } + + // no cleanup, # stays the same + conf.cleanup = false; + g_assert_true(idx.start(conf)); + while (idx.is_running()) + g_usleep(10000); + g_assert_false(idx.is_running()); + g_assert_true(idx.stop()); + g_assert_cmpuint(store->size(),==, 14); + + // cleanup, message is gone from store. + conf.cleanup = true; + g_assert_true(idx.start(conf)); + while (idx.is_running()) + g_usleep(10000); + g_assert_false(idx.is_running()); + g_assert_true(idx.stop()); + g_assert_cmpuint(store->size(),==, 13); +} + + int main(int argc, char* argv[]) { mu_test_init(&argc, &argv); g_test_add_func("/index/basic", test_index_basic); + g_test_add_func("/index/lazy", test_index_lazy); + g_test_add_func("/index/cleanup", test_index_cleanup); return g_test_run(); diff --git a/lib/mu-maildir.cc b/lib/mu-maildir.cc index 68685c52..4a0b2a73 100644 --- a/lib/mu-maildir.cc +++ b/lib/mu-maildir.cc @@ -205,23 +205,20 @@ clear_links(const std::string& path, DIR* dir) switch(d_type) { case DT_LNK: if (::unlink(fullpath.c_str()) != 0) { - mu_warning("error unlinking {}: {}", - fullpath, g_strerror(errno)); + mu_warning("error unlinking {}: {}", fullpath, g_strerror(errno)); res = false; } break; case DT_DIR: { DIR* subdir{::opendir(fullpath.c_str())}; if (!subdir) { - mu_warning("failed to open dir {}: {}", fullpath, - g_strerror(errno)); + mu_warning("error opening dir {}: {}", fullpath, g_strerror(errno)); res = false; } if (!clear_links(fullpath, subdir)) res = false; ::closedir(subdir); - } - break; + } break; default: break; } @@ -289,7 +286,7 @@ msg_move_g_file(const std::string& src, const std::string& dst) else return Err(Error::Code::File, &err, "error moving {} -> {}", src, dst); } -/* LCOV_EXCL_STOPT*/ +/* LCOV_EXCL_STOP*/ /* use mv to move files; this is slower than rename() so only use this when * needed: when moving across filesystems */ @@ -323,10 +320,11 @@ msg_move(const std::string& src, const std::string& dst, bool assume_remote) if (::rename(src.c_str(), dst.c_str()) == 0) /* seems it worked; double-check */ return msg_move_verify(src, dst); - + /* LCOV_EXCL_START*/ if (errno != EXDEV) /* some unrecoverable error occurred */ return Err(Error{Error::Code::File, "error moving {} -> {}: {}", src, dst, strerror(errno)}); + /* LCOV_EXCL_STOP*/ } /* the EXDEV / assume-remote case -- source and target live on different @@ -461,12 +459,7 @@ Mu::maildir_determine_target(const std::string& old_path, const auto dst_file{determine_dst_filename(src_file, newflags, new_name)}; /* and the complete path name. */ - const auto subdir = std::invoke([&]()->std::string { - if (none_of(newflags & Flags::New)) - return "cur"; - else - return "new"; - }); + const std::string subdir{(none_of(newflags & Flags::New)) ? "cur" : "new"}; return join_paths(dst_mdir, subdir,dst_file); } diff --git a/lib/mu-query-xapianizer.cc b/lib/mu-query-xapianizer.cc index e63c83a0..249dda26 100644 --- a/lib/mu-query-xapianizer.cc +++ b/lib/mu-query-xapianizer.cc @@ -264,14 +264,12 @@ parse_basic(const Field &field, Sexp &&vals, Mu::ParserFlags flags) if (auto&& finfo{flag_info(val)}; finfo) return Xapian::Query{field.xapian_term(finfo->shortcut_lower())}; else - return Err(Error::Code::InvalidArgument, - "invalid flag '{}'", val); + return Err(Error::Code::InvalidArgument, "invalid flag '{}'", val); case Field::Id::Priority: if (auto&& prio{priority_from_name(val)}; prio) return Xapian::Query{field.xapian_term(to_char(*prio))}; else - return Err(Error::Code::InvalidArgument, - "invalid priority '{}'", val); + return Err(Error::Code::InvalidArgument, "invalid priority '{}'", val); default: { auto q{Xapian::Query{field.xapian_term(val)}}; if (ngrams) { // special case: cjk; see if we can create an expanded query. @@ -328,8 +326,7 @@ parse(const Store& store, Sexp&& s, Mu::ParserFlags flags) return parse_field_matcher(store, *field, *match_sym, std::move(*args)); } - return Err(Error::Code::InvalidArgument, - "unexpected sexp {}", s.to_string()); + return Err(Error::Code::InvalidArgument, "unexpected sexp {}", s.to_string()); } /* LCOV_EXCL_START */ diff --git a/lib/mu-scanner.cc b/lib/mu-scanner.cc index 89cfb6b7..3f006462 100644 --- a/lib/mu-scanner.cc +++ b/lib/mu-scanner.cc @@ -250,14 +250,12 @@ Scanner::Private::start() { const auto mode{F_OK | R_OK}; if (G_UNLIKELY(::access(root_dir_.c_str(), mode) != 0)) - return Err(Error::Code::File, - "'{}' is not readable: {}", root_dir_, + return Err(Error::Code::File, "'{}' is not readable: {}", root_dir_, g_strerror(errno)); struct stat statbuf {}; if (G_UNLIKELY(::stat(root_dir_.c_str(), &statbuf) != 0)) - return Err(Error::Code::File, - "'{}' is not stat'able: {}", + return Err(Error::Code::File, "'{}' is not stat'able: {}", root_dir_, g_strerror(errno)); if (G_UNLIKELY(!S_ISDIR(statbuf.st_mode))) @@ -322,6 +320,7 @@ Scanner::is_running() const #if BUILD_TESTS +/* LCOV_EXCL_START*/ #include "mu-test-utils.hh" static void @@ -334,11 +333,15 @@ test_scan_maildirs() MU_TESTMAILDIR, [&](const std::string& fullpath, const struct stat* statbuf, auto&& htype) -> bool { ++count; + g_usleep(10000); return true; }}; - g_assert_true(scanner.start()); + assert_valid_result(scanner.start()); + scanner.stop(); + count = 0; + assert_valid_result(scanner.start()); - while (scanner.is_running()) { g_usleep(1000); } + while (scanner.is_running()) { g_usleep(100000); } // very rudimentary test... g_assert_cmpuint(count,==,23); @@ -356,7 +359,7 @@ test_count_maildirs() dirs.emplace_back(basename(fullpath)); return true; }, Scanner::Mode::MaildirsOnly}; - g_assert_true(scanner.start()); + assert_valid_result(scanner.start()); while (scanner.is_running()) { g_usleep(1000); } @@ -366,14 +369,27 @@ test_count_maildirs() g_assert_true(seq_find_if(dirs, [](auto& p){return p == "wom_bat";}) != dirs.end()); } +static void +test_fail_nonexistent() +{ + allow_warnings(); + + Scanner scanner{"/foo/bar/non-existent", + [&](auto&& a1, auto&& a2, auto&& a3){ return false; }}; + g_assert_false(scanner.is_running()); + g_assert_false(!!scanner.start()); + g_assert_false(scanner.is_running()); +} + int main(int argc, char* argv[]) { mu_test_init(&argc, &argv); - g_test_add_func("/index/scan-maildirs", test_scan_maildirs); - g_test_add_func("/index/count-maildirs", test_count_maildirs); + g_test_add_func("/scanner/scan-maildirs", test_scan_maildirs); + g_test_add_func("/scanner/count-maildirs", test_count_maildirs); + g_test_add_func("/scanner/fail-nonexistent", test_fail_nonexistent); return g_test_run(); } @@ -402,4 +418,5 @@ main (int argc, char *argv[]) return 0; } +/* LCOV_EXCL_STOP*/ #endif /*BUILD_LIST_MAILDIRS*/ diff --git a/lib/mu-store.hh b/lib/mu-store.hh index 6ac96d17..e8c99bba 100644 --- a/lib/mu-store.hh +++ b/lib/mu-store.hh @@ -285,7 +285,6 @@ public: DupFlags = 1 << 1, /**< Update flags for duplicate messages too*/ }; - /** * Move a message both in the filesystem and in the store. After a * successful move, the message is updated. diff --git a/lib/mu-xapian-db.hh b/lib/mu-xapian-db.hh index 64a24f90..2566007d 100644 --- a/lib/mu-xapian-db.hh +++ b/lib/mu-xapian-db.hh @@ -111,6 +111,13 @@ struct MetadataIface { /// In-memory db struct MemDb: public MetadataIface { + /** + * Create a new memdb + * + * @param readonly read-only? (for testing) + */ + MemDb(bool readonly=false):read_only_{readonly} {} + /** * Set some metadata * @@ -141,7 +148,7 @@ struct MemDb: public MetadataIface { * * @return true or false */ - bool read_only() const override { return false; } + bool read_only() const override { return read_only_; } /** @@ -157,6 +164,7 @@ struct MemDb: public MetadataIface { private: std::unordered_map map_; + const bool read_only_; }; /** @@ -171,8 +179,8 @@ public: * */ enum struct Flavor { - ReadOnly, /**< Read-only database */ - Open, /**< Open existing read-write */ + ReadOnly, /**< Read-only database */ + Open, /**< Open existing read-write */ CreateOverwrite, /**< Create new or overwrite existing */ }; diff --git a/lib/tests/test-mu-maildir.cc b/lib/tests/test-mu-maildir.cc index c14b753f..639a1342 100644 --- a/lib/tests/test-mu-maildir.cc +++ b/lib/tests/test-mu-maildir.cc @@ -363,8 +363,9 @@ test_maildir_get_new_path_02(void) } } + static void -test_maildir_get_new_path_custom(void) +test_maildir_get_new_path_custom_real(bool change_name) { struct { std::string oldpath; @@ -393,12 +394,30 @@ test_maildir_get_new_path_custom(void) paths[1].root_maildir, paths[i].targetdir, paths[i].flags, - false)}; + change_name)}; assert_valid_result(newpath); - assert_equal(*newpath, paths[i].newpath); + if (change_name) + g_assert_true(*newpath != paths[i].newpath); // weak test + else + assert_equal(*newpath, paths[i].newpath); } } + +static void +test_maildir_get_new_path_custom(void) +{ + return test_maildir_get_new_path_custom_real(false); +} + + +static void +test_maildir_get_new_path_custom_change_name(void) +{ + return test_maildir_get_new_path_custom_real(true); +} + + static void test_maildir_from_path(void) { @@ -506,32 +525,30 @@ test_maildir_move_gio() int main(int argc, char* argv[]) { - g_test_init(&argc, &argv, NULL); + mu_test_init(&argc, &argv); /* mu_util_maildir_mkmdir */ - g_test_add_func("/mu-maildir/mu-maildir-mkdir-01", test_maildir_mkdir_01); - g_test_add_func("/mu-maildir/mu-maildir-mkdir-02", test_maildir_mkdir_02); - g_test_add_func("/mu-maildir/mu-maildir-mkdir-03", test_maildir_mkdir_03); - g_test_add_func("/mu-maildir/mu-maildir-mkdir-04", test_maildir_mkdir_04); - g_test_add_func("/mu-maildir/mu-maildir-mkdir-05", test_maildir_mkdir_05); + g_test_add_func("/maildir/mkdir-01", test_maildir_mkdir_01); + g_test_add_func("/maildir/mkdir-02", test_maildir_mkdir_02); + g_test_add_func("/maildir/mkdir-03", test_maildir_mkdir_03); + g_test_add_func("/maildir/mkdir-04", test_maildir_mkdir_04); + g_test_add_func("/maildir/mkdir-05", test_maildir_mkdir_05); - g_test_add_func("/mu-maildir/mu-maildir-determine-target-ok", - test_determine_target_ok); - g_test_add_func("/mu-maildir/mu-maildir-determine-target-fail", - test_determine_target_fail); + g_test_add_func("/maildir/determine-target-ok", test_determine_target_ok); + g_test_add_func("/maildir/determine-target-fail", test_determine_target_fail); // /* get/set flags */ - g_test_add_func("/mu-maildir/mu-maildir-get-new-path-01", test_maildir_get_new_path_01); - g_test_add_func("/mu-maildir/mu-maildir-get-new-path-02", test_maildir_get_new_path_02); - g_test_add_func("/mu-maildir/mu-maildir-get-new-path-custom", - test_maildir_get_new_path_custom); - g_test_add_func("/mu-maildir/mu-maildir-from-path", - test_maildir_from_path); + g_test_add_func("/maildir/get-new-path-01", test_maildir_get_new_path_01); + g_test_add_func("/maildir/get-new-path-02", test_maildir_get_new_path_02); + g_test_add_func("/maildir/get-new-path-custom", test_maildir_get_new_path_custom); + g_test_add_func("/maildir/get-new-path-custom-change-name", + test_maildir_get_new_path_custom_change_name); - g_test_add_func("/mu-maildir/mu-maildir-link", test_maildir_link); + g_test_add_func("/maildir/from-path", test_maildir_from_path); - g_test_add_func("/mu-maildir/mu-maildir-move-vanilla", test_maildir_move_vanilla); - g_test_add_func("/mu-maildir/mu-maildir-move-gio", test_maildir_move_gio); + g_test_add_func("/maildir/link", test_maildir_link); + g_test_add_func("/maildir/move-vanilla", test_maildir_move_vanilla); + g_test_add_func("/maildir/aildir-move-gio", test_maildir_move_gio); return g_test_run(); } diff --git a/lib/tests/test-mu-store.cc b/lib/tests/test-mu-store.cc index fbf553e8..0a525cbd 100644 --- a/lib/tests/test-mu-store.cc +++ b/lib/tests/test-mu-store.cc @@ -505,6 +505,26 @@ test_store_circular_symlink(void) remove_directory(testhome); } +static void +test_store_maildirs() +{ + allow_warnings(); + + TempDir tdir; + auto store = Store::make_new(tdir.path(), MU_TESTMAILDIR2); + assert_valid_result(store); + g_assert_true(store->empty()); + + const auto mdirs = store->maildirs(); + + g_assert_cmpuint(mdirs.size(), ==, 3); + g_assert(seq_some(mdirs, [](auto&& m){return m == "/Foo";})); + g_assert(seq_some(mdirs, [](auto&& m){return m == "/bar";})); + g_assert(seq_some(mdirs, [](auto&& m){return m == "/wom_bat";})); +} + + + static void test_store_fail() @@ -521,6 +541,7 @@ test_store_fail() } } + int main(int argc, char* argv[]) { @@ -534,8 +555,12 @@ main(int argc, char* argv[]) g_test_add_func("/store/message/attachments", test_message_attachments); g_test_add_func("/store/move-dups", test_store_move_dups); + + g_test_add_func("/store/maildirs", test_store_maildirs); + g_test_add_func("/store/index/index-move", test_index_move); g_test_add_func("/store/index/circular-symlink", test_store_circular_symlink); + g_test_add_func("/store/index/fail", test_store_fail); return g_test_run(); diff --git a/lib/utils/meson.build b/lib/utils/meson.build index 1351608f..b45a87a5 100644 --- a/lib/utils/meson.build +++ b/lib/utils/meson.build @@ -87,6 +87,12 @@ test('test-logger', cpp_args: ['-DBUILD_TESTS'], dependencies: [glib_dep, lib_mu_utils_dep, thread_dep ])) +test('test-option', + executable('test-option', 'mu-option.cc', + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [glib_dep, lib_mu_utils_dep ])) + test('test-lang-detector', executable('test-lang-detector', 'mu-lang-detector.cc', install: false, diff --git a/lib/utils/mu-option.cc b/lib/utils/mu-option.cc index a136b861..e096117f 100644 --- a/lib/utils/mu-option.cc +++ b/lib/utils/mu-option.cc @@ -30,3 +30,77 @@ Mu::to_string_opt_gchar(gchar*&& str) return res; } + +#if BUILD_TESTS +#include "mu-test-utils.hh" + +static Option +get_opt_int(bool b) +{ + if (b) + return Some(123); + else + return Nothing; +} + +static void +test_option() +{ + { + const auto oi{get_opt_int(true)}; + g_assert_true(!!oi); + g_assert_cmpint(oi.value(), ==, 123); + } + + { + const auto oi{get_opt_int(false)}; + g_assert_false(!!oi); + g_assert_false(oi.has_value()); + g_assert_cmpint(oi.value_or(456), ==, 456); + } +} + +static void +test_unwrap() +{ + { + auto&& oi{get_opt_int(true)}; + g_assert_cmpint(unwrap(std::move(oi)), ==, 123); + } + + auto ex{0}; + try { + auto&& oi{get_opt_int(false)}; + unwrap(std::move(oi)); + } catch(...) { + ex = 1; + } + + g_assert_cmpuint(ex, ==, 1); +} + +static void +test_opt_gchar() +{ + auto o1{to_string_opt_gchar(g_strdup("boo!"))}; + auto o2{to_string_opt_gchar(nullptr)}; + + g_assert_false(!!o2); + g_assert_true(o1.value() == "boo!"); +} + + + +int +main(int argc, char* argv[]) +{ + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/option/option", test_option); + g_test_add_func("/option/unwrap", test_unwrap); + g_test_add_func("/option/opt-gchar", test_opt_gchar); + + return g_test_run(); +} + +#endif /*BUILD_TESTS*/ diff --git a/lib/utils/mu-regex.cc b/lib/utils/mu-regex.cc index 06028284..cfdeccf0 100644 --- a/lib/utils/mu-regex.cc +++ b/lib/utils/mu-regex.cc @@ -22,8 +22,6 @@ using namespace Mu; -// LCOV_EXCL_STOP - #if BUILD_TESTS #include "mu-test-utils.hh" @@ -75,17 +73,14 @@ test_regex_replace() int main(int argc, char* argv[]) -try { +{ mu_test_init(&argc, &argv); g_test_add_func("/regex/match", test_regex_match); g_test_add_func("/regex/match2", test_regex_match2); g_test_add_func("/regex/replace", test_regex_replace); - return g_test_run(); -} catch (const std::runtime_error& re) { - mu_printerrln("{}", re.what()); - return 1; + return g_test_run(); } #endif /*BUILD_TESTS*/ diff --git a/lib/utils/tests/meson.build b/lib/utils/tests/meson.build index 83d4e492..aea5b3fd 100644 --- a/lib/utils/tests/meson.build +++ b/lib/utils/tests/meson.build @@ -1,4 +1,4 @@ -## Copyright (C) 2021 Dirk-Jan C. Binnema +## Copyright (C) 2021-2023 Dirk-Jan C. Binnema ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -17,12 +17,7 @@ ################################################################################ # tests -# -test('test-option', - executable('test-option', - 'test-option.cc', - install: false, - dependencies: [glib_dep, lib_mu_utils_dep])) + test('test-mu-utils', executable('test-mu-utils', 'test-utils.cc', diff --git a/lib/utils/tests/test-option.cc b/lib/utils/tests/test-option.cc deleted file mode 100644 index 3313afb1..00000000 --- a/lib/utils/tests/test-option.cc +++ /dev/null @@ -1,59 +0,0 @@ -/* -** Copyright (C) 2020 Dirk-Jan C. Binnema -** -** This library is free software; you can redistribute it and/or -** modify it under the terms of the GNU Lesser General Public License -** as published by the Free Software Foundation; either version 2.1 -** of the License, or (at your option) any later version. -** -** This library is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -** Lesser General Public License for more details. -** -** You should have received a copy of the GNU Lesser General Public -** License along with this library; if not, write to the Free -** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA -** 02110-1301, USA. -*/ - -#include "mu-utils.hh" -#include "mu-option.hh" - -using namespace Mu; - -static Option -get_opt_int(bool b) -{ - if (b) - return Some(123); - else - return Nothing; -} - -static void -test_option() -{ - { - const auto oi{get_opt_int(true)}; - g_assert_true(!!oi); - g_assert_cmpint(oi.value(), ==, 123); - } - - { - const auto oi{get_opt_int(false)}; - g_assert_false(!!oi); - g_assert_false(oi.has_value()); - g_assert_cmpint(oi.value_or(456), ==, 456); - } -} - -int -main(int argc, char* argv[]) -{ - g_test_init(&argc, &argv, NULL); - - g_test_add_func("/option/option", test_option); - - return g_test_run(); -} diff --git a/lib/utils/tests/test-regex.cc b/lib/utils/tests/test-regex.cc deleted file mode 100644 index e6bf0836..00000000 --- a/lib/utils/tests/test-regex.cc +++ /dev/null @@ -1,18 +0,0 @@ -/* -** Copyright (C) 2023 Dirk-Jan C. Binnema -** -** This program is free software; you can redistribute it and/or modify it -** under the terms of the GNU General Public License as published by the -** Free Software Foundation; either version 3, or (at your option) any -** later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software Foundation, -** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -** -*/