mirror of
https://github.com/simonmichael/hledger.git
synced 2024-11-07 21:15:19 +03:00
;doc: move more content out of CONTRIBUTING
This commit is contained in:
parent
0fc4181456
commit
d62d302fdc
152
BENCHMARKS.md
Normal file
152
BENCHMARKS.md
Normal file
@ -0,0 +1,152 @@
|
||||
# Benchmarks
|
||||
|
||||
Benchmarks are standard performance measurements,
|
||||
which we define using `bench` declarations in cabal files.
|
||||
There is [one in hledger.cabal](https://github.com/simonmichael/hledger/blob/master/hledger/hledger.cabal#L228),
|
||||
with related code and data files in [hledger/bench/](https://github.com/simonmichael/hledger/tree/master/hledger/bench).
|
||||
|
||||
To run the standard hledger benchmark, use `stack bench hledger`.
|
||||
This installs haskell dependencies (but not system dependencies) and rebuilds as needed,
|
||||
then runs [hledger/bench/bench.hs](https://github.com/simonmichael/hledger/blob/master/hledger/bench/bench.hs),
|
||||
which by default shows quick elapsed-time measurements for several operations on a standard data file:
|
||||
|
||||
```shell
|
||||
$ stack bench hledger
|
||||
NOTE: the bench command is functionally equivalent to 'build --bench'
|
||||
...
|
||||
hledger-0.27: benchmarks
|
||||
Running 1 benchmarks...
|
||||
Benchmark bench: RUNNING...
|
||||
Benchmarking hledger in /Users/simon/src/hledger/hledger with timeit
|
||||
read bench/10000x1000x10.journal [1.57s]
|
||||
print [1.29s]
|
||||
register [1.92s]
|
||||
balance [0.21s]
|
||||
stats [0.23s]
|
||||
Total: 5.22s
|
||||
Benchmark bench: FINISH
|
||||
```
|
||||
|
||||
bench.hs has some other modes, which you can use by compiling and running it directly.
|
||||
`--criterion` reports more detailed and dependable measurements, but takes longer:
|
||||
|
||||
```shell
|
||||
$ cd hledger; stack exec -- ghc -ibench bench/bench && bench/bench --criterion
|
||||
...
|
||||
Linking bench/bench ...
|
||||
Benchmarking hledger in /Users/simon/src/hledger/hledger with criterion
|
||||
benchmarking read bench/10000x1000x10.journal
|
||||
time 1.414 s (1.234 s .. 1.674 s)
|
||||
0.996 R² (0.989 R² .. 1.000 R²)
|
||||
mean 1.461 s (1.422 s .. 1.497 s)
|
||||
std dev 59.69 ms (0.0 s .. 62.16 ms)
|
||||
variance introduced by outliers: 19% (moderately inflated)
|
||||
|
||||
benchmarking print
|
||||
time 1.323 s (1.279 s .. 1.385 s)
|
||||
1.000 R² (0.999 R² .. 1.000 R²)
|
||||
mean 1.305 s (1.285 s .. 1.316 s)
|
||||
std dev 17.20 ms (0.0 s .. 19.14 ms)
|
||||
variance introduced by outliers: 19% (moderately inflated)
|
||||
|
||||
benchmarking register
|
||||
time 1.995 s (1.883 s .. 2.146 s)
|
||||
0.999 R² (0.998 R² .. NaN R²)
|
||||
mean 1.978 s (1.951 s .. 1.995 s)
|
||||
std dev 25.09 ms (0.0 s .. 28.26 ms)
|
||||
variance introduced by outliers: 19% (moderately inflated)
|
||||
|
||||
benchmarking balance
|
||||
time 251.3 ms (237.6 ms .. 272.4 ms)
|
||||
0.998 R² (0.997 R² .. 1.000 R²)
|
||||
mean 260.4 ms (254.3 ms .. 266.5 ms)
|
||||
std dev 7.609 ms (3.192 ms .. 9.638 ms)
|
||||
variance introduced by outliers: 16% (moderately inflated)
|
||||
|
||||
benchmarking stats
|
||||
time 325.5 ms (299.1 ms .. 347.2 ms)
|
||||
0.997 R² (0.985 R² .. 1.000 R²)
|
||||
mean 329.2 ms (321.5 ms .. 339.6 ms)
|
||||
std dev 11.08 ms (2.646 ms .. 14.82 ms)
|
||||
variance introduced by outliers: 16% (moderately inflated)
|
||||
```
|
||||
|
||||
`--simplebench` shows a table of elapsed-time measurements for the commands defined in [bench/default.bench](https://github.com/simonmichael/hledger/blob/master/hledger/bench/default.bench).
|
||||
It can also show the results for multiple h/ledger executables side by side, if you tweak the bench.hs code.
|
||||
Unlike the other modes, it does not link with the hledger code directly, but runs the "hledger" executable found in $PATH (so ensure that's the one you intend to test).
|
||||
|
||||
```shell
|
||||
$ cd hledger; stack exec -- ghc -ibench bench/bench && bench/bench --simplebench
|
||||
Benchmarking /Users/simon/.local/bin/hledger in /Users/simon/src/hledger/hledger with simplebench and shell
|
||||
Using bench/default.bench
|
||||
Running 4 tests 1 times with 1 executables at 2015-08-23 16:58:59.128112 UTC:
|
||||
1: hledger -f bench/10000x1000x10.journal print [3.27s]
|
||||
1: hledger -f bench/10000x1000x10.journal register [3.65s]
|
||||
1: hledger -f bench/10000x1000x10.journal balance [2.06s]
|
||||
1: hledger -f bench/10000x1000x10.journal stats [2.13s]
|
||||
|
||||
Summary (best iteration):
|
||||
|
||||
+-----------------------------------------++---------+
|
||||
| || hledger |
|
||||
+=========================================++=========+
|
||||
| -f bench/10000x1000x10.journal print || 3.27 |
|
||||
| -f bench/10000x1000x10.journal register || 3.65 |
|
||||
| -f bench/10000x1000x10.journal balance || 2.06 |
|
||||
| -f bench/10000x1000x10.journal stats || 2.13 |
|
||||
+-----------------------------------------++---------+
|
||||
```
|
||||
|
||||
bench's --simplebench mode is based on a standalone tool, [tools/simplebench.hs](https://github.com/simonmichael/hledger/blob/master/tools/simplebench.hs).
|
||||
simplebench.hs is a generic benchmarker of one or more executables (specified on the command line) against one or more sets of command-line arguments (specified in a file).
|
||||
It has a better command-line interface than bench.hs, so you may find it more convenient
|
||||
for comparing multiple hledger versions, or hledger and ledger. Eg:
|
||||
|
||||
```shell
|
||||
$ stack exec -- ghc tools/simplebench
|
||||
[1 of 1] Compiling Main ( tools/simplebench.hs, tools/simplebench.o )
|
||||
Linking tools/simplebench ...
|
||||
```
|
||||
```shell
|
||||
$ tools/simplebench -h
|
||||
tools/simplebench -h
|
||||
simplebench: at least one executable needed
|
||||
bench [-f testsfile] [-n iterations] [-p precision] executable1 [executable2 ...]
|
||||
|
||||
Run some functional tests with each of the specified executables,
|
||||
where a test is "zero or more arguments supported by all executables",
|
||||
and report the best execution times.
|
||||
|
||||
-f testsfile --testsfile=testsfile file containing tests, one per line, default: bench.tests
|
||||
-n iterations --iterations=iterations number of test iterations to run, default: 2
|
||||
-p precision --precision=precision show times with this precision, default: 2
|
||||
-v --verbose show intermediate results
|
||||
-h --help show this help
|
||||
|
||||
Tips:
|
||||
- executables may have arguments if enclosed in quotes
|
||||
- tests can be commented out with #
|
||||
- results are saved in benchresults.{html,txt}
|
||||
```
|
||||
```shell
|
||||
cd hledger; $ ../tools/simplebench -f bench/default.bench hledger ledger
|
||||
Using bench/default.bench
|
||||
Running 4 tests 2 times with 2 executables at 2015-08-24 04:24:37.257068 UTC:
|
||||
|
||||
Summary (best iteration):
|
||||
|
||||
+-----------------------------------------++---------+--------+
|
||||
| || hledger | ledger |
|
||||
+=========================================++=========+========+
|
||||
| -f bench/10000x1000x10.journal print || 3.24 | 0.43 |
|
||||
| -f bench/10000x1000x10.journal register || 3.80 | 3.48 |
|
||||
| -f bench/10000x1000x10.journal balance || 2.05 | 0.18 |
|
||||
| -f bench/10000x1000x10.journal stats || 2.10 | 0.19 |
|
||||
+-----------------------------------------++---------+--------+
|
||||
```
|
||||
|
||||
Finally, for quick, fine-grained performance measurements when troubleshooting or optimising, I use
|
||||
[dev.hs](https://github.com/simonmichael/hledger/blob/master/dev.hs).
|
||||
|
||||
|
||||
|
211
CODE.md
Normal file
211
CODE.md
Normal file
@ -0,0 +1,211 @@
|
||||
# Code
|
||||
|
||||
hledger is a suite of applications, tools and libraries.
|
||||
The main hledger code repository is [github.com/simonmichael/hledger](https://github.com/simonmichael/hledger)
|
||||
(shortcut url `code.hledger.org`).
|
||||
There are also various hledger addons maintained as separate projects with their own repos.
|
||||
|
||||
## hledger packages
|
||||
|
||||
Within the main repo, there are a number of separate cabal packages,
|
||||
making it easier to pick and choose parts of hledger to install or to package.
|
||||
They are:
|
||||
|
||||
### hledger-lib
|
||||
|
||||
[package](https://hackage.haskell.org/package/hledger-lib),
|
||||
[code](https://github.com/simonmichael/hledger/tree/master/hledger-lib)
|
||||
|
||||
Core data models, parsing, standard reports, and utilities.
|
||||
Most data types are defined in [Hledger.Data.Types](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html),
|
||||
while functions that operate on them are defined in Hledger.Data.TYPENAME.
|
||||
Under [Hledger.Read](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Read.hs)
|
||||
are parsers for the supported input formats.
|
||||
Data files are parsed into a
|
||||
[Journal](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Journal),
|
||||
which contains a list of
|
||||
[Transactions](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Transaction),
|
||||
each containing multiple
|
||||
[Postings](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Posting)
|
||||
of some
|
||||
[MixedAmount](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:MixedAmount)
|
||||
(multiple
|
||||
single-[CommoditySymbol](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:CommoditySymbol)
|
||||
[Amounts](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Amount))
|
||||
to some
|
||||
[AccountName](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:AccountName).
|
||||
When needed, the Journal is further processed to derive a
|
||||
[Ledger](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Ledger),
|
||||
which contains summed
|
||||
[Accounts](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Account).
|
||||
In [Hledger.Reports](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Reports.html)
|
||||
there are standard reports, which extract useful data from the Journal or Ledger.
|
||||
|
||||
Here's a diagram of the main data model:
|
||||
|
||||
<a href="https://hledger.org/images/data-model.png" class="highslide" onclick="return hs.expand(this)">
|
||||
<img src="https://hledger.org/images/data-model.png" alt="diagram" title="main data types" style="max-width:100%;">
|
||||
</a>
|
||||
|
||||
<!-- generated by plantuml from:
|
||||
<uml>
|
||||
hide empty members
|
||||
hide circle
|
||||
skinparam packageStyle Rect
|
||||
|
||||
Ledger *-- Journal
|
||||
Ledger *-- "*" Account
|
||||
note top of Ledger: A Journal and all its accounts with their balances.\nUsed for balance report
|
||||
note top of Journal: A journal file and parsed transactions & directives.\nUsed for print & register reports
|
||||
note bottom of Account: An account's name, balance (inclusive &\nexclusive), parent and child accounts
|
||||
Account o-- "*" Account :subaccounts, parent
|
||||
Journal o-- File
|
||||
File o-- "*" File :include
|
||||
Journal *-- "*" MarketPrice
|
||||
Journal *-- "*" Transaction
|
||||
MarketPrice -- Date
|
||||
MarketPrice -- Amount
|
||||
Transaction -- Date
|
||||
Transaction *-- "*" Posting
|
||||
Transaction o-- "*" Tag
|
||||
Posting o- "*" Tag
|
||||
Posting -- "0..1" Date
|
||||
Account -- AccountName
|
||||
Posting -- AccountName
|
||||
Account -- "2" MixedAmount
|
||||
Posting -- MixedAmount
|
||||
MixedAmount *-- "*" Amount
|
||||
Amount -- CommoditySymbol
|
||||
Amount -- Quantity
|
||||
Amount -- AmountPrice
|
||||
Amount -- AmountStyle
|
||||
</uml>
|
||||
-->
|
||||
|
||||
### hledger
|
||||
|
||||
[package](https://hackage.haskell.org/package/hledger),
|
||||
[code](https://github.com/simonmichael/hledger/tree/master/hledger),
|
||||
[manual](https://hledger.org/hledger.html)
|
||||
|
||||
hledger's command line interface, and command line options and utilities for other hledger tools.
|
||||
|
||||
Try tracing the execution of a hledger command:
|
||||
|
||||
1. [Hledger.Cli.Main:main](https://github.com/simonmichael/hledger/blob/master/hledger/Hledger/Cli/Main.hs#L302)
|
||||
parses the command line to select a command, then
|
||||
2. gives it to
|
||||
[Hledger.Cli.Utils:withJournalDo](https://github.com/simonmichael/hledger/blob/master/hledger/Hledger/Cli/Utils.hs#L73),
|
||||
which runs it after doing all the initial parsing.
|
||||
3. Parsing code is under
|
||||
[hledger-lib:Hledger.Read](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Read.hs),
|
||||
eg [Hledger.Read.JournalReader](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Read/JournalReader.hs).
|
||||
4. Commands extract useful information from the parsed data model using
|
||||
[hledger-lib:Hledger.Reports](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Reports),
|
||||
and
|
||||
5. render in plain text for console output (or another output format, like CSV).
|
||||
6. Everything uses the data types and utilities from
|
||||
[hledger-lib:Hledger.Data](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Data)
|
||||
and [hledger-lib:Hledger.Utils](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Utils.hs).
|
||||
|
||||
### hledger-ui
|
||||
|
||||
[package](https://hackage.haskell.org/package/hledger-ui),
|
||||
[code](https://github.com/simonmichael/hledger/tree/master/hledger-ui),
|
||||
[manual](https://hledger.org/hledger-ui.html)
|
||||
|
||||
A terminal interface.
|
||||
|
||||
### hledger-web
|
||||
|
||||
[package](https://hackage.haskell.org/package/hledger-web),
|
||||
[code](https://github.com/simonmichael/hledger/tree/master/hledger-web),
|
||||
[manual](https://hledger.org/hledger-web.html)
|
||||
|
||||
A web interface.
|
||||
hledger-web starts a web server built with the yesod framework,
|
||||
and (by default) opens a web browser view on it.
|
||||
It reads the journal file(s) at startup and again whenever they change.
|
||||
It can also write (append) new transactions to the journal file.
|
||||
|
||||
There are two main views, which can be filtered with
|
||||
[queries](https://hledger.org/hledger.html#queries):
|
||||
|
||||
- [/journal](https://demo.hledger.org/journal), showing general journal entries (like `hledger print`)
|
||||
|
||||
- [/register](https://demo.hledger.org/register?q=inacct:Expenses:Food),
|
||||
showing transactions affecting an account (slightly different from
|
||||
hledger's [register](https://hledger.org/hledger.html#register) command, which shows postings).
|
||||
|
||||
There is also:
|
||||
|
||||
- a sidebar (toggled by pressing `s`) showing the chart of accounts (like `hledger balance`)
|
||||
- an [add form](https://demo.hledger.org/journal?add=1) for adding new transactions (press `a`)
|
||||
- a help dialog showing quick help and keybindings (press `h` or click ?)
|
||||
|
||||
Most of the action is in
|
||||
|
||||
- [config/routes](https://github.com/simonmichael/hledger/tree/master/hledger-web/config/routes)
|
||||
- [templates/default-layout-wrapper.hamlet](https://github.com/simonmichael/hledger/tree/master/hledger-web/templates/default-layout-wrapper.hamlet)
|
||||
- [Foundation](https://github.com/simonmichael/hledger/tree/master/hledger-web/Foundation.hs)
|
||||
- [Handler.*](https://github.com/simonmichael/hledger/tree/master/hledger-web/Handler)
|
||||
- [static/hledger.js](https://github.com/simonmichael/hledger/tree/master/hledger-web/static/hledger.js)
|
||||
- [static/hledger.css](https://github.com/simonmichael/hledger/tree/master/hledger-web/static/hledger.css)
|
||||
|
||||
Handler module and function names end with R, like the yesod-generated route type they deal with.
|
||||
|
||||
Dynamically generated page content is mostly inline hamlet.
|
||||
Lucius/Julius files and widgets generally are not used, except for the default layout.
|
||||
|
||||
Here are some ways to run it during development:
|
||||
|
||||
- `yesod devel`: runs in developer mode, rebuilds automatically when config, template, static or haskell files change
|
||||
(but only files in the hledger-web package):
|
||||
```shell
|
||||
$ (cd hledger-web; yesod devel)
|
||||
```
|
||||
|
||||
- [yesod-fast-devel](https://hackage.haskell.org/package/yesod-fast-devel)
|
||||
may be a good alternative, also reloads the browser page
|
||||
|
||||
- `stack ghci`: runs the server in developer mode from GHCI.
|
||||
Changes to static files like hledger.js will be visible on page reload;
|
||||
to see other changes, restart it as shown.
|
||||
```shell
|
||||
$ (cd hledger-web; stack ghci hledger-web)
|
||||
hledger-web> :main --serve # restart: ctrl-c, :r, enter, ctrl-p, ctrl-p, enter
|
||||
```
|
||||
|
||||
- `make ghci-web`: runs the server in developer mode from GHCI, also
|
||||
interprets the hledger-lib and hledger packages so that :reload picks
|
||||
up changes in those packages too:
|
||||
```shell
|
||||
$ make ghci-web
|
||||
ghci> :main --serve
|
||||
```
|
||||
(This rule also creates symbolic links to hledger-web's `config`, `messages`, `static` and `templates`
|
||||
directories, needed in developer mode, so it can run from the top directory. This may not work on Windows.)
|
||||
|
||||
## Quality
|
||||
|
||||
Relevant tools include:
|
||||
|
||||
- unit tests (HUnit, make unittest)
|
||||
- functional tests (shelltestrunner, make functest)
|
||||
- performance tests (simplebench, make bench)
|
||||
- documentation tests (make haddocktest + manual)
|
||||
- ui tests (manual)
|
||||
- installation tests (manual)
|
||||
- code reviews
|
||||
|
||||
## Code review
|
||||
|
||||
- Code review party 2014/7/21-25:
|
||||
[discussion](https://thread.gmane.org/gmane.comp.finance.ledger.hledger/1070)<!-- missing ,
|
||||
[log](https://hledger.org/static/irc-20140725-code-review.html) -->
|
||||
- Dev sprint/party 2015/10/10:
|
||||
[discussion](https://thread.gmane.org/gmane.comp.finance.ledger.hledger/1254)<!-- ircbrowse down ,
|
||||
[pre-chat](https://ircbrowse.net/day/hledger/2015/10/10),
|
||||
[log](https://ircbrowse.net/day/hledger/2015/10/11) -->
|
||||
|
||||
|
885
CONTRIBUTING.md
885
CONTRIBUTING.md
@ -7,6 +7,9 @@
|
||||
This is a collection of old developer docs on all topics.
|
||||
Gradually, these are moving to separate files/pages
|
||||
and this doc is becoming a focussed guide for new contributors.
|
||||
If you are unexpectedly seeing this page after following a link,
|
||||
the content probably moved to a separate page:
|
||||
see the [Developer docs](dev.md).
|
||||
|
||||
## Quick Links
|
||||
|
||||
@ -243,885 +246,3 @@ See [Developer workflows](#developer-workflows).
|
||||
|
||||
|
||||
|
||||
## Make
|
||||
|
||||
A Makefile is provided to make common developer tasks easy to remember,
|
||||
and to insulate us a little from the ever-evolving Haskell tools ecosystem.
|
||||
Using it is entirely optional, but recommended.
|
||||
You'll need [GNU Make](https://www.gnu.org/software/make) installed.
|
||||
|
||||
The Makefile contains a fair amount of obsolete cruft and needs cleanup. Some tasks (docs, website) are now handled by the [Shake](#shake) file instead.
|
||||
|
||||
The Makefile is self-documenting. Run `make` to see a list of the main make rules:
|
||||
|
||||
```shell
|
||||
$ make
|
||||
Makefile:37: -------------------- hledger make rules --------------------
|
||||
Makefile:39: make [help] -- list documented rules in this makefile. make -n RULE shows more detail.
|
||||
Makefile:204: (INSTALLING:)
|
||||
Makefile:206: make install -- download dependencies and install hledger executables to ~/.local/bin or equivalent (with stack)
|
||||
Makefile:231: (BUILDING:)
|
||||
Makefile:235: make build -- download dependencies and build hledger executables (with stack)
|
||||
Makefile:304: make hledgerdev -- quickly build the hledger executable (with ghc and -DDEVELOPMENT)
|
||||
...
|
||||
```
|
||||
|
||||
To see what a make rule will do without actually doing it, use the `-n` flag:
|
||||
|
||||
```shell
|
||||
$ make build -n
|
||||
stack build
|
||||
```
|
||||
```shell
|
||||
$ make test -n
|
||||
(stack test \
|
||||
&& echo pkgtest PASSED) || echo pkgtest FAILED
|
||||
(stack exec hledger test \
|
||||
&& echo builtintest PASSED) || echo builtintest FAILED
|
||||
(COLUMNS=80 PATH=`pwd`/bin:/home/simon/src/hledger/bin:/home/simon/.local/bin:/home/simon/.cabal/bin:/opt/ghc/7.10.1/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/var/lib/gems/1.9.1/bin stack exec -- shelltest --execdir -- -j16 --hide-successes tests \
|
||||
&& echo functest PASSED) || echo functest FAILED
|
||||
```
|
||||
|
||||
|
||||
## Shake
|
||||
|
||||
`Shake.hs` in the top directory complements the Makefile; it is used for some more complex tasks, such as building documentation and the web site.
|
||||
|
||||
Compile it:
|
||||
|
||||
./Shake.hs # or, make Shake
|
||||
|
||||
See help:
|
||||
|
||||
./Shake
|
||||
|
||||
|
||||
## Code
|
||||
|
||||
hledger is a suite of applications, tools and libraries.
|
||||
The main hledger code repository is [github.com/simonmichael/hledger](https://github.com/simonmichael/hledger)
|
||||
(shortcut url `code.hledger.org`).
|
||||
There are also various hledger addons maintained as separate projects with their own repos.
|
||||
|
||||
### hledger packages
|
||||
|
||||
Within the main repo, there are a number of separate cabal packages,
|
||||
making it easier to pick and choose parts of hledger to install or to package.
|
||||
They are:
|
||||
|
||||
#### hledger-lib
|
||||
|
||||
[package](https://hackage.haskell.org/package/hledger-lib),
|
||||
[code](https://github.com/simonmichael/hledger/tree/master/hledger-lib)
|
||||
|
||||
Core data models, parsing, standard reports, and utilities.
|
||||
Most data types are defined in [Hledger.Data.Types](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html),
|
||||
while functions that operate on them are defined in Hledger.Data.TYPENAME.
|
||||
Under [Hledger.Read](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Read.hs)
|
||||
are parsers for the supported input formats.
|
||||
Data files are parsed into a
|
||||
[Journal](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Journal),
|
||||
which contains a list of
|
||||
[Transactions](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Transaction),
|
||||
each containing multiple
|
||||
[Postings](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Posting)
|
||||
of some
|
||||
[MixedAmount](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:MixedAmount)
|
||||
(multiple
|
||||
single-[CommoditySymbol](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:CommoditySymbol)
|
||||
[Amounts](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Amount))
|
||||
to some
|
||||
[AccountName](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:AccountName).
|
||||
When needed, the Journal is further processed to derive a
|
||||
[Ledger](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Ledger),
|
||||
which contains summed
|
||||
[Accounts](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Account).
|
||||
In [Hledger.Reports](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Reports.html)
|
||||
there are standard reports, which extract useful data from the Journal or Ledger.
|
||||
|
||||
Here's a diagram of the main data model:
|
||||
|
||||
<a href="https://hledger.org/images/data-model.png" class="highslide" onclick="return hs.expand(this)">
|
||||
<img src="https://hledger.org/images/data-model.png" alt="diagram" title="main data types" style="max-width:100%;">
|
||||
</a>
|
||||
|
||||
<!-- generated by plantuml from:
|
||||
<uml>
|
||||
hide empty members
|
||||
hide circle
|
||||
skinparam packageStyle Rect
|
||||
|
||||
Ledger *-- Journal
|
||||
Ledger *-- "*" Account
|
||||
note top of Ledger: A Journal and all its accounts with their balances.\nUsed for balance report
|
||||
note top of Journal: A journal file and parsed transactions & directives.\nUsed for print & register reports
|
||||
note bottom of Account: An account's name, balance (inclusive &\nexclusive), parent and child accounts
|
||||
Account o-- "*" Account :subaccounts, parent
|
||||
Journal o-- File
|
||||
File o-- "*" File :include
|
||||
Journal *-- "*" MarketPrice
|
||||
Journal *-- "*" Transaction
|
||||
MarketPrice -- Date
|
||||
MarketPrice -- Amount
|
||||
Transaction -- Date
|
||||
Transaction *-- "*" Posting
|
||||
Transaction o-- "*" Tag
|
||||
Posting o- "*" Tag
|
||||
Posting -- "0..1" Date
|
||||
Account -- AccountName
|
||||
Posting -- AccountName
|
||||
Account -- "2" MixedAmount
|
||||
Posting -- MixedAmount
|
||||
MixedAmount *-- "*" Amount
|
||||
Amount -- CommoditySymbol
|
||||
Amount -- Quantity
|
||||
Amount -- AmountPrice
|
||||
Amount -- AmountStyle
|
||||
</uml>
|
||||
-->
|
||||
|
||||
#### hledger
|
||||
|
||||
[package](https://hackage.haskell.org/package/hledger),
|
||||
[code](https://github.com/simonmichael/hledger/tree/master/hledger),
|
||||
[manual](https://hledger.org/hledger.html)
|
||||
|
||||
hledger's command line interface, and command line options and utilities for other hledger tools.
|
||||
|
||||
Try tracing the execution of a hledger command:
|
||||
|
||||
1. [Hledger.Cli.Main:main](https://github.com/simonmichael/hledger/blob/master/hledger/Hledger/Cli/Main.hs#L302)
|
||||
parses the command line to select a command, then
|
||||
2. gives it to
|
||||
[Hledger.Cli.Utils:withJournalDo](https://github.com/simonmichael/hledger/blob/master/hledger/Hledger/Cli/Utils.hs#L73),
|
||||
which runs it after doing all the initial parsing.
|
||||
3. Parsing code is under
|
||||
[hledger-lib:Hledger.Read](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Read.hs),
|
||||
eg [Hledger.Read.JournalReader](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Read/JournalReader.hs).
|
||||
4. Commands extract useful information from the parsed data model using
|
||||
[hledger-lib:Hledger.Reports](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Reports),
|
||||
and
|
||||
5. render in plain text for console output (or another output format, like CSV).
|
||||
6. Everything uses the data types and utilities from
|
||||
[hledger-lib:Hledger.Data](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Data)
|
||||
and [hledger-lib:Hledger.Utils](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Utils.hs).
|
||||
|
||||
#### hledger-ui
|
||||
|
||||
[package](https://hackage.haskell.org/package/hledger-ui),
|
||||
[code](https://github.com/simonmichael/hledger/tree/master/hledger-ui),
|
||||
[manual](https://hledger.org/hledger-ui.html)
|
||||
|
||||
A terminal interface.
|
||||
|
||||
#### hledger-web
|
||||
|
||||
[package](https://hackage.haskell.org/package/hledger-web),
|
||||
[code](https://github.com/simonmichael/hledger/tree/master/hledger-web),
|
||||
[manual](https://hledger.org/hledger-web.html)
|
||||
|
||||
A web interface.
|
||||
hledger-web starts a web server built with the yesod framework,
|
||||
and (by default) opens a web browser view on it.
|
||||
It reads the journal file(s) at startup and again whenever they change.
|
||||
It can also write (append) new transactions to the journal file.
|
||||
|
||||
There are two main views, which can be filtered with
|
||||
[queries](https://hledger.org/hledger.html#queries):
|
||||
|
||||
- [/journal](https://demo.hledger.org/journal), showing general journal entries (like `hledger print`)
|
||||
|
||||
- [/register](https://demo.hledger.org/register?q=inacct:Expenses:Food),
|
||||
showing transactions affecting an account (slightly different from
|
||||
hledger's [register](https://hledger.org/hledger.html#register) command, which shows postings).
|
||||
|
||||
There is also:
|
||||
|
||||
- a sidebar (toggled by pressing `s`) showing the chart of accounts (like `hledger balance`)
|
||||
- an [add form](https://demo.hledger.org/journal?add=1) for adding new transactions (press `a`)
|
||||
- a help dialog showing quick help and keybindings (press `h` or click ?)
|
||||
|
||||
Most of the action is in
|
||||
|
||||
- [config/routes](https://github.com/simonmichael/hledger/tree/master/hledger-web/config/routes)
|
||||
- [templates/default-layout-wrapper.hamlet](https://github.com/simonmichael/hledger/tree/master/hledger-web/templates/default-layout-wrapper.hamlet)
|
||||
- [Foundation](https://github.com/simonmichael/hledger/tree/master/hledger-web/Foundation.hs)
|
||||
- [Handler.*](https://github.com/simonmichael/hledger/tree/master/hledger-web/Handler)
|
||||
- [static/hledger.js](https://github.com/simonmichael/hledger/tree/master/hledger-web/static/hledger.js)
|
||||
- [static/hledger.css](https://github.com/simonmichael/hledger/tree/master/hledger-web/static/hledger.css)
|
||||
|
||||
Handler module and function names end with R, like the yesod-generated route type they deal with.
|
||||
|
||||
Dynamically generated page content is mostly inline hamlet.
|
||||
Lucius/Julius files and widgets generally are not used, except for the default layout.
|
||||
|
||||
Here are some ways to run it during development:
|
||||
|
||||
- `yesod devel`: runs in developer mode, rebuilds automatically when config, template, static or haskell files change
|
||||
(but only files in the hledger-web package):
|
||||
```shell
|
||||
$ (cd hledger-web; yesod devel)
|
||||
```
|
||||
|
||||
- [yesod-fast-devel](https://hackage.haskell.org/package/yesod-fast-devel)
|
||||
may be a good alternative, also reloads the browser page
|
||||
|
||||
- `stack ghci`: runs the server in developer mode from GHCI.
|
||||
Changes to static files like hledger.js will be visible on page reload;
|
||||
to see other changes, restart it as shown.
|
||||
```shell
|
||||
$ (cd hledger-web; stack ghci hledger-web)
|
||||
hledger-web> :main --serve # restart: ctrl-c, :r, enter, ctrl-p, ctrl-p, enter
|
||||
```
|
||||
|
||||
- `make ghci-web`: runs the server in developer mode from GHCI, also
|
||||
interprets the hledger-lib and hledger packages so that :reload picks
|
||||
up changes in those packages too:
|
||||
```shell
|
||||
$ make ghci-web
|
||||
ghci> :main --serve
|
||||
```
|
||||
(This rule also creates symbolic links to hledger-web's `config`, `messages`, `static` and `templates`
|
||||
directories, needed in developer mode, so it can run from the top directory. This may not work on Windows.)
|
||||
|
||||
### Quality
|
||||
|
||||
Relevant tools include:
|
||||
|
||||
- unit tests (HUnit, make unittest)
|
||||
- functional tests (shelltestrunner, make functest)
|
||||
- performance tests (simplebench, make bench)
|
||||
- documentation tests (make haddocktest + manual)
|
||||
- ui tests (manual)
|
||||
- installation tests (manual)
|
||||
- code reviews
|
||||
|
||||
### Code review
|
||||
|
||||
- Code review party 2014/7/21-25:
|
||||
[discussion](https://thread.gmane.org/gmane.comp.finance.ledger.hledger/1070)<!-- missing ,
|
||||
[log](https://hledger.org/static/irc-20140725-code-review.html) -->
|
||||
- Dev sprint/party 2015/10/10:
|
||||
[discussion](https://thread.gmane.org/gmane.comp.finance.ledger.hledger/1254)<!-- ircbrowse down ,
|
||||
[pre-chat](https://ircbrowse.net/day/hledger/2015/10/10),
|
||||
[log](https://ircbrowse.net/day/hledger/2015/10/11) -->
|
||||
|
||||
|
||||
## Tests
|
||||
|
||||
About testing in the hledger project, as of 201809.
|
||||
|
||||
### Kinds of tests
|
||||
|
||||
<div style="margin:1em 2em; font-style:italic;">
|
||||
"Here, then, is a list of properties of tests. Not all tests need to exhibit all properties. However, no property should be given up without receiving a property of greater value in return.
|
||||
|
||||
- Isolated — tests should return the same results regardless of the order in which they are run.
|
||||
- Composable — if tests are isolated, then I can run 1 or 10 or 100 or 1,000,000 and get the same results.
|
||||
- Fast — tests should run quickly.
|
||||
- Inspiring — passing the tests should inspire confidence
|
||||
- Writable — tests should be cheap to write relative to the cost of the code being tested.
|
||||
- Readable — tests should be comprehensible for reader, invoking the motivation for writing this particular test.
|
||||
- Behavioral — tests should be sensitive to changes in the behavior of the code under test. If the behavior changes, the test result should change.
|
||||
- Structure-insensitive — tests should not change their result if the structure of the code changes.
|
||||
- Automated — tests should run without human intervention.
|
||||
- Specific — if a test fails, the cause of the failure should be obvious.
|
||||
- Deterministic — if nothing changes, the test result shouldn’t change.
|
||||
- Predictive — if the tests all pass, then the code under test should be suitable for production."
|
||||
--[Kent Beck](https://medium.com/@kentbeck_7670/test-desiderata-94150638a4b3)
|
||||
</div>
|
||||
|
||||
1. Unit tests
|
||||
|
||||
Unit tests exercise small chunks of functionality. In hledger, that
|
||||
means a function. So, many of our functions have one or more unit
|
||||
tests. These are mostly in hledger-lib, with a few in hledger.
|
||||
|
||||
Our unit tests use the
|
||||
[tasty](https://hackage.haskell.org/package/tasty) test runner,
|
||||
[tasty-hunit](https://hackage.haskell.org/package/tasty-hunit) HUnit-style tests,
|
||||
and some helpers from
|
||||
[Hledger.Utils.Test](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Utils/Test.hs),
|
||||
such as:
|
||||
|
||||
- `tests` and `test` aliases for `testGroup` and `testCase`
|
||||
- `assert*` helpers for constructing various kinds of assertions
|
||||
|
||||
We would like our unit tests to be:
|
||||
|
||||
- easy to read (clear, concise)
|
||||
- easy to write (low boilerplate, low cognitive load)
|
||||
- easy to maintain (easy to edit, easy to refactor, robust)
|
||||
- easy to associate with the code under test (easy to view/jump
|
||||
between code & test, easy to estimate coverage)
|
||||
- and scalable (usable for all devs, easy to run and select,
|
||||
suitable for small/large modules/packages).
|
||||
|
||||
Here\'s the current pattern (let us know if you see a better way):
|
||||
|
||||
``` haskell
|
||||
module Foo (
|
||||
...
|
||||
tests_Foo -- export this module's and submodules' tests
|
||||
)
|
||||
where
|
||||
import Hledger -- provides Hledger.Utils.Test helpers
|
||||
import Bar -- submodules, providing tests_Bar etc.
|
||||
import Baz
|
||||
|
||||
functionA = ...
|
||||
functionB = ...
|
||||
functionC = ...
|
||||
functionD = ...
|
||||
|
||||
tests_Foo = tests "Foo" [ -- define tests at the end of each module
|
||||
|
||||
-- a group of several named tests for functionA
|
||||
tests "functionA" [
|
||||
test "a basic test" $ assertBool "" SOMEBOOL
|
||||
,test "a pretty equality test" $ SOMEEXPR @?= EXPECTEDVALUE
|
||||
,test "a pretty parsing test" $ assertParseEq PARSER INPUT EXPECTEDRESULT
|
||||
,test "a multiple assertions test" $ do
|
||||
A @?= B
|
||||
doSomeIO
|
||||
C @?= D
|
||||
]
|
||||
|
||||
-- a single test containing multiple unnamed assertions for functionB
|
||||
,test "functionB" $ do
|
||||
assertBool "" BOOL
|
||||
EXPR @?= VALUE
|
||||
|
||||
,tests_Foo -- aggregate submodule tests
|
||||
,tests_Bar
|
||||
]
|
||||
```
|
||||
|
||||
Here are
|
||||
[some](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Data/Posting.hs#L296)
|
||||
real-world
|
||||
[examples](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Read/JournalReader.hs#L579).
|
||||
|
||||
The unit tests are shipped as part of the hledger executable, and
|
||||
can always be run via the [test](https://hledger.org/manual#test)
|
||||
command (`hledger test`).
|
||||
|
||||
Here\'s the quick way to run unit tests while developing:\
|
||||
`make ghcid-test` or `make ghcid-test-Some.Module`.
|
||||
|
||||
2. Doc tests
|
||||
|
||||
Like unit tests, but defined inside functions\' haddock
|
||||
documentation, in the style of a GHCI transcript. These test
|
||||
functionality, provide usage examples in the API docs, and test
|
||||
those examples, all at once. They are a bit more finicky and slower
|
||||
than unit tests. See
|
||||
[doctest](https://hackage.haskell.org/package/doctest) for more.
|
||||
|
||||
doctests [do not work on Mac with GHC
|
||||
8.4+](https://github.com/sol/doctest/issues/199), out of the box.
|
||||
See
|
||||
[ghc\#15105](https://ghc.haskell.org/trac/ghc/ticket/15105#comment:10)
|
||||
for current status and a workaround.
|
||||
|
||||
3. Functional tests
|
||||
|
||||
Functional tests test the overall functioning of the program. For
|
||||
hledger, that means running `hledger` with various inputs and
|
||||
options and checking for the expected output. This exercises
|
||||
functionality in the hledger and hledger-lib packages. We do this
|
||||
with
|
||||
[shelltestrunner](https://hackage.haskell.org/package/shelltestrunner).
|
||||
Tests are defined in files named `*.test` under
|
||||
[hledger/test/](https://github.com/simonmichael/hledger/tree/master/hledger/test),
|
||||
grouped by *component* (command or topic name).
|
||||
For more about these, see the README there.
|
||||
|
||||
4. Code tests
|
||||
|
||||
We have some tests aimed at testing eg code quality, generally
|
||||
defined as make rules, such as:
|
||||
|
||||
--------------------- -------------------------------------------------------------------------------------
|
||||
`make haddocktest` can haddock process all code docs without error
|
||||
`make buildtest` does all code build warning free with the default GHC version & stackage snapshot
|
||||
`make buildtestall` does the code build warning free with all supported GHC versions/stackage snapshots
|
||||
--------------------- -------------------------------------------------------------------------------------
|
||||
|
||||
See below for examples.
|
||||
|
||||
5. Package test suites
|
||||
|
||||
Haskell tools like stack and cabal recognise test suites defined in
|
||||
a package\'s cabal file (or package.yaml file). These can be run via
|
||||
`stack test`, `cabal test` etc., and they are required to build and
|
||||
pass by services like Stackage. Here are the currently hledger
|
||||
package test suites:
|
||||
|
||||
------------- ------------ ---------------------------------------------------------------
|
||||
package test suite what it runs
|
||||
hledger-lib doctests doctests
|
||||
hledger-lib easytests unit tests
|
||||
hledger test builtin test command (hledger\'s + hledger-lib\'s unit tests)
|
||||
hledger-ui
|
||||
hledger-web
|
||||
------------- ------------ ---------------------------------------------------------------
|
||||
|
||||
### Coverage
|
||||
|
||||
This means how thoroughly the code is tested - both in breadth (are all
|
||||
parts of the code tested at least a little ?) and in depth (are all
|
||||
possible code paths, states, situations tested ?).
|
||||
|
||||
Our current test coverage can be summarised like so:
|
||||
|
||||
------------- ------ ----- ------------
|
||||
package unit doc functional
|
||||
hledger-lib X X X
|
||||
hledger X X
|
||||
hledger-ui
|
||||
hledger-web
|
||||
------------- ------ ----- ------------
|
||||
|
||||
There are ways to generate detailed coverage reports for haskell unit
|
||||
tests, at least. It would be useful to set this up for hledger.
|
||||
|
||||
### How to run tests
|
||||
|
||||
Run unit tests:
|
||||
|
||||
``` example
|
||||
$ make unittest
|
||||
```
|
||||
|
||||
Run doctests:
|
||||
|
||||
``` example
|
||||
$ make doctest
|
||||
```
|
||||
|
||||
Run functional tests (and unit tests, now):
|
||||
|
||||
``` example
|
||||
$ stack install shelltestrunner
|
||||
$ make functest
|
||||
```
|
||||
|
||||
Run the package tests (unit tests, maybe doctests, but not functional
|
||||
tests) of all or selected packages.
|
||||
|
||||
``` example
|
||||
$ stack test [PKG]
|
||||
```
|
||||
|
||||
Run \"default tests: package plus functional tests\":
|
||||
|
||||
``` example
|
||||
$ make test
|
||||
```
|
||||
|
||||
Test generation of haddock docs:
|
||||
|
||||
``` example
|
||||
$ make haddocktest
|
||||
```
|
||||
|
||||
Thorough test for build issues with current GHC:
|
||||
|
||||
``` example
|
||||
$ make buildtest
|
||||
```
|
||||
|
||||
Thorough test for build issues with all supported GHC versions:
|
||||
|
||||
``` example
|
||||
$ make buildtestall
|
||||
```
|
||||
|
||||
Run built-in hledger/hledger-lib unit tests via hledger command:
|
||||
|
||||
``` example
|
||||
$ hledger test # test installed hledger
|
||||
$ stack build hledger && stack exec -- hledger test # test just-built hledger
|
||||
$ hledger test --help
|
||||
test [TESTPATTERN] [SEED]
|
||||
Run the unit tests built in to hledger-lib and hledger,
|
||||
printing results on stdout and exiting with success or failure.
|
||||
Tests are run in two batches: easytest-based and hunit-based tests.
|
||||
If any test fails or gives an error, the exit code will be non-zero.
|
||||
If a pattern argument (case sensitive) is provided, only easytests
|
||||
in that scope and only hunit tests whose name contains it are run.
|
||||
If a numeric second argument is provided, it will set the randomness
|
||||
seed for easytests.
|
||||
```
|
||||
|
||||
Rebuild and rerun hledger/hledger-lib unit tests via ghcid:
|
||||
|
||||
``` example
|
||||
$ make ghcid-test
|
||||
```
|
||||
|
||||
Rebuild and rerun only some tests via ghcid (see hledger test --help):
|
||||
|
||||
``` example
|
||||
$ make ghcid-test-TESTPATTERN
|
||||
```
|
||||
|
||||
See all test-related make rules:
|
||||
|
||||
``` example
|
||||
$ make help-test
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Benchmarks are standard performance measurements,
|
||||
which we define using `bench` declarations in cabal files.
|
||||
There is [one in hledger.cabal](https://github.com/simonmichael/hledger/blob/master/hledger/hledger.cabal#L228),
|
||||
with related code and data files in [hledger/bench/](https://github.com/simonmichael/hledger/tree/master/hledger/bench).
|
||||
|
||||
To run the standard hledger benchmark, use `stack bench hledger`.
|
||||
This installs haskell dependencies (but not system dependencies) and rebuilds as needed,
|
||||
then runs [hledger/bench/bench.hs](https://github.com/simonmichael/hledger/blob/master/hledger/bench/bench.hs),
|
||||
which by default shows quick elapsed-time measurements for several operations on a standard data file:
|
||||
|
||||
```shell
|
||||
$ stack bench hledger
|
||||
NOTE: the bench command is functionally equivalent to 'build --bench'
|
||||
...
|
||||
hledger-0.27: benchmarks
|
||||
Running 1 benchmarks...
|
||||
Benchmark bench: RUNNING...
|
||||
Benchmarking hledger in /Users/simon/src/hledger/hledger with timeit
|
||||
read bench/10000x1000x10.journal [1.57s]
|
||||
print [1.29s]
|
||||
register [1.92s]
|
||||
balance [0.21s]
|
||||
stats [0.23s]
|
||||
Total: 5.22s
|
||||
Benchmark bench: FINISH
|
||||
```
|
||||
|
||||
bench.hs has some other modes, which you can use by compiling and running it directly.
|
||||
`--criterion` reports more detailed and dependable measurements, but takes longer:
|
||||
|
||||
```shell
|
||||
$ cd hledger; stack exec -- ghc -ibench bench/bench && bench/bench --criterion
|
||||
...
|
||||
Linking bench/bench ...
|
||||
Benchmarking hledger in /Users/simon/src/hledger/hledger with criterion
|
||||
benchmarking read bench/10000x1000x10.journal
|
||||
time 1.414 s (1.234 s .. 1.674 s)
|
||||
0.996 R² (0.989 R² .. 1.000 R²)
|
||||
mean 1.461 s (1.422 s .. 1.497 s)
|
||||
std dev 59.69 ms (0.0 s .. 62.16 ms)
|
||||
variance introduced by outliers: 19% (moderately inflated)
|
||||
|
||||
benchmarking print
|
||||
time 1.323 s (1.279 s .. 1.385 s)
|
||||
1.000 R² (0.999 R² .. 1.000 R²)
|
||||
mean 1.305 s (1.285 s .. 1.316 s)
|
||||
std dev 17.20 ms (0.0 s .. 19.14 ms)
|
||||
variance introduced by outliers: 19% (moderately inflated)
|
||||
|
||||
benchmarking register
|
||||
time 1.995 s (1.883 s .. 2.146 s)
|
||||
0.999 R² (0.998 R² .. NaN R²)
|
||||
mean 1.978 s (1.951 s .. 1.995 s)
|
||||
std dev 25.09 ms (0.0 s .. 28.26 ms)
|
||||
variance introduced by outliers: 19% (moderately inflated)
|
||||
|
||||
benchmarking balance
|
||||
time 251.3 ms (237.6 ms .. 272.4 ms)
|
||||
0.998 R² (0.997 R² .. 1.000 R²)
|
||||
mean 260.4 ms (254.3 ms .. 266.5 ms)
|
||||
std dev 7.609 ms (3.192 ms .. 9.638 ms)
|
||||
variance introduced by outliers: 16% (moderately inflated)
|
||||
|
||||
benchmarking stats
|
||||
time 325.5 ms (299.1 ms .. 347.2 ms)
|
||||
0.997 R² (0.985 R² .. 1.000 R²)
|
||||
mean 329.2 ms (321.5 ms .. 339.6 ms)
|
||||
std dev 11.08 ms (2.646 ms .. 14.82 ms)
|
||||
variance introduced by outliers: 16% (moderately inflated)
|
||||
```
|
||||
|
||||
`--simplebench` shows a table of elapsed-time measurements for the commands defined in [bench/default.bench](https://github.com/simonmichael/hledger/blob/master/hledger/bench/default.bench).
|
||||
It can also show the results for multiple h/ledger executables side by side, if you tweak the bench.hs code.
|
||||
Unlike the other modes, it does not link with the hledger code directly, but runs the "hledger" executable found in $PATH (so ensure that's the one you intend to test).
|
||||
|
||||
```shell
|
||||
$ cd hledger; stack exec -- ghc -ibench bench/bench && bench/bench --simplebench
|
||||
Benchmarking /Users/simon/.local/bin/hledger in /Users/simon/src/hledger/hledger with simplebench and shell
|
||||
Using bench/default.bench
|
||||
Running 4 tests 1 times with 1 executables at 2015-08-23 16:58:59.128112 UTC:
|
||||
1: hledger -f bench/10000x1000x10.journal print [3.27s]
|
||||
1: hledger -f bench/10000x1000x10.journal register [3.65s]
|
||||
1: hledger -f bench/10000x1000x10.journal balance [2.06s]
|
||||
1: hledger -f bench/10000x1000x10.journal stats [2.13s]
|
||||
|
||||
Summary (best iteration):
|
||||
|
||||
+-----------------------------------------++---------+
|
||||
| || hledger |
|
||||
+=========================================++=========+
|
||||
| -f bench/10000x1000x10.journal print || 3.27 |
|
||||
| -f bench/10000x1000x10.journal register || 3.65 |
|
||||
| -f bench/10000x1000x10.journal balance || 2.06 |
|
||||
| -f bench/10000x1000x10.journal stats || 2.13 |
|
||||
+-----------------------------------------++---------+
|
||||
```
|
||||
|
||||
bench's --simplebench mode is based on a standalone tool, [tools/simplebench.hs](https://github.com/simonmichael/hledger/blob/master/tools/simplebench.hs).
|
||||
simplebench.hs is a generic benchmarker of one or more executables (specified on the command line) against one or more sets of command-line arguments (specified in a file).
|
||||
It has a better command-line interface than bench.hs, so you may find it more convenient
|
||||
for comparing multiple hledger versions, or hledger and ledger. Eg:
|
||||
|
||||
```shell
|
||||
$ stack exec -- ghc tools/simplebench
|
||||
[1 of 1] Compiling Main ( tools/simplebench.hs, tools/simplebench.o )
|
||||
Linking tools/simplebench ...
|
||||
```
|
||||
```shell
|
||||
$ tools/simplebench -h
|
||||
tools/simplebench -h
|
||||
simplebench: at least one executable needed
|
||||
bench [-f testsfile] [-n iterations] [-p precision] executable1 [executable2 ...]
|
||||
|
||||
Run some functional tests with each of the specified executables,
|
||||
where a test is "zero or more arguments supported by all executables",
|
||||
and report the best execution times.
|
||||
|
||||
-f testsfile --testsfile=testsfile file containing tests, one per line, default: bench.tests
|
||||
-n iterations --iterations=iterations number of test iterations to run, default: 2
|
||||
-p precision --precision=precision show times with this precision, default: 2
|
||||
-v --verbose show intermediate results
|
||||
-h --help show this help
|
||||
|
||||
Tips:
|
||||
- executables may have arguments if enclosed in quotes
|
||||
- tests can be commented out with #
|
||||
- results are saved in benchresults.{html,txt}
|
||||
```
|
||||
```shell
|
||||
cd hledger; $ ../tools/simplebench -f bench/default.bench hledger ledger
|
||||
Using bench/default.bench
|
||||
Running 4 tests 2 times with 2 executables at 2015-08-24 04:24:37.257068 UTC:
|
||||
|
||||
Summary (best iteration):
|
||||
|
||||
+-----------------------------------------++---------+--------+
|
||||
| || hledger | ledger |
|
||||
+=========================================++=========+========+
|
||||
| -f bench/10000x1000x10.journal print || 3.24 | 0.43 |
|
||||
| -f bench/10000x1000x10.journal register || 3.80 | 3.48 |
|
||||
| -f bench/10000x1000x10.journal balance || 2.05 | 0.18 |
|
||||
| -f bench/10000x1000x10.journal stats || 2.10 | 0.19 |
|
||||
+-----------------------------------------++---------+--------+
|
||||
```
|
||||
|
||||
Finally, for quick, fine-grained performance measurements when troubleshooting or optimising, I use
|
||||
[dev.hs](https://github.com/simonmichael/hledger/blob/master/dev.hs).
|
||||
|
||||
|
||||
|
||||
## Version numbers
|
||||
|
||||
Some places version numbers appear:
|
||||
|
||||
- --version (and sometimes --help) output of all hledger* executables
|
||||
- web manuals on hledger.org
|
||||
- download page
|
||||
- changelogs
|
||||
- release notes
|
||||
- release announcements
|
||||
- hackage/stackage uris
|
||||
- cabal tarball filenames
|
||||
- platform-specific packages
|
||||
|
||||
Some old version numbering goals:
|
||||
|
||||
1. automation, robustness, simplicity, platform independence
|
||||
2. cabal versions must be all-numeric
|
||||
3. release versions can be concise (without extra .0's)
|
||||
4. releases should have a corresponding VCS tag
|
||||
5. development builds should have a precise version appearing in --version
|
||||
6. development builds should generate cabal packages with non-confusing versions
|
||||
7. there should be a way to mark builds/releases as alpha or beta
|
||||
8. avoid unnecessary compiling and linking
|
||||
9. minimise VCS noise and syncing issues (commits, unrecorded changes)
|
||||
|
||||
Current version numbering policy:
|
||||
|
||||
- We (should) follow <https://haskell.org/haskellwiki/Package_versioning_policy>
|
||||
|
||||
- The "full release version" is ma.jor.minor, where minor is 0 for a
|
||||
normal release or 1..n for bugfix releases. Each component is a
|
||||
natural number (can be >= 10). Eg: 1.13 major release, 1.13.1
|
||||
bugfix release.
|
||||
|
||||
- The "release version", which we prefer to use when possible, is
|
||||
just ma.jor when minor is 0. Ie elide the dot zero.
|
||||
|
||||
- The build version is ma.jor.minor+patches, where patches is the number
|
||||
of patches applied in the current repo since the last release tag.
|
||||
|
||||
- `hledger --version` shows the release version or build version as
|
||||
appropriate.
|
||||
|
||||
- Release tags in the VCS are like PKG-VERSION. Eg hledger-1.13,
|
||||
- hledger-ui-1.13.1.
|
||||
|
||||
Current process:
|
||||
|
||||
- In each hledger package directory there's a `.version` file
|
||||
containing its desired version number.
|
||||
|
||||
- After changing a `.version` file: run `./Shake setversion` to
|
||||
propagate the versions to all other places in the packages where
|
||||
they should appear. This is not perfect (see Shake.hs) so review and
|
||||
manually adjust the proposed changes before committing. Those
|
||||
places include (you can also run these rules individually):
|
||||
|
||||
- `PKG/package.yaml` contains the cabal package version declaration,
|
||||
bounds on other hledger packages, and a CPP VERSION macro used in
|
||||
`hledger/Hledger/Cli/Version.hs`. Changes in package.yaml will be
|
||||
propagated to `PKG/PKG.cabal` on the next stack or Shake build, or
|
||||
by `make gencabal`.
|
||||
|
||||
- `PKG/.version.m4` contains the _version_ macro used in documentation source files (*.m4.md). It is updated by `./Shake setversion`.
|
||||
|
||||
- `PKG/.date.m4` contains the _monthyear_ macro used in man pages. It is updated by `./Shake manuals`.
|
||||
|
||||
- At release time:
|
||||
|
||||
- `./Shake PKG/CHANGES.md-finalise` converts the topmost heading, if
|
||||
it is an interim heading (just a commit hash), to a permanent
|
||||
heading containing the version and today's date.
|
||||
|
||||
- for each package being released, a PKG-VERSION git tag is created.
|
||||
|
||||
- At major release time:
|
||||
|
||||
- A new snapshot of the reference docs is added to the website, by
|
||||
`./Shake site/doc/VERSION/.snapshot`, and added to the links in
|
||||
`site/js/site.js`.
|
||||
|
||||
## Sample journals
|
||||
|
||||
Synthetic data files like `examples/100x100x10.journal` are useful for benchmarks and testing.
|
||||
The numbers describe the number of transactions, number of accounts, and maximum account depth respectively.
|
||||
They are generated by [`tools/generatejournal.hs`](https://github.com/simonmichael/hledger/blob/master/tools/generatejournal.hs).
|
||||
They should get built automatically as needed, if not you can use `make samplejournals`:
|
||||
|
||||
```shell
|
||||
$ make samplejournals
|
||||
ghc tools/generatejournal.hs
|
||||
[1 of 1] Compiling Main ( tools/generatejournal.hs, tools/generatejournal.o )
|
||||
Linking tools/generatejournal ...
|
||||
tools/generatejournal 100 100 10 >examples/100x100x10.journal
|
||||
tools/generatejournal 1000 1000 10 >examples/1000x1000x10.journal
|
||||
tools/generatejournal 1000 10000 10 >examples/1000x10000x10.journal
|
||||
tools/generatejournal 10000 1000 10 >examples/10000x1000x10.journal
|
||||
tools/generatejournal 10000 10000 10 >examples/10000x10000x10.journal
|
||||
tools/generatejournal 100000 1000 10 >examples/100000x1000x10.journal
|
||||
tools/generatejournal 3 5 5 >examples/ascii.journal
|
||||
tools/generatejournal 3 5 5 --chinese >examples/chinese.journal
|
||||
tools/generatejournal 3 5 5 --mixed >examples/mixed.journal
|
||||
```
|
||||
|
||||
|
||||
## Docs
|
||||
|
||||
### Four kinds of documentation
|
||||
|
||||
20191209: needs update. See also doc/README.
|
||||
|
||||
|
||||
<div style="margin:1em 2em; font-style:italic;">
|
||||
"There is a secret that needs to be understood in order to write good
|
||||
software documentation: there isn’t one thing called documentation,
|
||||
there are four. They are: tutorials, how-to guides, explanation and
|
||||
technical reference. They represent four different purposes or
|
||||
functions, and require four different approaches to their creation."
|
||||
--[Daniele Procida] (https://news.ycombinator.com/item?id=21289832)
|
||||
</div>
|
||||
|
||||
https://github.com/simonmichael/hledger/tree/master/doc
|
||||
|
||||
Project documentation lives in a number of places:
|
||||
|
||||
- `site/*.md` is the hledger.org website content, which is generated with hakyll[-std] and pandoc
|
||||
- haddock documentation in the code appears on Hackage
|
||||
- short blurbs: cabal files, module headers, HCAR, GSOC project, ..
|
||||
- `doc/notes.org` has some old developer notes
|
||||
- developer reports (profiles, benchmarks, coverage..) in doc/profs, sometimes published at hledger.org/profs
|
||||
|
||||
## Funding
|
||||
|
||||
My vision for the hledger project has always been for it to be "accountable" and "self-sustaining", possibly through new forms of incentivisation.
|
||||
Classic non-monetary FOSS communities are a beautiful and precious thing.
|
||||
Adding money can change their dynamic.
|
||||
Yet, we would enjoy having a lot more issues resolved, and a faster rate of progress.
|
||||
So we experiment, gently.
|
||||
|
||||
Currently we use bounties as a way to encourage resolution of issues.
|
||||
There are a few ways to do this:
|
||||
|
||||
1. You or your organisation can offer a bounty simply by saying so on the issue.
|
||||
|
||||
2. You can use Bountysource. A few hledger bounties have been completed there.
|
||||
|
||||
3. You can use the new Open Collective process below.
|
||||
|
||||
Issues with bounties of any kind are marked with the `bounty` label.
|
||||
The Bounty Manager is @simonmichael.
|
||||
|
||||
### New bounty process
|
||||
|
||||
It currently looks like this, and will evolve:
|
||||
|
||||
- Issues are marked as bounties by @simonmichael. Feel free to suggest additional issues which should receive the bounty label.
|
||||
|
||||
- Bounties are paid from the hledger project's public Open Collective fund.
|
||||
By contributing to the fund as an individual or organisation, you enable more bounties.
|
||||
|
||||
- These OC bounties (unlike 1 and 2 above) have standard amounts.
|
||||
These may be adjusted over time, depending eg on the state of our funds.
|
||||
Our current bounty amounts are
|
||||
- level 1: 10 USD
|
||||
- level 2: 25 USD
|
||||
- level 3: 50 USD
|
||||
|
||||
- When you complete a bounty, submit an expense to Open Collective,
|
||||
for whichever of the above bounty amounts you think appropriate,
|
||||
based eg on time or expertise spent, how much you need it,
|
||||
how much remains in our fund for other bounties, etc.
|
||||
This will be reviewed by OC and (maybe ?) @simonmichael.
|
||||
Successful claims, like donations, will appear in our public OC ledger.
|
||||
|
||||
Our bounty amounts are small, and nothing like professional rates in most countries,
|
||||
but they still establish a principle of sustainability,
|
||||
and help us to experiment.
|
||||
You are encouraged to claim your bounties,
|
||||
though you can also choose to transfer them to a new issue of your choice.
|
||||
|
||||
## Commit messages
|
||||
|
||||
See [COMMITS](COMMITS.html).
|
||||
|
||||
## Issues
|
||||
|
||||
See [ISSUES](ISSUES.html).
|
||||
|
||||
## Pull Requests
|
||||
|
||||
See [PULLREQUESTS](PULLREQUESTS.html).
|
||||
|
||||
## Developer workflows
|
||||
|
||||
See [WORKFLOWS](WORKFLOWS.html).
|
||||
|
||||
|
25
DOCS.md
Normal file
25
DOCS.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Docs
|
||||
|
||||
20191209: needs update. See also doc/README.
|
||||
|
||||
## Four kinds of documentation
|
||||
|
||||
<div style="margin:1em 2em; font-style:italic;">
|
||||
"There is a secret that needs to be understood in order to write good
|
||||
software documentation: there isn’t one thing called documentation,
|
||||
there are four. They are: tutorials, how-to guides, explanation and
|
||||
technical reference. They represent four different purposes or
|
||||
functions, and require four different approaches to their creation."
|
||||
--[Daniele Procida] (https://news.ycombinator.com/item?id=21289832)
|
||||
</div>
|
||||
|
||||
https://github.com/simonmichael/hledger/tree/master/doc
|
||||
|
||||
Project documentation lives in a number of places:
|
||||
|
||||
- `site/*.md` is the hledger.org website content, which is generated with hakyll[-std] and pandoc
|
||||
- haddock documentation in the code appears on Hackage
|
||||
- short blurbs: cabal files, module headers, HCAR, GSOC project, ..
|
||||
- `doc/notes.org` has some old developer notes
|
||||
- developer reports (profiles, benchmarks, coverage..) in doc/profs, sometimes published at hledger.org/profs
|
||||
|
31
EXAMPLES.md
Normal file
31
EXAMPLES.md
Normal file
@ -0,0 +1,31 @@
|
||||
# EXAMPLES
|
||||
|
||||
## Collected examples
|
||||
|
||||
Many example input files in journal and other formats can be found
|
||||
in `examples/` in the `hledger` repo.
|
||||
|
||||
## Sample journals
|
||||
|
||||
Synthetic data files like `examples/100x100x10.journal` are useful for benchmarks and testing.
|
||||
The numbers describe the number of transactions, number of accounts, and maximum account depth respectively.
|
||||
They are generated by [`tools/generatejournal.hs`](https://github.com/simonmichael/hledger/blob/master/tools/generatejournal.hs).
|
||||
They should get built automatically as needed, if not you can use `make samplejournals`:
|
||||
|
||||
```shell
|
||||
$ make samplejournals
|
||||
ghc tools/generatejournal.hs
|
||||
[1 of 1] Compiling Main ( tools/generatejournal.hs, tools/generatejournal.o )
|
||||
Linking tools/generatejournal ...
|
||||
tools/generatejournal 100 100 10 >examples/100x100x10.journal
|
||||
tools/generatejournal 1000 1000 10 >examples/1000x1000x10.journal
|
||||
tools/generatejournal 1000 10000 10 >examples/1000x10000x10.journal
|
||||
tools/generatejournal 10000 1000 10 >examples/10000x1000x10.journal
|
||||
tools/generatejournal 10000 10000 10 >examples/10000x10000x10.journal
|
||||
tools/generatejournal 100000 1000 10 >examples/100000x1000x10.journal
|
||||
tools/generatejournal 3 5 5 >examples/ascii.journal
|
||||
tools/generatejournal 3 5 5 --chinese >examples/chinese.journal
|
||||
tools/generatejournal 3 5 5 --mixed >examples/mixed.journal
|
||||
```
|
||||
|
||||
|
49
FUNDING.md
Normal file
49
FUNDING.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Funding
|
||||
|
||||
My vision for the hledger project has always been for it to be "accountable" and "self-sustaining", possibly through new forms of incentivisation.
|
||||
Classic non-monetary FOSS communities are a beautiful and precious thing.
|
||||
Adding money can change their dynamic.
|
||||
Yet, we would enjoy having a lot more issues resolved, and a faster rate of progress.
|
||||
So we experiment, gently.
|
||||
|
||||
Currently we use bounties as a way to encourage resolution of issues.
|
||||
There are a few ways to do this:
|
||||
|
||||
1. You or your organisation can offer a bounty simply by saying so on the issue.
|
||||
|
||||
2. You can use Bountysource. A few hledger bounties have been completed there.
|
||||
|
||||
3. You can use the new Open Collective process below.
|
||||
|
||||
Issues with bounties of any kind are marked with the `bounty` label.
|
||||
The Bounty Manager is @simonmichael.
|
||||
|
||||
## New bounty process
|
||||
|
||||
It currently looks like this, and will evolve:
|
||||
|
||||
- Issues are marked as bounties by @simonmichael. Feel free to suggest additional issues which should receive the bounty label.
|
||||
|
||||
- Bounties are paid from the hledger project's public Open Collective fund.
|
||||
By contributing to the fund as an individual or organisation, you enable more bounties.
|
||||
|
||||
- These OC bounties (unlike 1 and 2 above) have standard amounts.
|
||||
These may be adjusted over time, depending eg on the state of our funds.
|
||||
Our current bounty amounts are
|
||||
- level 1: 10 USD
|
||||
- level 2: 25 USD
|
||||
- level 3: 50 USD
|
||||
|
||||
- When you complete a bounty, submit an expense to Open Collective,
|
||||
for whichever of the above bounty amounts you think appropriate,
|
||||
based eg on time or expertise spent, how much you need it,
|
||||
how much remains in our fund for other bounties, etc.
|
||||
This will be reviewed by OC and (maybe ?) @simonmichael.
|
||||
Successful claims, like donations, will appear in our public OC ledger.
|
||||
|
||||
Our bounty amounts are small, and nothing like professional rates in most countries,
|
||||
but they still establish a principle of sustainability,
|
||||
and help us to experiment.
|
||||
You are encouraged to claim your bounties,
|
||||
though you can also choose to transfer them to a new issue of your choice.
|
||||
|
40
MAKE.md
Normal file
40
MAKE.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Make
|
||||
|
||||
A Makefile is provided to make common developer tasks easy to remember,
|
||||
and to insulate us a little from the ever-evolving Haskell tools ecosystem.
|
||||
Using it is entirely optional, but recommended.
|
||||
You'll need [GNU Make](https://www.gnu.org/software/make) installed.
|
||||
|
||||
The Makefile contains a fair amount of obsolete cruft and needs cleanup. Some tasks (docs, website) are now handled by the [Shake](#shake) file instead.
|
||||
|
||||
The Makefile is self-documenting. Run `make` to see a list of the main make rules:
|
||||
|
||||
```shell
|
||||
$ make
|
||||
Makefile:37: -------------------- hledger make rules --------------------
|
||||
Makefile:39: make [help] -- list documented rules in this makefile. make -n RULE shows more detail.
|
||||
Makefile:204: (INSTALLING:)
|
||||
Makefile:206: make install -- download dependencies and install hledger executables to ~/.local/bin or equivalent (with stack)
|
||||
Makefile:231: (BUILDING:)
|
||||
Makefile:235: make build -- download dependencies and build hledger executables (with stack)
|
||||
Makefile:304: make hledgerdev -- quickly build the hledger executable (with ghc and -DDEVELOPMENT)
|
||||
...
|
||||
```
|
||||
|
||||
To see what a make rule will do without actually doing it, use the `-n` flag:
|
||||
|
||||
```shell
|
||||
$ make build -n
|
||||
stack build
|
||||
```
|
||||
```shell
|
||||
$ make test -n
|
||||
(stack test \
|
||||
&& echo pkgtest PASSED) || echo pkgtest FAILED
|
||||
(stack exec hledger test \
|
||||
&& echo builtintest PASSED) || echo builtintest FAILED
|
||||
(COLUMNS=80 PATH=`pwd`/bin:/home/simon/src/hledger/bin:/home/simon/.local/bin:/home/simon/.cabal/bin:/opt/ghc/7.10.1/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/var/lib/gems/1.9.1/bin stack exec -- shelltest --execdir -- -j16 --hide-successes tests \
|
||||
&& echo functest PASSED) || echo functest FAILED
|
||||
```
|
||||
|
||||
|
13
SHAKE.md
Normal file
13
SHAKE.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Shake
|
||||
|
||||
`Shake.hs` in the top directory complements the Makefile; it is used for some more complex tasks, such as building documentation and the web site.
|
||||
|
||||
Compile it:
|
||||
|
||||
./Shake.hs # or, make Shake
|
||||
|
||||
See help:
|
||||
|
||||
./Shake
|
||||
|
||||
|
269
TESTS.md
Normal file
269
TESTS.md
Normal file
@ -0,0 +1,269 @@
|
||||
# Tests
|
||||
|
||||
About testing in the hledger project, as of 201809.
|
||||
|
||||
## Kinds of tests
|
||||
|
||||
<div style="margin:1em 2em; font-style:italic;">
|
||||
"Here, then, is a list of properties of tests. Not all tests need to exhibit all properties. However, no property should be given up without receiving a property of greater value in return.
|
||||
|
||||
- Isolated — tests should return the same results regardless of the order in which they are run.
|
||||
- Composable — if tests are isolated, then I can run 1 or 10 or 100 or 1,000,000 and get the same results.
|
||||
- Fast — tests should run quickly.
|
||||
- Inspiring — passing the tests should inspire confidence
|
||||
- Writable — tests should be cheap to write relative to the cost of the code being tested.
|
||||
- Readable — tests should be comprehensible for reader, invoking the motivation for writing this particular test.
|
||||
- Behavioral — tests should be sensitive to changes in the behavior of the code under test. If the behavior changes, the test result should change.
|
||||
- Structure-insensitive — tests should not change their result if the structure of the code changes.
|
||||
- Automated — tests should run without human intervention.
|
||||
- Specific — if a test fails, the cause of the failure should be obvious.
|
||||
- Deterministic — if nothing changes, the test result shouldn’t change.
|
||||
- Predictive — if the tests all pass, then the code under test should be suitable for production."
|
||||
--[Kent Beck](https://medium.com/@kentbeck_7670/test-desiderata-94150638a4b3)
|
||||
</div>
|
||||
|
||||
1. Unit tests
|
||||
|
||||
Unit tests exercise small chunks of functionality. In hledger, that
|
||||
means a function. So, many of our functions have one or more unit
|
||||
tests. These are mostly in hledger-lib, with a few in hledger.
|
||||
|
||||
Our unit tests use the
|
||||
[tasty](https://hackage.haskell.org/package/tasty) test runner,
|
||||
[tasty-hunit](https://hackage.haskell.org/package/tasty-hunit) HUnit-style tests,
|
||||
and some helpers from
|
||||
[Hledger.Utils.Test](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Utils/Test.hs),
|
||||
such as:
|
||||
|
||||
- `tests` and `test` aliases for `testGroup` and `testCase`
|
||||
- `assert*` helpers for constructing various kinds of assertions
|
||||
|
||||
We would like our unit tests to be:
|
||||
|
||||
- easy to read (clear, concise)
|
||||
- easy to write (low boilerplate, low cognitive load)
|
||||
- easy to maintain (easy to edit, easy to refactor, robust)
|
||||
- easy to associate with the code under test (easy to view/jump
|
||||
between code & test, easy to estimate coverage)
|
||||
- and scalable (usable for all devs, easy to run and select,
|
||||
suitable for small/large modules/packages).
|
||||
|
||||
Here\'s the current pattern (let us know if you see a better way):
|
||||
|
||||
``` haskell
|
||||
module Foo (
|
||||
...
|
||||
tests_Foo -- export this module's and submodules' tests
|
||||
)
|
||||
where
|
||||
import Hledger -- provides Hledger.Utils.Test helpers
|
||||
import Bar -- submodules, providing tests_Bar etc.
|
||||
import Baz
|
||||
|
||||
functionA = ...
|
||||
functionB = ...
|
||||
functionC = ...
|
||||
functionD = ...
|
||||
|
||||
tests_Foo = tests "Foo" [ -- define tests at the end of each module
|
||||
|
||||
-- a group of several named tests for functionA
|
||||
tests "functionA" [
|
||||
test "a basic test" $ assertBool "" SOMEBOOL
|
||||
,test "a pretty equality test" $ SOMEEXPR @?= EXPECTEDVALUE
|
||||
,test "a pretty parsing test" $ assertParseEq PARSER INPUT EXPECTEDRESULT
|
||||
,test "a multiple assertions test" $ do
|
||||
A @?= B
|
||||
doSomeIO
|
||||
C @?= D
|
||||
]
|
||||
|
||||
-- a single test containing multiple unnamed assertions for functionB
|
||||
,test "functionB" $ do
|
||||
assertBool "" BOOL
|
||||
EXPR @?= VALUE
|
||||
|
||||
,tests_Foo -- aggregate submodule tests
|
||||
,tests_Bar
|
||||
]
|
||||
```
|
||||
|
||||
Here are
|
||||
[some](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Data/Posting.hs#L296)
|
||||
real-world
|
||||
[examples](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Read/JournalReader.hs#L579).
|
||||
|
||||
The unit tests are shipped as part of the hledger executable, and
|
||||
can always be run via the [test](https://hledger.org/manual#test)
|
||||
command (`hledger test`).
|
||||
|
||||
Here\'s the quick way to run unit tests while developing:\
|
||||
`make ghcid-test` or `make ghcid-test-Some.Module`.
|
||||
|
||||
2. Doc tests
|
||||
|
||||
Like unit tests, but defined inside functions\' haddock
|
||||
documentation, in the style of a GHCI transcript. These test
|
||||
functionality, provide usage examples in the API docs, and test
|
||||
those examples, all at once. They are a bit more finicky and slower
|
||||
than unit tests. See
|
||||
[doctest](https://hackage.haskell.org/package/doctest) for more.
|
||||
|
||||
doctests [do not work on Mac with GHC
|
||||
8.4+](https://github.com/sol/doctest/issues/199), out of the box.
|
||||
See
|
||||
[ghc\#15105](https://ghc.haskell.org/trac/ghc/ticket/15105#comment:10)
|
||||
for current status and a workaround.
|
||||
|
||||
3. Functional tests
|
||||
|
||||
Functional tests test the overall functioning of the program. For
|
||||
hledger, that means running `hledger` with various inputs and
|
||||
options and checking for the expected output. This exercises
|
||||
functionality in the hledger and hledger-lib packages. We do this
|
||||
with
|
||||
[shelltestrunner](https://hackage.haskell.org/package/shelltestrunner).
|
||||
Tests are defined in files named `*.test` under
|
||||
[hledger/test/](https://github.com/simonmichael/hledger/tree/master/hledger/test),
|
||||
grouped by *component* (command or topic name).
|
||||
For more about these, see the README there.
|
||||
|
||||
4. Code tests
|
||||
|
||||
We have some tests aimed at testing eg code quality, generally
|
||||
defined as make rules, such as:
|
||||
|
||||
--------------------- -------------------------------------------------------------------------------------
|
||||
`make haddocktest` can haddock process all code docs without error
|
||||
`make buildtest` does all code build warning free with the default GHC version & stackage snapshot
|
||||
`make buildtestall` does the code build warning free with all supported GHC versions/stackage snapshots
|
||||
--------------------- -------------------------------------------------------------------------------------
|
||||
|
||||
See below for examples.
|
||||
|
||||
5. Package test suites
|
||||
|
||||
Haskell tools like stack and cabal recognise test suites defined in
|
||||
a package\'s cabal file (or package.yaml file). These can be run via
|
||||
`stack test`, `cabal test` etc., and they are required to build and
|
||||
pass by services like Stackage. Here are the currently hledger
|
||||
package test suites:
|
||||
|
||||
------------- ------------ ---------------------------------------------------------------
|
||||
package test suite what it runs
|
||||
hledger-lib doctests doctests
|
||||
hledger-lib easytests unit tests
|
||||
hledger test builtin test command (hledger\'s + hledger-lib\'s unit tests)
|
||||
hledger-ui
|
||||
hledger-web
|
||||
------------- ------------ ---------------------------------------------------------------
|
||||
|
||||
## Coverage
|
||||
|
||||
This means how thoroughly the code is tested - both in breadth (are all
|
||||
parts of the code tested at least a little ?) and in depth (are all
|
||||
possible code paths, states, situations tested ?).
|
||||
|
||||
Our current test coverage can be summarised like so:
|
||||
|
||||
------------- ------ ----- ------------
|
||||
package unit doc functional
|
||||
hledger-lib X X X
|
||||
hledger X X
|
||||
hledger-ui
|
||||
hledger-web
|
||||
------------- ------ ----- ------------
|
||||
|
||||
There are ways to generate detailed coverage reports for haskell unit
|
||||
tests, at least. It would be useful to set this up for hledger.
|
||||
|
||||
## How to run tests
|
||||
|
||||
Run unit tests:
|
||||
|
||||
``` example
|
||||
$ make unittest
|
||||
```
|
||||
|
||||
Run doctests:
|
||||
|
||||
``` example
|
||||
$ make doctest
|
||||
```
|
||||
|
||||
Run functional tests (and unit tests, now):
|
||||
|
||||
``` example
|
||||
$ stack install shelltestrunner
|
||||
$ make functest
|
||||
```
|
||||
|
||||
Run the package tests (unit tests, maybe doctests, but not functional
|
||||
tests) of all or selected packages.
|
||||
|
||||
``` example
|
||||
$ stack test [PKG]
|
||||
```
|
||||
|
||||
Run \"default tests: package plus functional tests\":
|
||||
|
||||
``` example
|
||||
$ make test
|
||||
```
|
||||
|
||||
Test generation of haddock docs:
|
||||
|
||||
``` example
|
||||
$ make haddocktest
|
||||
```
|
||||
|
||||
Thorough test for build issues with current GHC:
|
||||
|
||||
``` example
|
||||
$ make buildtest
|
||||
```
|
||||
|
||||
Thorough test for build issues with all supported GHC versions:
|
||||
|
||||
``` example
|
||||
$ make buildtestall
|
||||
```
|
||||
|
||||
Run built-in hledger/hledger-lib unit tests via hledger command:
|
||||
|
||||
``` example
|
||||
$ hledger test # test installed hledger
|
||||
$ stack build hledger && stack exec -- hledger test # test just-built hledger
|
||||
$ hledger test --help
|
||||
test [TESTPATTERN] [SEED]
|
||||
Run the unit tests built in to hledger-lib and hledger,
|
||||
printing results on stdout and exiting with success or failure.
|
||||
Tests are run in two batches: easytest-based and hunit-based tests.
|
||||
If any test fails or gives an error, the exit code will be non-zero.
|
||||
If a pattern argument (case sensitive) is provided, only easytests
|
||||
in that scope and only hunit tests whose name contains it are run.
|
||||
If a numeric second argument is provided, it will set the randomness
|
||||
seed for easytests.
|
||||
```
|
||||
|
||||
Rebuild and rerun hledger/hledger-lib unit tests via ghcid:
|
||||
|
||||
``` example
|
||||
$ make ghcid-test
|
||||
```
|
||||
|
||||
Rebuild and rerun only some tests via ghcid (see hledger test --help):
|
||||
|
||||
``` example
|
||||
$ make ghcid-test-TESTPATTERN
|
||||
```
|
||||
|
||||
See all test-related make rules:
|
||||
|
||||
``` example
|
||||
$ make help-test
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
82
VERSIONNUMBERS.md
Normal file
82
VERSIONNUMBERS.md
Normal file
@ -0,0 +1,82 @@
|
||||
# Version numbers
|
||||
|
||||
Some places version numbers appear:
|
||||
|
||||
- --version (and sometimes --help) output of all hledger* executables
|
||||
- web manuals on hledger.org
|
||||
- download page
|
||||
- changelogs
|
||||
- release notes
|
||||
- release announcements
|
||||
- hackage/stackage uris
|
||||
- cabal tarball filenames
|
||||
- platform-specific packages
|
||||
|
||||
Some old version numbering goals:
|
||||
|
||||
1. automation, robustness, simplicity, platform independence
|
||||
2. cabal versions must be all-numeric
|
||||
3. release versions can be concise (without extra .0's)
|
||||
4. releases should have a corresponding VCS tag
|
||||
5. development builds should have a precise version appearing in --version
|
||||
6. development builds should generate cabal packages with non-confusing versions
|
||||
7. there should be a way to mark builds/releases as alpha or beta
|
||||
8. avoid unnecessary compiling and linking
|
||||
9. minimise VCS noise and syncing issues (commits, unrecorded changes)
|
||||
|
||||
Current version numbering policy:
|
||||
|
||||
- We (should) follow <https://haskell.org/haskellwiki/Package_versioning_policy>
|
||||
|
||||
- The "full release version" is ma.jor.minor, where minor is 0 for a
|
||||
normal release or 1..n for bugfix releases. Each component is a
|
||||
natural number (can be >= 10). Eg: 1.13 major release, 1.13.1
|
||||
bugfix release.
|
||||
|
||||
- The "release version", which we prefer to use when possible, is
|
||||
just ma.jor when minor is 0. Ie elide the dot zero.
|
||||
|
||||
- The build version is ma.jor.minor+patches, where patches is the number
|
||||
of patches applied in the current repo since the last release tag.
|
||||
|
||||
- `hledger --version` shows the release version or build version as
|
||||
appropriate.
|
||||
|
||||
- Release tags in the VCS are like PKG-VERSION. Eg hledger-1.13,
|
||||
- hledger-ui-1.13.1.
|
||||
|
||||
Current process:
|
||||
|
||||
- In each hledger package directory there's a `.version` file
|
||||
containing its desired version number.
|
||||
|
||||
- After changing a `.version` file: run `./Shake setversion` to
|
||||
propagate the versions to all other places in the packages where
|
||||
they should appear. This is not perfect (see Shake.hs) so review and
|
||||
manually adjust the proposed changes before committing. Those
|
||||
places include (you can also run these rules individually):
|
||||
|
||||
- `PKG/package.yaml` contains the cabal package version declaration,
|
||||
bounds on other hledger packages, and a CPP VERSION macro used in
|
||||
`hledger/Hledger/Cli/Version.hs`. Changes in package.yaml will be
|
||||
propagated to `PKG/PKG.cabal` on the next stack or Shake build, or
|
||||
by `make gencabal`.
|
||||
|
||||
- `PKG/.version.m4` contains the _version_ macro used in documentation source files (*.m4.md). It is updated by `./Shake setversion`.
|
||||
|
||||
- `PKG/.date.m4` contains the _monthyear_ macro used in man pages. It is updated by `./Shake manuals`.
|
||||
|
||||
- At release time:
|
||||
|
||||
- `./Shake PKG/CHANGES.md-finalise` converts the topmost heading, if
|
||||
it is an interim heading (just a commit hash), to a permanent
|
||||
heading containing the version and today's date.
|
||||
|
||||
- for each package being released, a PKG-VERSION git tag is created.
|
||||
|
||||
- At major release time:
|
||||
|
||||
- A new snapshot of the reference docs is added to the website, by
|
||||
`./Shake site/doc/VERSION/.snapshot`, and added to the links in
|
||||
`site/js/site.js`.
|
||||
|
Loading…
Reference in New Issue
Block a user