diff options
author | David Bremner <bremner@debian.org> | 2023-11-28 09:33:47 -0400 |
---|---|---|
committer | David Bremner <bremner@debian.org> | 2023-11-28 09:33:47 -0400 |
commit | 178d0d440d4f8365f9adae0293457b9737a520bd (patch) | |
tree | 7f19fe076680916fd90ccd81bdd17ecfad1ec99c | |
parent | 1cb0c3e6e4eaf15413307280db9dbfeb39b52e78 (diff) | |
parent | abc7c069b91f513ecfafe562cb7b39829250d2df (diff) |
Update to upstream 3.3.2
[git-debrebase anchor: new upstream 3.3.2, merge]
-rw-r--r-- | CMakeLists.txt | 9 | ||||
-rw-r--r-- | NEWS.md | 22 | ||||
-rw-r--r-- | README.md | 75 | ||||
-rw-r--r-- | doc/ledger.1 | 6 | ||||
-rw-r--r-- | doc/ledger3.texi | 36 | ||||
-rw-r--r-- | flake.nix | 53 | ||||
-rw-r--r-- | src/global.h | 9 | ||||
-rw-r--r-- | src/pool.cc | 2 | ||||
-rw-r--r-- | src/pyinterp.cc | 43 | ||||
-rw-r--r-- | src/pyinterp.h | 1 | ||||
-rw-r--r-- | src/system.hh.in | 1 | ||||
-rw-r--r-- | src/textual.cc | 8 | ||||
-rw-r--r-- | src/timelog.cc | 2 | ||||
-rw-r--r-- | src/unistring.h | 3 | ||||
-rw-r--r-- | src/utils.cc | 4 | ||||
-rw-r--r-- | test/regress/2205_a.test | 15 | ||||
-rw-r--r-- | test/regress/2205_b.test | 16 | ||||
-rw-r--r-- | test/regress/2207.test | 14 | ||||
-rw-r--r-- | test/regress/777.test | 8 |
19 files changed, 197 insertions, 130 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 394cf3eb..83a6f89d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,9 +4,9 @@ PROJECT(ledger) set(Ledger_VERSION_MAJOR 3) set(Ledger_VERSION_MINOR 3) -set(Ledger_VERSION_PATCH 0) +set(Ledger_VERSION_PATCH 2) set(Ledger_VERSION_PRERELEASE "") -set(Ledger_VERSION_DATE 20230208) +set(Ledger_VERSION_DATE 20230330) set(Ledger_TEST_TIMEZONE "America/Chicago") @@ -20,7 +20,10 @@ endif() enable_testing() -add_definitions(-std=c++11) +add_definitions( + -std=c++11 + -DBOOST_FILESYSTEM_NO_DEPRECATED +) if (CYGWIN) add_definitions(-U__STRICT_ANSI__) endif() @@ -1,5 +1,27 @@ # Ledger NEWS +## 3.3.2 (2023-03-30) + +- Fix divide by zero (bugs #777 and #2207) + +- Increase string size limit in src/unistring.h assert (bug #2174) + +- Require tzdata for Nix flake build (bug #2213) + +## 3.3.1 (2023-03-03) + +- Fix regression leading to incorrect error about `format` directives (bug #2205) + +- Add information about compile features to `--version` + +- Fix compiler warnings by minimizing the use of deprecated APIs + +- Update flake.nix to match nixpkgs ledger/default.nix + +- Remove unused Python server related code + +- Various documentation improvements + ## 3.3 (2023-02-08) - Use `$PAGER` when environment variable is set (bug #1674) @@ -1,7 +1,7 @@ [![Join the chat at https://gitter.im/use-package/Lobby](https://badges.gitter.im/use-package/Lobby.svg)](https://gitter.im/use-package/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ![Build Status master](https://github.com/ledger/ledger/actions/workflows/cmake.yml/badge.svg) [![Status](https://img.shields.io/badge/status-active-brightgreen.svg?style=flat)](https://github.com/ledger/ledger/pulse/monthly) -[![License](https://img.shields.io/badge/license-BSD-blue.svg?style=flat)](http://opensource.org/licenses/BSD-3-Clause) +[![License](https://img.shields.io/badge/license-BSD-blue.svg?style=flat)](https://opensource.org/licenses/BSD-3-Clause) [![GitHub release](https://img.shields.io/github/release/ledger/ledger.svg?style=flat)](https://github.com/ledger/ledger/releases) # Ledger: Command-Line Accounting @@ -52,22 +52,25 @@ script: $ ./acprep dependencies +Note that some features, e.g. `--import` require building Ledger with +Python support. + If that doesn't completely work, here are the dependencies for building the current `master` branch: -Dependency | Version (or greater) ------------|--------------------- -[Boost] | 1.49 -[GMP] | 4.2.2 -[MPFR] | 2.4.0 -[utfcpp] | 2.3.4 -[gettext] | 0.17 _optional_ -[libedit] | 20090111-3.0 _optional_ -[Python] | 2.4 _optional_ -[doxygen] | 1.5.7.1 _optional_, for `make docs` -[graphviz] | 2.20.3 _optional_, for `make docs` -[texinfo] | 4.13 _optional_, for `make docs` -[lcov] | 1.6 _optional_, for `make report`, used with `/./acprep gcov` +Dependency | Version (or greater) +------------|--------------------- +[Boost] | 1.49 +[GMP] | 4.2.2 +[MPFR] | 2.4.0 +[utfcpp] | 2.3.4 +[gettext] | 0.17 _optional_ +[libedit] | 20090111-3.0 _optional_ +[Python] | 3.9 _optional_ +[doxygen] | 1.5.7.1 _optional_, for `make docs` +[graphviz] | 2.20.3 _optional_, for `make docs` +[texinfo] | 4.13 _optional_, for `make docs` +[lcov] | 1.6 _optional_, for `make report`, used with `/./acprep gcov` [sloccount] | 2.26 _optional_, for `make sloc` ### macOS @@ -171,30 +174,34 @@ patch (I prefer attachments over pasted text), or to get an account on GitHub. Once you do, fork the [Ledger project][github], hack as much as you like, then send me a pull request via GitHub. -[Homepage]: http://ledger-cli.org/ -[documentation]: http://www.ledger-cli.org/docs.html -[mailing list]: http://list.ledger-cli.org/ -[wiki]: http://wiki.ledger-cli.org/ +[Homepage]: https://ledger-cli.org/ +[documentation]: https://www.ledger-cli.org/docs.html +[mailing list]: https://list.ledger-cli.org/ +[wiki]: https://wiki.ledger-cli.org/ [IRC]: irc://irc.libera.chat/ledger -[github]: http://github.com/ledger/ledger +[github]: https://github.com/ledger/ledger [ledger/vim-ledger repository]: https://github.com/ledger/vim-ledger -[Homebrew]: http://brew.sh/ +[Homebrew]: https://brew.sh/ [MacPorts]: https://www.macports.org/ -[Boost]: http://boost.org -[GMP]: http://gmplib.org/ -[MPFR]: http://www.mpfr.org/ -[utfcpp]: http://utfcpp.sourceforge.net +[Boost]: https://boost.org +[GMP]: https://gmplib.org/ +[MPFR]: https://www.mpfr.org/ +[utfcpp]: https://utfcpp.sourceforge.net [gettext]: https://www.gnu.org/software/gettext/ -[libedit]: http://thrysoee.dk/editline/ -[Python]: http://python.org -[doxygen]: http://www.doxygen.org/ -[graphviz]: http://graphviz.org/ -[texinfo]: http://www.gnu.org/software/texinfo/ -[lcov]: http://ltp.sourceforge.net/coverage/lcov.php -[sloccount]: http://www.dwheeler.com/sloccount/ -[pcre]: http://www.pcre.org/ -[libofx]: http://libofx.sourceforge.net -[expat]: http://www.libexpat.org +[libedit]: https://thrysoee.dk/editline/ +[Python]: https://python.org +[doxygen]: https://www.doxygen.org/ +[graphviz]: https://graphviz.org/ +[texinfo]: https://www.gnu.org/software/texinfo/ +[lcov]: https://ltp.sourceforge.net/coverage/lcov.php +[sloccount]: https://www.dwheeler.com/sloccount/ +[pcre]: https://www.pcre.org/ +[libofx]: https://libofx.sourceforge.net +[expat]: https://libexpat.github.io +<!-- +xmlsoft url kept as http since its TLS certificate setup is incorrect and browser show +a "This Connection Is Not Private" message. [Last checked: 2023-03-13] +--> [libxml2]: http://xmlsoft.org [openhub]: https://www.openhub.net/p/ledger [conda-forge]: https://conda-forge.org diff --git a/doc/ledger.1 b/doc/ledger.1 index 89b7181b..fab5625a 100644 --- a/doc/ledger.1 +++ b/doc/ledger.1 @@ -257,12 +257,6 @@ The synonyms and .Ic r are also accepted. -.It Ic server -This command requires that Python support be active. If so, it starts up an -.Tn HTTP -server listening for requests on port 9000. This provides an alternate -interface to creating and viewing reports. Note that this is very much a -work-in-progress, and will not be fully functional until a later version. .It Ic select Oo Ar sql-query Oc List all postings matching the .Ar sql-query . diff --git a/doc/ledger3.texi b/doc/ledger3.texi index f272c992..ffd93fbb 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -2890,9 +2890,13 @@ primary date with an equals sign: @end smallexample What this auxiliary date means is entirely up to you. The only use -Ledger has for it is that if you specify @option{--aux-date}, then all -reports and calculations (including pricing) will use the auxiliary -date as if it were the primary date. +Ledger has for it is that if you specify @option{--aux-date} (or +@option{--effective}), then all reports and calculations (including +pricing) will use the auxiliary date as if it were the primary date. + +Note that the @option{--aux-date} option is an alias for +@option{--effective}; for more details on effective dates +@pxref{Effective Dates}. @node Codes, Transaction state, Auxiliary dates, Transactions @section Codes @@ -3108,7 +3112,7 @@ date. * Payee metadata:: @end menu -@node Payee metadata, , Metadata payee, Metadata +@node Payee metadata, , Typed metadata, Metadata @subsection Payee metadata @cindex Payee metadata @findex register @@ -4045,8 +4049,8 @@ This entry accomplishes this. Every month you'll see an automatic $37.50 deficit like you should, while your checking account really knows that it debited $225 this month. -And using the @option{--effective} option, the initial date will be -overridden by the effective dates. +And using the @option{--effective} (or @option{--aux-date}) option, +the initial date will be overridden by the effective dates. @smallexample @c command:6453542 $ ledger --effective register Groceries @@ -4061,6 +4065,10 @@ $ ledger --effective register Groceries 09-Mar-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 225.00 @end smallexample +Note that the @option{--aux-date} option is an alias for +@option{--effective}; for a brief explanation of auxiliary date +@pxref{Auxiliary dates}. + @node Periodic Transactions, Concrete Example of Automated Transactions, Effective Dates, Automated Transactions @subsection Periodic Transactions @findex --budget @@ -4085,15 +4093,15 @@ automated posting at the top of your ledger file: @smallexample @c input:C371854 ; This automated transaction will compute Huqúqu'lláh based on this ; journal's postings. Any accounts that match will affect the -; Liabilities:Huququ'llah account by 19% of the value of that posting. +; Liabilities:Huqúqu'lláh account by 19% of the value of that posting. = /^(?:Income:|Expenses:(?:Business|Rent$|Furnishings|Taxes|Insurance))/ - (Liabilities:Huququ'llah) 0.19 + (Liabilities:Huqúqu'lláh) 0.19 @end smallexample This automated posting works by looking at each posting in the ledger file. If any match the given value expression, 19% of the -posting's value is applied to the @samp{Liabilities:Huququ'llah} +posting's value is applied to the @samp{Liabilities:Huqúqu'lláh} account. So, if $1000 is earned from @samp{Income:Salary}, $190 is added to @samp{Liabilities:Huqúqu'lláh}; if $1000 is spent on Rent, $190 is subtracted. @@ -4111,11 +4119,11 @@ $190 is subtracted. The ultimate balance of Huqúqu'lláh reflects how much is owed in order to fulfill one's obligation to Huqúqu'lláh. When ready to pay, just write a check to cover the amount shown in -@samp{Liabilities:Huququ'llah}. That transaction would look like: +@samp{Liabilities:Huqúqu'lláh}. That transaction would look like: @smallexample @c input:validate -2003/01/01 (101) Baha'i Huqúqu'lláh Trust - Liabilities:Huququ'llah $1,000.00 +2003/01/01 (101) Bahá'í Huqúqu'lláh Trust + Liabilities:Huqúqu'lláh $1,000.00 Assets:Checking @end smallexample @@ -4123,11 +4131,11 @@ That's it. To see how much Huqúq is currently owed based on your ledger transactions, use: @smallexample @c command:C371854 -$ ledger balance Liabilities:Huquq +$ ledger balance Liabilities:Huqúq @end smallexample @smallexample @c output:C371854 - $-95 Liabilities:Huququ'llah + $-95 Liabilities:Huqúqu'lláh @end smallexample This works fine, but omits one aspect of the law: that Huqúq is only @@ -1,9 +1,10 @@ { description = "A double-entry accounting system with a command-line reporting interface"; + nixConfig.bash-prompt = "ledger$ "; outputs = { self, nixpkgs }: let - usePython = false; - useGpgme = true; + usePython = true; + gpgmeSupport = true; forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; }); systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; @@ -11,36 +12,43 @@ packages = forAllSystems (system: let pkgs = nixpkgsFor.${system}; - in { - ledger = pkgs.stdenv.mkDerivation { + in with pkgs; { + ledger = stdenv.mkDerivation { pname = "ledger"; - version = "3.3.0-${self.shortRev or "dirty"}"; + version = "3.3.2-${self.shortRev or "dirty"}"; src = self; - nativeBuildInputs = with pkgs; [ cmake ]; - buildInputs = with pkgs; [ - (boost.override { enablePython = usePython; python = python3; }) - gmp mpfr libedit texinfo gnused - ] - ++ pkgs.lib.optional usePython python3 - ++ pkgs.lib.optional useGpgme gpgme; + outputs = [ "out" "dev" ] ++ lib.optionals usePython [ "py" ]; + + buildInputs = [ + gmp mpfr libedit gnused + ] ++ lib.optionals gpgmeSupport [ + gpgme + ] ++ (if usePython + then [ python3 (boost.override { enablePython = true; python = python3; }) ] + else [ boost ]); + + nativeBuildInputs = [ cmake texinfo tzdata ]; enableParallelBuilding = true; cmakeFlags = [ "-DCMAKE_INSTALL_LIBDIR=lib" - (pkgs.lib.optionalString usePython "-DUSE_PYTHON:BOOL=ON") - (pkgs.lib.optionalString useGpgme "-DUSE_GPGME:BOOL=ON") + "-DBUILD_DOCS:BOOL=ON" + "-DUSE_PYTHON:BOOL=${if usePython then "ON" else "OFF"}" + "-DUSE_GPGME:BOOL=${if gpgmeSupport then "ON" else "OFF"}" ]; # by default, it will query the python interpreter for its sitepackages location # however, that would write to a different nixstore path, pass our own sitePackages location - prePatch = pkgs.lib.optionalString usePython '' + prePatch = lib.optionalString usePython '' substituteInPlace src/CMakeLists.txt \ - --replace 'DESTINATION ''${Python_SITEARCH}' 'DESTINATION "lib/${pkgs.python3.sitePackages}"' + --replace 'DESTINATION ''${Python_SITEARCH}' 'DESTINATION "${placeholder "py"}/${python3.sitePackages}"' ''; + installTargets = [ "doc" "install" ]; + checkPhase = '' export LD_LIBRARY_PATH=$PWD export DYLD_LIBRARY_PATH=$PWD @@ -49,20 +57,19 @@ doCheck = true; - meta = { - homepage = "http://ledger-cli.org/"; + meta = with lib; { description = "A double-entry accounting system with a command-line reporting interface"; - license = pkgs.lib.licenses.bsd3; - + homepage = "https://ledger-cli.org/"; + changelog = "https://github.com/ledger/ledger/raw/v${version}/NEWS.md"; + license = lib.licenses.bsd3; longDescription = '' Ledger is a powerful, double-entry accounting system that is accessed from the UNIX command-line. This may put off some users, as there is no flashy UI, but for those who want unparalleled reporting access to their data, there really is no alternative. ''; - - platforms = pkgs.lib.platforms.all; - maintainers = with pkgs.lib.maintainers; [ jwiegley ]; + platforms = lib.platforms.all; + maintainers = with maintainers; [ jwiegley ]; }; }; }); diff --git a/src/global.h b/src/global.h index 036765b8..5a29796d 100644 --- a/src/global.h +++ b/src/global.h @@ -128,6 +128,15 @@ public: if (Ledger_VERSION_DATE != 0) out << '-' << Ledger_VERSION_DATE; out << _(", the command-line accounting tool"); + out << _("\nwith"); +#if !HAVE_GPGME + out << _("out"); +#endif + out << _(" support for gpg encrypted journals and with"); +#if !HAVE_BOOST_PYTHON + out <<_("out"); +#endif + out << _(" Python support"); out << _("\n\nCopyright (c) 2003-2023, John Wiegley. All rights reserved.\n\n\ This program is made available under the terms of the BSD Public License.\n\ diff --git a/src/pool.cc b/src/pool.cc index 2c055d64..73e76644 100644 --- a/src/pool.cc +++ b/src/pool.cc @@ -257,7 +257,7 @@ commodity_pool_t::exchange(const amount_t& amount, current_annotation = &as_annotated_commodity(commodity).details; amount_t per_unit_cost = - (is_per_unit || amount.is_realzero()) ? cost.abs() : (cost / amount).abs(); + (is_per_unit || amount.is_zero()) ? cost.abs() : (cost / amount).abs(); if (! cost.has_commodity()) per_unit_cost.clear_commodity(); diff --git a/src/pyinterp.cc b/src/pyinterp.cc index 43859b84..6a261609 100644 --- a/src/pyinterp.cc +++ b/src/pyinterp.cc @@ -362,44 +362,6 @@ value_t python_interpreter_t::python_command(call_scope_t& args) return NULL_VALUE; } -value_t python_interpreter_t::server_command(call_scope_t& args) -{ - if (! is_initialized) - initialize(); - - python::object server_module; - - try { - server_module = python::import("ledger.server"); - if (! server_module) - throw_(std::runtime_error, - _("Could not import ledger.server; please check your PYTHONPATH")); - } - catch (const error_already_set&) { - PyErr_Print(); - throw_(std::runtime_error, - _("Could not import ledger.server; please check your PYTHONPATH")); - } - - if (python::object main_function = server_module.attr("main")) { - functor_t func(main_function, "main"); - try { - func(args); - return true; - } - catch (const error_already_set&) { - PyErr_Print(); - throw_(std::runtime_error, - _("Error while invoking ledger.server's main() function")); - } - } else { - throw_(std::runtime_error, - _("The ledger.server module is missing its main() function!")); - } - - return false; -} - option_t<python_interpreter_t> * python_interpreter_t::lookup_option(const char * p) { @@ -474,11 +436,6 @@ expr_t::ptr_op_t python_interpreter_t::lookup(const symbol_t::kind_t kind, if (is_eq(p, "python")) return MAKE_FUNCTOR(python_interpreter_t::python_command); break; - - case 's': - if (is_eq(p, "server")) - return MAKE_FUNCTOR(python_interpreter_t::server_command); - break; } } diff --git a/src/pyinterp.h b/src/pyinterp.h index b85d7ce8..c19adfc2 100644 --- a/src/pyinterp.h +++ b/src/pyinterp.h @@ -106,7 +106,6 @@ public: } value_t python_command(call_scope_t& scope); - value_t server_command(call_scope_t& args); class functor_t { functor_t(); diff --git a/src/system.hh.in b/src/system.hh.in index f473bb8b..60ea0fd8 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -148,6 +148,7 @@ #include <boost/filesystem/exception.hpp> #include <boost/filesystem/fstream.hpp> #include <boost/filesystem/operations.hpp> +#include <boost/filesystem/directory.hpp> #include <boost/filesystem/path.hpp> #include <boost/foreach.hpp> diff --git a/src/textual.cc b/src/textual.cc index 62007abb..5276c92b 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -1132,10 +1132,12 @@ void instance_t::commodity_format_directive(commodity_t& comm, string format) // observational formatting. trim(format); amount_t amt; - amt.parse(format); + amt.parse(format, PARSE_NO_REDUCE); if (amt.commodity() != comm) - throw_(parse_error, _f("commodity directive symbol %1% and format directive symbol %2% should be the same") % - comm.symbol() % amt.commodity().symbol()); + throw_(parse_error, + _f("commodity directive symbol %1% and format directive symbol %2% should be the same") + % comm.symbol() + % amt.commodity().symbol()); amt.commodity().add_flags(COMMODITY_STYLE_NO_MIGRATE); VERIFY(amt.valid()); } diff --git a/src/timelog.cc b/src/timelog.cc index 67791bd1..99c2dd36 100644 --- a/src/timelog.cc +++ b/src/timelog.cc @@ -55,7 +55,7 @@ namespace { curr->append_note(in_event.note.c_str(), *context.scope); char buf[32]; - std::sprintf(buf, "%lds", long((out_event.checkin - in_event.checkin) + std::snprintf(buf, 32, "%lds", long((out_event.checkin - in_event.checkin) .total_seconds())); amount_t amt; amt.parse(buf); diff --git a/src/unistring.h b/src/unistring.h index 3daf5357..4a468a98 100644 --- a/src/unistring.h +++ b/src/unistring.h @@ -69,7 +69,8 @@ public: const char * p = input.c_str(); std::size_t len = input.length(); - assert(len < 1024); + // This size should be at least as large as MAX_LINE in context.h + assert(len < 4096); VERIFY(utf8::is_valid(p, p + len)); utf8::unchecked::utf8to32(p, p + len, std::back_inserter(utf32chars)); diff --git a/src/utils.cc b/src/utils.cc index e6147f09..1d457891 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -849,7 +849,11 @@ path resolve_path(const path& pathname) path temp = pathname; if (temp.string()[0] == '~') temp = expand_path(temp); +#if (BOOST_VERSION >= 106000) + temp.lexically_normal(); +#else temp.normalize(); +#endif return temp; } diff --git a/test/regress/2205_a.test b/test/regress/2205_a.test new file mode 100644 index 00000000..5d1ca8cf --- /dev/null +++ b/test/regress/2205_a.test @@ -0,0 +1,15 @@ + +commodity h + format 1,000.00 h + default + +2023-03-03 * Opening balance + Assets:Time 100 h + Equity:Opening balance + +test bal + 100.00 h Assets:Time + -100.00 h Equity:Opening balance +-------------------- + 0 +end test diff --git a/test/regress/2205_b.test b/test/regress/2205_b.test new file mode 100644 index 00000000..0b0a8411 --- /dev/null +++ b/test/regress/2205_b.test @@ -0,0 +1,16 @@ + +C 1.00000000 BTC = 100000000 sat + +commodity BTC + format 1.00000000 BTC + +2023-03-03 * Opening balance + Assets:Investments 1.00 BTC + Equity:Opening balance + +test bal + 1.00000000 BTC Assets:Investments + -1.00000000 BTC Equity:Opening balance +-------------------- + 0 +end test diff --git a/test/regress/2207.test b/test/regress/2207.test new file mode 100644 index 00000000..38695998 --- /dev/null +++ b/test/regress/2207.test @@ -0,0 +1,14 @@ + +commodity FOO + format 1,000.00 FOO + +commodity $ + format $1,000.00 + +2023/01/03 * Transaction + Assets:Brokerage -0.003 FOO @ $16.79 + Assets:Checking + +test bal + $0.05 Assets:Checking +end test diff --git a/test/regress/777.test b/test/regress/777.test new file mode 100644 index 00000000..fbb81a70 --- /dev/null +++ b/test/regress/777.test @@ -0,0 +1,8 @@ + +2012-07-01 * Test + A (1/9 FOO) @ 200.00 EUR + B -22.22 EUR + +test bal + -22.22 EUR B +end test |