Merge branch 'docs'

This commit is contained in:
Michael Walker 2017-08-19 16:39:25 +01:00
commit 91fa969fc2
19 changed files with 1196 additions and 94 deletions

View File

@ -18,7 +18,7 @@ or email (mike@barrucadu.co.uk).
[next-major]: https://github.com/barrucadu/dejafu/tree/next-major
Test Coverage
Test coverage
-------------
[`hpc`][hpc] can generate a coverage report from the execution of
@ -121,7 +121,7 @@ use the "lint.sh" and "style.sh" scripts to run the tools for you.
[hlint]: https://github.com/ndmitchell/hlint
Documenting your Changes
Documenting your changes
------------------------
**If a top-level definition is introduced or significantly changed**,

View File

@ -7,10 +7,12 @@ This project is versioned according to the [Package Versioning Policy](https://p
*de facto* standard Haskell versioning scheme.
1.1.2.1 [2017-06-07] (tag: [concurrency-1.1.2.1][])
1.1.2.1
-------
https://hackage.haskell.org/package/concurrency-1.1.2.1
- **Date** 2017-06-07
- **Git tag** [concurrency-1.1.2.1][]
- **Hackage** https://hackage.haskell.org/package/concurrency-1.1.2.1
### Changed
@ -28,10 +30,12 @@ https://hackage.haskell.org/package/concurrency-1.1.2.1
---------------------------------------------------------------------------------------------------
1.1.2.0 [2017-04-05] (tag: [concurrency-1.1.2.0][])
1.1.2.0
-------
https://hackage.haskell.org/package/concurrency-1.1.2.0
- **Date** 2017-04-05
- **Git tag** [concurrency-1.1.2.0][]
- **Hackage** https://hackage.haskell.org/package/concurrency-1.1.2.0
### Control.Concurrent.Classy.Async
@ -66,10 +70,12 @@ https://hackage.haskell.org/package/concurrency-1.1.2.0
---------------------------------------------------------------------------------------------------
1.1.1.0 [2017-03-04] (git tag: [concurrency-1.1.1.0][])
1.1.1.0
-------
https://hackage.haskell.org/package/concurrency-1.1.1.0
- **Date** 2017-03-04
- **Git tag** [concurrency-1.1.1.0][]
- **Hackage** https://hackage.haskell.org/package/concurrency-1.1.1.0
### Miscellaneous
@ -82,10 +88,12 @@ https://hackage.haskell.org/package/concurrency-1.1.1.0
---------------------------------------------------------------------------------------------------
1.1.0.0 [2017-02-21] (git tag: [concurrency-1.1.0.0][])
1.1.0.0
-------
https://hackage.haskell.org/package/concurrency-1.1.0.0
- **Date** 2017-02-21
- **Git tag** [concurrency-1.1.0.0][]
- **Hackage** https://hackage.haskell.org/package/concurrency-1.1.0.0
### Control.Monad.Conc.Class
@ -100,10 +108,12 @@ https://hackage.haskell.org/package/concurrency-1.1.0.0
---------------------------------------------------------------------------------------------------
1.0.0.0 [2016-09-10] (git tag: [concurrency-1.0.0.0][])
1.0.0.0
-------
https://hackage.haskell.org/package/concurrency-1.0.0.0
- **Date** 2016-09-10
- **Git tag** [concurrency-1.0.0.0][]
- **Hackage** https://hackage.haskell.org/package/concurrency-1.0.0.0
Initial release. Go read the API docs.

View File

@ -7,10 +7,12 @@ This project is versioned according to the [Package Versioning Policy](https://p
*de facto* standard Haskell versioning scheme.
0.7.1.1 [2017-08-16] (git tag: [dejafu-0.7.1.1][])
0.7.1.1
-------
https://hackage.haskell.org/package/dejafu-0.7.1.1
- **Date** 2017-08-16
- **Git tag** [dejafu-0.7.1.1][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.7.1.1
### Miscellaneous
@ -26,10 +28,12 @@ https://hackage.haskell.org/package/dejafu-0.7.1.1
---------------------------------------------------------------------------------------------------
0.7.1.0 [2017-08-10] (git tag: [dejafu-0.7.1.0][])
0.7.1.0
-------
https://hackage.haskell.org/package/dejafu-0.7.1.0
- **Date** 2017-08-10
- **Git tag** [dejafu-0.7.1.0][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.7.1.0
### Test.DejaFu
@ -60,10 +64,12 @@ https://hackage.haskell.org/package/dejafu-0.7.1.0
---------------------------------------------------------------------------------------------------
0.7.0.2 [2017-06-12] (git tag: [dejafu-0.7.0.2][])
0.7.0.2
-------
https://hackage.haskell.org/package/dejafu-0.7.0.2
- **Date** 2017-06-12
- **Git tag** [dejafu-0.7.0.2][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.7.0.2
### Test.DejaFu.Refinement
@ -81,10 +87,12 @@ https://hackage.haskell.org/package/dejafu-0.7.0.2
---------------------------------------------------------------------------------------------------
0.7.0.1 [2017-06-09] (git tag: [dejafu-0.7.0.1][])
0.7.0.1
-------
https://hackage.haskell.org/package/dejafu-0.7.0.1
- **Date** 2017-06-09
- **Git tag** [dejafu-0.7.0.1][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.7.0.1
### Test.DejaFu.Refinement
@ -98,10 +106,12 @@ https://hackage.haskell.org/package/dejafu-0.7.0.1
---------------------------------------------------------------------------------------------------
0.7.0.0 [2017-06-07] (git tag: [dejafu-0.7.0.0][])
0.7.0.0
-------
https://hackage.haskell.org/package/dejafu-0.7.0.0
- **Date** 2017-06-07
- **Git tag** [dejafu-0.7.0.0][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.7.0.0
### Test.DejaFu
@ -145,10 +155,12 @@ https://hackage.haskell.org/package/dejafu-0.7.0.0
---------------------------------------------------------------------------------------------------
0.6.0.0 [2017-04-08] (git tag: [dejafu-0.6.0.0][])
0.6.0.0
-------
https://hackage.haskell.org/package/dejafu-0.6.0.0
- **Date** 2017-04-08
- **Git tag** [dejafu-0.6.0.0][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.6.0.0
### Test.DejaFu.Conc
@ -176,10 +188,12 @@ https://hackage.haskell.org/package/dejafu-0.6.0.0
---------------------------------------------------------------------------------------------------
0.5.1.3 [2017-04-05] (git tag: [dejafu-0.5.1.3][])
0.5.1.3
-------
https://hackage.haskell.org/package/dejafu-0.5.1.3
- **Date** 2017-04-05
- **Git tag** [dejafu-0.5.1.3][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.5.1.3
### Miscellaneous
@ -191,10 +205,12 @@ https://hackage.haskell.org/package/dejafu-0.5.1.3
---------------------------------------------------------------------------------------------------
0.5.1.2 [2017-03-04] (git tag: [dejafu-0.5.1.2][])
0.5.1.2
-------
https://hackage.haskell.org/package/dejafu-0.5.1.2
- **Date** 2017-03-04
- **Git tag** [dejafu-0.5.1.2][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.5.1.2
**This version was misnumbered! It should have caused a minor version bump!**
@ -217,10 +233,12 @@ https://hackage.haskell.org/package/dejafu-0.5.1.2
---------------------------------------------------------------------------------------------------
0.5.1.1 [2017-02-25] (git tag: [dejafu-0.5.1.1][])
0.5.1.1
-------
https://hackage.haskell.org/package/dejafu-0.5.1.1
- **Date** 2017-02-25
- **Git tag** [dejafu-0.5.1.1][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.5.1.1
### Fixed
@ -234,10 +252,12 @@ https://hackage.haskell.org/package/dejafu-0.5.1.1
---------------------------------------------------------------------------------------------------
0.5.1.0 [2017-02-25] (git tag: [dejafu-0.5.1.0][])
0.5.1.0
-------
https://hackage.haskell.org/package/dejafu-0.5.1.0
- **Date** 2017-02-25
- **Git tag** [dejafu-0.5.1.0][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.5.1.0
### Test.DejaFu
@ -265,10 +285,12 @@ https://hackage.haskell.org/package/dejafu-0.5.1.0
---------------------------------------------------------------------------------------------------
0.5.0.2 [2017-02-22] (git tag: [dejafu-0.5.0.2][])
0.5.0.2
-------
https://hackage.haskell.org/package/dejafu-0.5.0.2
- **Date** 2017-02-22
- **Git tag** [dejafu-0.5.0.2][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.5.0.2
**This version was misnumbered! It should have caused a major version bump!**
@ -295,10 +317,12 @@ https://hackage.haskell.org/package/dejafu-0.5.0.2
---------------------------------------------------------------------------------------------------
0.5.0.1 [2017-02-21] (git tag: [dejafu-0.5.0.1][])
0.5.0.1
-------
**This version was never pushed to hackage, whoops!**
- **Date** 2017-02-21
- **Git tag** [dejafu-0.5.0.1][]
- **This version was never pushed to hackage, whoops!**
### Fixed
@ -310,10 +334,12 @@ https://hackage.haskell.org/package/dejafu-0.5.0.2
---------------------------------------------------------------------------------------------------
0.5.0.0 [2017-02-21] (git tag: [dejafu-0.5.0.0][])
0.5.0.0
-------
https://hackage.haskell.org/package/dejafu-0.5.0.0
- **Date** 2017-02-21
- **Git tag** [dejafu-0.5.0.0][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.5.0.0
### Test.DejaFu
@ -356,10 +382,12 @@ https://hackage.haskell.org/package/dejafu-0.5.0.0
---------------------------------------------------------------------------------------------------
0.4.0.0 [2016-09-10] (git tag: [dejafu-0.4.0.0][])
0.4.0.0
-------
https://hackage.haskell.org/package/dejafu-0.4.0.0
- **Date** 2016-09-10
- **Git tag** [dejafu-0.4.0.0][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.4.0.0
### Test.DejaFu
@ -403,10 +431,12 @@ https://hackage.haskell.org/package/dejafu-0.4.0.0
---------------------------------------------------------------------------------------------------
0.3.2.1 [2016-07-21] (git tag: [dejafu-0.3.2.1][])
0.3.2.1
-------
https://hackage.haskell.org/package/dejafu-0.3.2.1
- **Date** 2016-07-21
- **Git tag** [dejafu-0.3.2.1][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.3.2.1
### Fixed
@ -419,10 +449,12 @@ https://hackage.haskell.org/package/dejafu-0.3.2.1
---------------------------------------------------------------------------------------------------
0.3.2.0 [2016-06-06] (git tag: [dejafu-0.3.2.0][])
0.3.2.0
-------
https://hackage.haskell.org/package/dejafu-0.3.2.0
- **Date** 2016-06-06
- **Git tag** [dejafu-0.3.2.0][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.3.2.0
**Builds with both dpor-0.1 and dpor-0.2, however some improvements require dpor-0.2.**
@ -444,10 +476,12 @@ https://hackage.haskell.org/package/dejafu-0.3.2.0
---------------------------------------------------------------------------------------------------
0.3.1.1 [2016-05-26] (git tag: [dejafu-0.3.1.1][])
0.3.1.1
-------
https://hackage.haskell.org/package/dejafu-0.3.1.1
- **Date** 2016-05-26
- **Git tag** [dejafu-0.3.1.1][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.3.1.1
### Miscellaneous
@ -459,10 +493,12 @@ https://hackage.haskell.org/package/dejafu-0.3.1.1
---------------------------------------------------------------------------------------------------
0.3.1.0 [2016-05-02] (git tag: [dejafu-0.3.1.0][])
0.3.1.0
-------
https://hackage.haskell.org/package/dejafu-0.3.1.0
- **Date** 2016-05-02
- **Git tag** [dejafu-0.3.1.0][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.3.1.0
### Fixed
@ -475,10 +511,12 @@ https://hackage.haskell.org/package/dejafu-0.3.1.0
---------------------------------------------------------------------------------------------------
0.3.0.0 [2016-04-03] (git tag: [dejafu-0.3.0.0][])
0.3.0.0
-------
https://hackage.haskell.org/package/dejafu-0.3.0.0
- **Date** 2016-04-03
- **Git tag** [dejafu-0.3.0.0][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.3.0.0
**The minimum supported version of GHC is now 7.10.**
@ -491,10 +529,12 @@ logs.
---------------------------------------------------------------------------------------------------
0.2.0.0 [2015-12-01] (git tag: [0.2.0.0][])
0.2.0.0
-------
https://hackage.haskell.org/package/dejafu-0.2.0.0
- **Date** 2015-12-01
- **Git tag** [0.2.0.0][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.2.0.0
I didn't write proper release notes, and this is so far back I don't really care to dig through the
logs.
@ -505,10 +545,12 @@ logs.
---------------------------------------------------------------------------------------------------
0.1.0.0 [2015-08-27] (git tag: [0.1.0.0][])
0.1.0.0
-------
https://hackage.haskell.org/package/dejafu-0.1.0.0
- **Date** 2015-08-27
- **Git tag** [0.1.0.0][]
- **Hackage** https://hackage.haskell.org/package/dejafu-0.1.0.0
Initial release. Go read the API docs.

1
doc/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_build

20
doc/Makefile Normal file
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = DjFu
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

207
doc/advanced.rst Normal file
View File

@ -0,0 +1,207 @@
Advanced Usage
==============
Déjà Fu tries to have a sensible set of defaults, but there are some
times when the defaults are not suitable. There are a lot of knobs
provided to tweak how things work.
.. _settings:
Execution settings
------------------
The ``autocheck``, ``dejafu``, and ``dejafus`` functions from
``Test.DejaFu`` all have a variant which lets you specify the **memory
model** used for ``CRef`` operations and the **way** in which
schedules are explored. These are ``autocheckWay``, ``dejafuWay``,
and ``dejafusWay`` (plus ``IO`` variants).
Memory model
~~~~~~~~~~~~
Threads running under modern multicore processors do not behave as a
simple interleaving of the individual thread actions. Processors do
all sorts of complex things to increase speed, such as buffering
writes. For concurrent programs which make use of non-synchronised
functions (such as ``readCRef`` coupled with ``writeCRef``) different
memory models may yield different results.
As an example, consider this program from the ``Data.IORef``
documentation. Two ``CRef`` variables are created, and two threads
spawned to write to and read from both. Each thread returns the value
it observes.
.. code-block:: haskell
example :: MonadConc m => m (Bool, Bool)
example = do
r1 <- newCRef False
r2 <- newCRef False
x <- spawn $ writeCRef r1 True >> readCRef r2
y <- spawn $ writeCRef r2 True >> readCRef r1
(,) <$> readMVar x <*> readMVar y
Under a sequentially consistent memory model the possible results are
``(True, True)``, ``(True, False)``, and ``(False, True)``. Under
total or partial store order, ``(False, False)`` is also a possible
result, even though there is no interleaving of the threads which can
lead to this.
We can see this by testing with different memory models:
.. code-block:: none
> autocheckWay defaultWay SequentialConsistency example
[pass] Never Deadlocks (checked: 6)
[pass] No Exceptions (checked: 6)
[fail] Consistent Result (checked: 5)
(False,True) S0---------S1----S0--S2----S0--
(True,True) S0---------S1-P2----S1---S0---
(True,False) S0---------S2----S1----S0---
False
> autocheckWay defaultWay TotalStoreOrder example
[pass] Never Deadlocks (checked: 28)
[pass] No Exceptions (checked: 28)
[fail] Consistent Result (checked: 27)
(False,True) S0---------S1----S0--S2----S0--
(False,False) S0---------S1--P2----S1--S0---
(True,False) S0---------S2----S1----S0---
(True,True) S0---------S1-C-S2----S1---S0---
False
Traces for non-sequentially-consistent memory models show where
``CRef`` writes are committed, which makes a write visible to all
threads rather than just the one which performed the write.
The default memory model is total store order, as that is how x86
processors behave.
Schedule bounds
~~~~~~~~~~~~~~~
Schedule bounding is an optimisation which only considers schedules
within some bound. This sacrifices completeness outside of the bound,
but can drastically reduce the number of schedules to test, and is in
fact necessary for non-terminating programs.
There are three supported types of bounds:
Pre-emption bounding
Restricts the number of pre-emptive context switches. A context
switch is pre-emptive if the previously executing thread is still
runnable and did not explicitly yield.
Fair bounding
Restricts how many times each thread can yield, by bounding the
maximum difference between the thread which has yielded the most,
and the thread which has yielded the least.
Length bounding
Restricts how long an execution can be, in terms of Déjà Fu's
"primitive actions".
The standard testing mechanism uses all three bounds. Pre-emption +
fair bounding is useful for programs which use spinlocks or yield for
control flow, but which are otherwise terminating. Length bounding
makes it possible to test potentially non-terminating programs.
If you wanted to disable pre-emption bounding, for example, you can do
so like so:
.. code-block:: haskell
dejafuWay (systematically defaultBounds { boundPreemp = Nothing })
defaultMemType
myAction
("Assert the thing holds", myPredicate)
Random scheduling
~~~~~~~~~~~~~~~~~
If you don't want to find all executions within the schedule bounds,
and instead want to test a fixed number of executions, you can use
random scheduling.
There are three variants:
``randomly randomGen numExecutions``
Perform the given number of executions using weighted random
scheduling. On creation, a thread is given a random weight, which
is used to perform a nonuniform random selection amongst the
runnable threads at every scheduling point.
``uniformly randomGen numExecutions``
Like ``randomly``, but rather than a weighted selection, it's a
uniform selection.
``swarmy randomGen numExecutions numUses``
Like ``randomly``, but each set of thread weights is used for
``numUses`` executions.
These are all given as the first argument to ``dejafuWay`` (and its
ilk), like ``systematically``. So for example you could do this:
.. code-block:: haskell
dejafuWay (randomly (mkStdGen 42) 1000)
defaultMemType
myAction
("Assert the thing holds", myPredicate)
.. _performance:
Performance tuning
------------------
* Are you happy to trade space for time?
Consider computing the results once and running multiple
predicates over the output: this is what ``dejafus`` /
``testDejafus`` / etc does.
* Can you sacrifice completeness?
Consider using the random testing functionality. See the ``*Way``
functions and ``Test.DejaFu.SCT.sct{Uniform,Weighted}Random``.
* Would strictness help?
Consider using the strict functions in ``Test.DejaFu.SCT`` (the
ones ending with a ``'``).
* Do you just want the set of results, and don't care about traces?
Consider using ``Test.DejaFu.SCT.resultsSet``.
* Do you know something about the sort of results you care about?
Consider discarding results you *don't* care about. See the
``*Discard`` functions in ``Test.DejaFu``, ``Test.DejaFu.SCT``,
and ``Test.{HUnit,Tasty}.DejaFu``.
For example, let's say you want to know if your test case deadlocks,
and are going to sacrifice completeness because your possible
state-space is huge. You could do it like this:
.. code-block:: haskell
dejafuDiscard
-- "efa" == "either failure a", discard everything but deadlocks
(\efa -> if efa == Left Deadlock then Nothing else Just DiscardResultAndTrace)
-- try 10000 executions with random scheduling
(randomly (mkStdGen 42) 10000)
-- use the default memory model
defaultMemType
-- your test case
testCase
-- the predicate to check (which is a bit redundant in this case)
("Never Deadlocks", deadlocksNever)

View File

@ -0,0 +1 @@
../concurrency/CHANGELOG.markdown

1
doc/changelog_dejafu.md Symbolic link
View File

@ -0,0 +1 @@
../dejafu/CHANGELOG.markdown

View File

@ -0,0 +1 @@
../hunit-dejafu/CHANGELOG.markdown

View File

@ -0,0 +1 @@
../tasty-dejafu/CHANGELOG.markdown

160
doc/conf.py Normal file
View File

@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
#
# Déjà Fu documentation build configuration file, created by
# sphinx-quickstart on Tue Aug 15 19:55:19 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
import os
from recommonmark.parser import CommonMarkParser
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = []
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
source_parsers = {
'.md': CommonMarkParser,
}
source_suffix = ['.rst', '.md']
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Déjà Fu'
copyright = u'2017, Michael Walker'
author = u'Michael Walker'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = u'0.7.1.0'
# The full version, including alpha/beta/rc tags.
release = u'0.7.1.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = os.environ['SPHINX_THEME'] if 'SPHINX_THEME' in os.environ else 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'DejaFudoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'DejaFu.tex', u'Déjà Fu Documentation',
u'Michael Walker', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'dejafu', u'Déjà Fu Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'DejaFu', u'Déjà Fu Documentation',
author, 'DejaFu', 'Concurrency testing for Haskell.',
'Miscellaneous'),
]

1
doc/contributing.md Symbolic link
View File

@ -0,0 +1 @@
../CONTRIBUTING.markdown

181
doc/getting_started.rst Normal file
View File

@ -0,0 +1,181 @@
Getting Started
===============
[Déjà Fu is] A martial art in which the user's limbs move in time
as well as space, […] It is best described as "the feeling that
you have been kicked in the head this way before"
**Terry Pratchett, Thief of Time**
Déjà Fu is a unit-testing library for concurrent Haskell programs.
Tests are deterministic and expressive, making it easy and convenient
to test your threaded code. Available on GitHub_, Hackage_, and
Stackage_.
.. _GitHub: https://github.com/barrucadu/dejafu
.. _Hackage: https://hackage.haskell.org/package/dejafu
.. _Stackage: https://www.stackage.org/package/dejafu
Features:
* An abstraction over the concurrency functionality in ``IO``
* Deterministic testing of nondeterministic code
* Both complete (slower) and incomplete (faster) modes
* A unit-testing-like approach to writing test cases
* A property-testing-like approach to comparing stateful operations
* Testing of potentially nonterminating programs
* Integration with HUnit_ and tasty_
.. _HUnit: https://hackage.haskell.org/package/HUnit
.. _Tasty: https://hackage.haskell.org/package/tasty
There are a few different packages under the Déjà Fu umbrella:
.. csv-table::
:header: "Package", "Version", "Summary"
"concurrency_", "1.1.2.1", "Typeclasses, functions, and data types for concurrency and STM"
"dejafu_", "0.7.1.0", "Systematic testing for Haskell concurrency"
"hunit-dejafu_", "0.7.0.0", "Déjà Fu support for the HUnit test framework"
"tasty-dejafu_", "0.7.0.0", "Déjà Fu support for the tasty test framework"
.. _concurrency: https://hackage.haskell.org/package/concurrency
.. _dejafu: https://hackage.haskell.org/package/dejafu
.. _hunit-dejafu: https://hackage.haskell.org/package/hunit-dejafu
.. _tasty-dejafu: https://hackage.haskell.org/package/tasty-dejafu
Everything is on Hackage and Stackage, and the last three major GHC
versions (currently 8.2, 8.0, and 7.10) are supported.
Installation
------------
Install from Hackage globally:
.. code-block:: none
$ cabal-install dejafu
Or add it to your cabal file:
.. code-block:: none
build-depends: ...
, dejafu
Or to your package.yaml:
.. code-block:: none
dependencies:
...
- dejafu
Quick start guide
-----------------
Déjà Fu supports unit testing, and comes with a helper function
called ``autocheck`` to look for some common issues. Let's see it in
action:
.. code-block:: haskell
import Control.Concurrent.Classy
myFunction :: MonadConc m => m String
myFunction = do
var <- newEmptyMVar
fork (putMVar var "hello")
fork (putMVar var "world")
readMVar var
That ``MonadConc`` is a typeclass abstraction over concurrency, but
we'll get onto that shortly. First, the result of testing:
.. code-block:: none
> autocheck myFunction
[pass] Never Deadlocks (checked: 12)
[pass] No Exceptions (checked: 12)
[fail] Consistent Result (checked: 11)
"hello" S0----S1-P2-S0--
"world" S0----S2--S0-P1-S0-
False
There are no deadlocks or uncaught exceptions, which is good; but the
program is (as you probably spotted) nondeterministic!
Along with each result, Déjà Fu gives us a representative execution
trace in an abbreviated form. ``Sn`` means that thread ``n`` started
executing, and ``Pn`` means that thread ``n`` pre-empted the
previously running thread.
Why Déjà Fu?
------------
Testing concurrent programs is difficult, because in general they are
nondeterministic. This leads to people using work-arounds like
running their testsuite many thousands of times; or running their
testsuite while putting their machine under heavy load.
These approaches are inadequate for a few reasons:
* **How many runs is enough?** When you are just hopping to spot a bug
by coincidence, how do you know to stop?
* **How do you know if you've fixed a bug you saw previously?**
Because the scheduler is a black box, you don't know if the
previously buggy schedule has been re-run.
* **You won't actually get that much scheduling variety!** Operating
systems and language runtimes like to run threads for long periods
of time, which reduces the variety you get (and so drives up the
number of runs you need).
Déjà Fu addresses these points by offering *complete* testing. You
can run a test case and be guaranteed to find all results with some
bounds. These bounds can be configured, or even disabled! The
underlying approach used is smarter than merely trying all possible
executions, and will in general explore the state-space quickly.
If your test case is just too big for complete testing, there is also
a random scheduling mode, which is necessarily *incomplete*. However,
Déjà Fu will tend to produce much more schedule variety than just
running your test case in ``IO`` the same number of times, and so bugs
will tend to crop up sooner. Furthermore, as you get execution traces
out, you can be certain that a bug has been fixed by simply following
the trace by eye.
**If you'd like to get involved with Déjà Fu**, check out `the
"beginner friendly" label on the issue tracker`__.
.. __: https://github.com/barrucadu/dejafu/issues?q=is%3Aissue+is%3Aopen+label%3A%22beginner+friendly%22
Questions, feedback, discussion
-------------------------------
* For general help talk to me in IRC (barrucadu in #haskell) or shoot
me an email (mike@barrucadu.co.uk)
* For bugs, issues, or requests, please `file an issue`__.
.. __: https://github.com/barrucadu/dejafu/issues
Bibliography
------------
Déjà Fu has been produced as part of my Ph.D work, and wouldn't be
possible without prior research. Here are the core papers:
* Bounded partial-order reduction, K. Coons, M. Musuvathi,
and K. McKinley (2013)
* Dynamic Partial Order Reduction for Relaxed Memory
Models, N. Zhang, M. Kusano, and C. Wang (2015)
* Concurrency Testing Using Schedule Bounding: an Empirical
Study, P. Thompson, A. Donaldson, and A. Betts (2014)
* On the Verification of Programs on Relaxed Memory
Models, A. Linden (2014)

33
doc/index.rst Normal file
View File

@ -0,0 +1,33 @@
This is Déjà Fu
===============
[Déjà Fu is] A martial art in which the user's limbs move in time
as well as space, […] It is best described as "the feeling that
you have been kicked in the head this way before"
**Terry Pratchett, Thief of Time**
.. toctree::
:maxdepth: 2
:caption: User Documentation
getting_started
typeclass
unit_testing
refinement_testing
advanced
.. toctree::
:maxdepth: 2
:caption: Release Notes
concurrency <changelog_concurrency>
dejafu <changelog_dejafu>
hunit-dejafu <changelog_hunit-dejafu>
tasty-dejafu <changelog_tasty-dejafu>
.. toctree::
:maxdepth: 2
:caption: Developer Documentation
contributing

159
doc/refinement_testing.rst Normal file
View File

@ -0,0 +1,159 @@
Refinement Testing
==================
Déjà Fu also supports a form of property-testing where you can check
things about the side-effects of stateful operations. For example, we
can assert that ``readMVar`` is equivalent to sequencing ``takeMVar``
and ``putMVar`` like so:
.. code-block:: haskell
prop_mvar_read_take_put =
sig readMVar `equivalentTo` sig (\v -> takeMVar v >>= putMVar v)
Given the signature function, ``sig``, defined in the next section.
If we check this, our property fails!
.. code-block:: none
> check prop_mvar_read_take_put
*** Failure: (seed Just 0)
left: [(Nothing,Just 0)]
right: [(Nothing,Just 0),(Just Deadlock,Just 0)]
False
This is because ``readMVar`` is atomic, whereas sequencing
``takeMVar`` with ``putMVar`` is not, and so another thread can
interfere with the ``MVar`` in the middle. The ``check`` and
``equivalentTo`` functions come from ``Test.DejaFu.Refinement`` (also
re-exported from ``Test.DejaFu``).
Signatures
----------
A signature tells the property-tester something about the state your
operation acts upon, it has a few components:
.. code-block:: haskell
data Sig s o x = Sig
{ initialise :: x -> ConcIO s
, observe :: s -> x -> ConcIO o
, interfere :: s -> x -> ConcIO ()
, expression :: s -> ConcIO ()
}
* ``s`` is the **state type**, it's the thing which your operations
mutate. For ``readMVar``, the state is some ``MVar a``.
* ``o`` is the **observation type**, it's some pure (and comparable)
proxy for a snapshot of your mutable state. For ``MVar a``, the
observation is probably a ``Maybe a``.
* ``x`` is the **seed type**, it's some pure value used to construct
the initial mutable state. For ``MVar a``, the seed is probably a
``Maybe a``.
* ``ConcIO`` is just one of the instances of ``MonadConc`` that Déjà
Fu defines for testing purposes. Just write code polymorphic in the
monad as usual, and all will work.
The ``initialise``, ``observe``, and ``expression`` functions should
be self-explanatory, but the ``interfere`` one may not be. It's the
job of the ``interfere`` function to change the state in some way;
it's run concurrently with the expression, to simulate the
nondeterministic action of other threads.
Here's a concrete example for our ``MVar`` example:
.. code-block:: haskell
sig :: (MVar ConcIO Int -> ConcIO a) -> Sig (MVar ConcIO Int) (Maybe Int) (Maybe Int)
sig e = Sig
{ initialise = maybe newEmptyMVar newMVar
, observe = \v _ -> tryTakeMVar v
, interfere = \v s -> tryTakeMVar v >> maybe (pure ()) (\x -> void $ tryPutMVar v (x * 1000)) s
, expression = void . e
}
The ``observe`` function should be deterministic, but as it is run
after the normal execution ends, it may have side-effects on the
state. The ``interfere`` function can do just about anything [#]_,
but a poor one may result in the property-checker being unable to
distinguish between atomic and nonatomic expressions.
.. [#] There are probably some concrete rules for a good function, but
I haven't figured them out yet.
Properties
----------
A property is a pair of signatures linked by one of three provided
functions. These functions are:
.. csv-table::
:header: "Function", "Operator", "Checks that..."
"``refines``", "``=>=``", "...the left is equivalent to, or strictly refines, the right"
"``strictlyRefines``", "``->-``", "...the left has strictly fewer behaviours than the right"
"``equivalentTo``", "``===``", "...the left and right have exactly the same behaviours"
The signatures can have different state types, as long as the seed and
observation types are the same. This lets you compare different
implementations of the same idea: for example, comparing a concurrent
stack implemented using ``MVar`` with one implemented using ``IORef``.
Properties can have parameters, given in the obvious way:
.. code-block:: haskell
check $ \a b c -> sig1 ... `op` sig2 ...
Under the hood, seed and parameter values are generated using the
LeanCheck_ package, an enumerative property-based testing library.
This means that any types you use will need to have a ``Listable``
instance.
.. _LeanCheck: https://hackage.haskell.org/package/leancheck
You can also think about the three functions in terms of sets of
results, where a result is a ``(Maybe Failure, o)`` value. A
``Failure`` is something like deadlocking, or being killed by an
exception; ``o`` is the observation type. An observation is always
made, even if execution of the expression fails.
.. csv-table::
:header: "Function", "Result-set operation"
"``refines``", "For all seed and parameter assignments, subset-or-equal"
"``strictlyRefines``", "For at least one seed and parameter assignment, proper subset; for all others, subset-or-equal"
"``equivalentTo``", "For all seed and parameter assignments, equality"
Finally, there is an ``expectFailure`` function, which inverts the
expected result of a property.
The Déjà Fu testsuite has `a collection of refinement properties`__,
which may help you get a feel for this sort of testing.
.. __: https://github.com/barrucadu/dejafu/blob/2a15549d97c2fa12f5e8b92ab918fdb34da78281/dejafu-tests/Cases/Refinement.hs
Using HUnit and Tasty
---------------------
As for unit testing, HUnit_ and tasty_ integration is provided for
refinement testing in the hunit-dejafu_ and tasty-dejafu_ packages.
.. _HUnit: https://hackage.haskell.org/package/HUnit
.. _Tasty: https://hackage.haskell.org/package/tasty
.. _hunit-dejafu: https://hackage.haskell.org/package/hunit-dejafu
.. _tasty-dejafu: https://hackage.haskell.org/package/tasty-dejafu
The ``testProperty`` function is used to check properties. Our example from the start becomes:
.. code-block:: haskell
testProperty "Read is equivalent to Take then Put" prop_mvar_read_take_put

74
doc/typeclass.rst Normal file
View File

@ -0,0 +1,74 @@
Typeclasses
===========
We don't use the regular ``Control.Concurrent`` and
``Control.Exception`` modules, we use typeclass-generalised ones
instead from the `concurrency`_ and `exceptions`_ packages.
.. _concurrency: https://hackage.haskell.org/package/concurrency
.. _exceptions: https://hackage.haskell.org/package/exceptions
Porting guide
-------------
If you want to test some existing code, you'll need to port it to the
appropriate typeclass. The typeclass is necessary, because we can't
peek inside ``IO`` and ``STM`` values, so we need to able to plug in
an alternative implementation when testing.
Fortunately, this tends to be a fairly mechanical and type-driven
process:
1. Import ``Control.Concurrent.Classy.*`` instead of
``Control.Concurrent.*``
2. Import ``Control.Monad.Catch`` instead of ``Control.Exception``
3. Change your monad type:
* ``IO a`` becomes ``MonadConc m => m a``
* ``STM a`` becomes ``MonadSTM stm => stm a``
4. Parameterise your state types by the monad:
* ``TVar`` becomes ``TVar stm``
* ``MVar`` becomes ``MVar m``
* ``IORef`` becomes ``CRef m`` [#]_
5. Some functions are renamed:
* ``*IORef*`` becomes ``*CRef*``
* ``forkIO*`` becomes ``fork*``
* ``atomicModifyIORefCAS*`` becomes ``modifyCRefCAS*``
6. Fix the type errors
If you're lucky enough to be starting a new concurrent Haskell
project, you can just program against the ``MonadConc`` interface.
.. [#] I felt that calling it ``IORef`` when there was no I/O involved
would be confusing, but this was perhaps a mistake.
What if I really need I/O?
--------------------------
You can use ``MonadIO`` and ``liftIO`` with ``MonadConc``, for
instance if you need to talk to a database (or just use some existing
library which needs real I/O).
To test ``IO``-using code, there are some rules you need to follow:
1. Given the same set of scheduling decisions, your ``IO`` code must
be deterministic [#]_
2. As ``IO`` values can't be broken up into smaller chunks, they
should be kept small; otherwise dejafu may miss buggy interleavings
3. You absolutely cannot block on the action of another thread inside
``IO``, or the test execution will just deadlock.
.. [#] This is only essential if you're using the systematic testing
(the default). Nondeterministic ``IO`` won't break the random
testing, it'll just make things more confusing.

170
doc/unit_testing.rst Normal file
View File

@ -0,0 +1,170 @@
Unit Testing
============
Writing tests with Déjà Fu is a little different to traditional unit
testing, as your test case may have multiple results. A "test" is a
combination of your code, and a predicate which says something about
the set of allowed results.
Most tests will look something like this:
.. code-block:: haskell
dejafu myAction ("Assert the thing holds", myPredicate)
The ``dejafu`` function comes from ``Test.DejaFu``. It can't deal
with testcases which need ``MonadIO``, use ``dejafuIO`` for that.
Actions
----------
An action is just something with the type ``MonadConc m => m a``, or
``(MonadConc m, MonadIO m) => m a`` for some ``a`` that your chosen
predicate can deal with.
For example, some users on Reddit found a couple of apparent bugs in
the auto-update_ package a while ago (`thread here`__). As the
package is simple and self-contained, I translated it to the
``MonadConc`` abstraction and wrote a couple of tests to replicate the
bugs. Here they are:
.. code-block:: haskell
deadlocks :: MonadConc m => m ()
deadlocks = do
auto <- mkAutoUpdate defaultUpdateSettings
auto
nondeterministic :: forall m. MonadConc m => m Int
nondeterministic = do
var <- newCRef 0
let settings = (defaultUpdateSettings :: UpdateSettings m ())
{ updateAction = atomicModifyCRef var (\x -> (x+1, x)) }
auto <- mkAutoUpdate settings
auto
auto
.. _auto-update: https://hackage.haskell.org/package/auto-update
.. __: https://www.reddit.com/r/haskell/comments/2i5d7m/updating_autoupdate/
These actions action could be tested with ``autocheck``, and the
issues would be revealed. The use of ``ScopedTypeVariables`` in the
second is an unfortunate example of what can happen when everything
becomes more polymorphic. But other than that, note how there is no
special mention of Déjà Fu in the actions: it's just normal concurrent
Haskell, simply written against a different interface.
The modified package is included `in the Déjà Fu testsuite`__, if you
want to see the full code. [#]_
.. __: https://github.com/barrucadu/dejafu/blob/2a15549d97c2fa12f5e8b92ab918fdb34da78281/dejafu-tests/Examples/AutoUpdate.hs
.. [#] The predicates in dejafu-tests are a little confusing, as
they're the opposite of what you would normally write! These
predicates are checking that the bug is found, not that the
code is correct.
Predicates
----------
There are a few predicates built in, and some helpers to define your
own.
.. csv-table::
:widths: 25, 75
``abortsNever``,"checks that the computation never aborts"
``abortsAlways``,"checks that the computation always aborts"
``abortsSometimes``,"checks that the computation aborts at least once"
An **abort** is where the scheduler chooses to terminate execution
early. If you see it, it probably means that a test didn't terminate
before it hit the execution length limit.
.. csv-table::
:widths: 25, 75
``deadlocksNever``,"checks that the computation never deadlocks"
``deadlocksAlways``,"checks that the computation always deadlocks"
``deadlocksSometimes``,"checks that the computation deadlocks at least once"
**Deadlocking** is where every thread becomes blocked. This can be,
for example, if every thread is trying to read from an ``MVar`` that
has been emptied.
.. csv-table::
:widths: 25, 75
``exceptionsNever``,"checks that the main thread is never killed by an exception"
``exceptionsAlways``,"checks that the main thread is always killed by an exception"
``exceptionsSometimes``,"checks that the main thread is killed by an exception at least once"
An uncaught **exception** in the main thread kills the process. These
can be synchronous (thrown in the main thread) or asynchronous (thrown
to it from a different thread).
.. csv-table::
:widths: 25, 75
``alwaysSame``,"checks that the computation is deterministic"
``notAlwaysSame``,"checks that the computation is nondeterministic"
Checking for **determinism** will also find nondeterministic failures:
deadlocking (for instance) is still a result of a test!
.. csv-table::
:widths: 25, 75
``alwaysTrue p``,"checks that ``p`` is true for every result"
``alwaysTrue2 p``,"checks that ``p`` is true for every pair of results"
``somewhereTrue p``,"checks that ``p`` is true for at least one result"
These can be used to check custom predicates. For example, you might
want all your results to be less than five.
.. csv-table::
:widths: 25, 75
``gives xs``,"checks that the set of results is exactly ``xs`` (which may include failures)"
``gives' xs``,"checks that the set of results is exactly ``xs`` (which may not include failures)"
These let you say exactly what you want the results to be. Your test
will fail if it has any extra results, or misses a result.
You can check multiple predicates against the same collection of
results using the ``dejafus`` and ``dejafusIO`` functions. These
avoid recomputing the results, and so may be faster than multiple
``dejafu`` / ``dejafuIO`` calls. See :ref:`performance`.
Using HUnit and Tasty
---------------------
By itself, Déjà Fu has no framework in place for named test groups and
parallel execution or anything like that. It does one thing and does
it well, which is running test cases for concurrent programs. HUnit_
and tasty_ integration is provided to get more of the features you'd
expect from a testing framework.
.. _HUnit: https://hackage.haskell.org/package/HUnit
.. _Tasty: https://hackage.haskell.org/package/tasty
The integration is provided by the hunit-dejafu_ and tasty-dejafu_ packages.
.. _hunit-dejafu: https://hackage.haskell.org/package/hunit-dejafu
.. _tasty-dejafu: https://hackage.haskell.org/package/tasty-dejafu
There's a simple naming convention used: the ``Test.DejaFu`` function
``dejafuFoo`` is wrapped in the appropriate way and exposed as
``testDejafuFoo`` from ``Test.HUnit.DejaFu`` and
``Test.Tasty.DejaFu``.
Our example from the start becomes:
.. code-block:: haskell
testDejafu myAction "Assert the thing holds" myPredicate
The ``autocheck`` and ``autocheckIO`` functions are exposed as
``testAuto`` and ``testAutoIO``.

View File

@ -7,10 +7,12 @@ This project is versioned according to the [Package Versioning Policy](https://p
*de facto* standard Haskell versioning scheme.
0.7.0.0 [2017-08-10] (git tag: [hunit-dejafu-0.7.0.0][])
0.7.0.0
-------
https://hackage.haskell.org/package/hunit-dejafu-0.7.0.0
- **Date** 2017-08-10
- **Git tag** [hunit-dejafu-0.7.0.0][]
- **Hackage** https://hackage.haskell.org/package/hunit-dejafu-0.7.0.0
### Test.HUnit.DejaFu
@ -28,10 +30,12 @@ https://hackage.haskell.org/package/hunit-dejafu-0.7.0.0
---------------------------------------------------------------------------------------------------
0.6.0.0 [2017-06-07] (git tag: [hunit-dejafu-0.6.0.0][])
0.6.0.0
-------
https://hackage.haskell.org/package/hunit-dejafu-0.6.0.0
- **Date** 2017-06-07
- **Git tag** [hunit-dejafu-0.6.0.0][]
- **Hackage** https://hackage.haskell.org/package/hunit-dejafu-0.6.0.0
### Test.HUnit.DejaFu
@ -55,10 +59,12 @@ https://hackage.haskell.org/package/hunit-dejafu-0.6.0.0
---------------------------------------------------------------------------------------------------
0.5.0.0 [2017-04-08] (git tag: [hunit-dejafu-0.5.0.0][])
0.5.0.0
-------
https://hackage.haskell.org/package/hunit-dejafu-0.5.0.0
- **Date** 2017-04-08
- **Git tag** [hunit-dejafu-0.5.0.0][]
- **Hackage** https://hackage.haskell.org/package/hunit-dejafu-0.5.0.0
### Test.HUnit.DejaFu
@ -76,10 +82,12 @@ https://hackage.haskell.org/package/hunit-dejafu-0.5.0.0
---------------------------------------------------------------------------------------------------
0.4.0.1 [2017-03-20] (git tag: [hunit-dejafu-0.4.0.1][])
0.4.0.1
-------
https://hackage.haskell.org/package/hunit-dejafu-0.4.0.1
- **Date** 2017-03-20
- **Git tag** [hunit-dejafu-0.4.0.1][]
- **Hackage** https://hackage.haskell.org/package/hunit-dejafu-0.4.0.1
### Miscellaneous
@ -91,10 +99,12 @@ https://hackage.haskell.org/package/hunit-dejafu-0.4.0.1
---------------------------------------------------------------------------------------------------
0.4.0.0 [2017-02-21] (git tag: [hunit-dejafu-0.4.0.0][])
0.4.0.0
-------
https://hackage.haskell.org/package/hunit-dejafu-0.4.0.0
- **Date** 2017-02-21
- **Git tag** [hunit-dejafu-0.4.0.0][]
- **Hackage** https://hackage.haskell.org/package/hunit-dejafu-0.4.0.0
### Test.HUnit.DejaFu
@ -112,10 +122,12 @@ https://hackage.haskell.org/package/hunit-dejafu-0.4.0.0
---------------------------------------------------------------------------------------------------
0.3.0.3 [2016-10-22] (git tag: [hunit-dejafu-0.3.0.3][])
0.3.0.3
-------
https://hackage.haskell.org/package/hunit-dejafu-0.3.0.3
- **Date** 2016-10-22
- **Git tag** [hunit-dejafu-0.3.0.3][]
- **Hackage** https://hackage.haskell.org/package/hunit-dejafu-0.3.0.3
### Miscellaneous
@ -127,10 +139,12 @@ https://hackage.haskell.org/package/hunit-dejafu-0.3.0.3
---------------------------------------------------------------------------------------------------
0.3.0.2 [2016-09-10] (git tag: [hunit-dejafu-0.3.0.2][])
0.3.0.2
-------
https://hackage.haskell.org/package/hunit-dejafu-0.3.0.2
- **Date** 2016-09-10
- **Git tag** [hunit-dejafu-0.3.0.2][]
- **Hackage** https://hackage.haskell.org/package/hunit-dejafu-0.3.0.2
### Miscellaneous
@ -142,10 +156,12 @@ https://hackage.haskell.org/package/hunit-dejafu-0.3.0.2
---------------------------------------------------------------------------------------------------
0.3.0.1 [2016-05-26] (git tag: [hunit-dejafu-0.3.0.1][])
0.3.0.1
-------
https://hackage.haskell.org/package/hunit-dejafu-0.3.0.1
- **Date** 2016-05-26
- **Git tag** [hunit-dejafu-0.3.0.1][]
- **Hackage** https://hackage.haskell.org/package/hunit-dejafu-0.3.0.1
### Miscellaneous
@ -157,10 +173,12 @@ https://hackage.haskell.org/package/hunit-dejafu-0.3.0.1
---------------------------------------------------------------------------------------------------
0.3.0.0 [2016-04-28] (git tag: [hunit-dejafu-0.3.0.0][])
0.3.0.0
-------
https://hackage.haskell.org/package/hunit-dejafu-0.3.0.0
- **Date** 2016-04-28
- **Git tag** [hunit-dejafu-0.3.0.0][]
- **Hackage** https://hackage.haskell.org/package/hunit-dejafu-0.3.0.0
### Test.HUnit.DejaFu
@ -177,10 +195,12 @@ https://hackage.haskell.org/package/hunit-dejafu-0.3.0.0
---------------------------------------------------------------------------------------------------
0.2.1.0 [2016-04-03] (git tag: [hunit-dejafu-0.2.1.0][])
0.2.1.0
-------
**This version was never pushed to hackage, whoops!**
- **Date** 2016-04-03
- **Git tag** [hunit-dejafu-0.2.1.0][]
- **This version was never pushed to hackage, whoops!**
### Miscellaneous
@ -192,10 +212,12 @@ https://hackage.haskell.org/package/hunit-dejafu-0.3.0.0
---------------------------------------------------------------------------------------------------
0.2.0.0 [2015-12-01] (git tag: [0.2.0.0][])
0.2.0.0
-------
https://hackage.haskell.org/package/hunit-dejafu-0.2.0.0
- **Date** 2015-12-01
- **Git tag** [0.2.0.0][]
- **Hackage** https://hackage.haskell.org/package/hunit-dejafu-0.2.0.0
Initial release. Go read the API docs.

View File

@ -7,10 +7,12 @@ This project is versioned according to the [Package Versioning Policy](https://p
*de facto* standard Haskell versioning scheme.
0.7.0.0 [2017-08-10] (git tag: [tasty-dejafu-0.7.0.0][])
0.7.0.0
-------
https://hackage.haskell.org/package/tasty-dejafu-0.6.0.0
- **Date** 2017-08-10
- **Git tag** [tasty-dejafu-0.7.0.0][]
- **Hackage** https://hackage.haskell.org/package/tasty-dejafu-0.6.0.0
### Test.Tasty.DejaFu
@ -28,10 +30,12 @@ https://hackage.haskell.org/package/tasty-dejafu-0.6.0.0
---------------------------------------------------------------------------------------------------
0.6.0.0 [2017-04-08] (git tag: [tasty-dejafu-0.6.0.0][])
0.6.0.0
-------
https://hackage.haskell.org/package/tasty-dejafu-0.6.0.0
- **Date** 2017-04-08
- **Git tag** [tasty-dejafu-0.6.0.0][]
- **Hackage** https://hackage.haskell.org/package/tasty-dejafu-0.6.0.0
### Test.Tasty.DejaFu
@ -55,10 +59,12 @@ https://hackage.haskell.org/package/tasty-dejafu-0.6.0.0
---------------------------------------------------------------------------------------------------
0.5.0.0 [2017-04-08] (git tag: [tasty-dejafu-0.5.0.0][])
0.5.0.0
-------
https://hackage.haskell.org/package/tasty-dejafu-0.5.0.0
- **Date** 2017-04-08
- **Git tag** [tasty-dejafu-0.5.0.0][]
- **Hackage** https://hackage.haskell.org/package/tasty-dejafu-0.5.0.0
### Test.Tasty.DejaFu
@ -76,10 +82,12 @@ https://hackage.haskell.org/package/tasty-dejafu-0.5.0.0
---------------------------------------------------------------------------------------------------
0.4.0.0 [2017-02-21] (git tag: [tasty-dejafu-0.4.0.0][])
0.4.0.0
-------
https://hackage.haskell.org/package/tasty-dejafu-0.4.0.0
- **Date** 2017-02-21
- **Git tag** [tasty-dejafu-0.4.0.0][]
- **Hackage** https://hackage.haskell.org/package/tasty-dejafu-0.4.0.0
### Test.Tasty.DejaFu
@ -101,10 +109,12 @@ https://hackage.haskell.org/package/tasty-dejafu-0.4.0.0
---------------------------------------------------------------------------------------------------
0.3.0.2 [2016-09-10] (git tag: [tasty-dejafu-0.3.0.2][])
0.3.0.2
-------
https://hackage.haskell.org/package/tasty-dejafu-0.3.0.2
- **Date** 2016-09-10
- **Git tag** [tasty-dejafu-0.3.0.2][]
- **Hackage** https://hackage.haskell.org/package/tasty-dejafu-0.3.0.2
### Miscellaneous
@ -116,10 +126,12 @@ https://hackage.haskell.org/package/tasty-dejafu-0.3.0.2
---------------------------------------------------------------------------------------------------
0.3.0.1 [2016-05-26] (git tag: [tasty-dejafu-0.3.0.1][])
0.3.0.1
-------
https://hackage.haskell.org/package/tasty-dejafu-0.3.0.1
- **Date** 2016-05-26
- **Git tag** [tasty-dejafu-0.3.0.1][]
- **Hackage** https://hackage.haskell.org/package/tasty-dejafu-0.3.0.1
### Miscellaneous
@ -131,10 +143,12 @@ https://hackage.haskell.org/package/tasty-dejafu-0.3.0.1
---------------------------------------------------------------------------------------------------
0.3.0.0 [2016-04-28] (git tag: [tasty-dejafu-0.3.0.0][])
0.3.0.0
-------
https://hackage.haskell.org/package/tasty-dejafu-0.3.0.0
- **Date** 2016-04-28
- **Git tag** [tasty-dejafu-0.3.0.0][]
- **Hackage** https://hackage.haskell.org/package/tasty-dejafu-0.3.0.0
### Test.Tasty.DejaFu
@ -157,10 +171,12 @@ https://hackage.haskell.org/package/tasty-dejafu-0.3.0.0
---------------------------------------------------------------------------------------------------
0.1.1.0 [2016-04-03] (git tag: [tasty-dejafu-0.1.1.0][])
0.1.1.0
-------
**This version was never pushed to hackage, whoops!**
- **Date** 2016-04-03
- **Git tag** [tasty-dejafu-0.1.1.0][]
- **This version was never pushed to hackage, whoops!**
**This version was misnumbered! It should have been 0.2.1.0!**
@ -174,10 +190,12 @@ https://hackage.haskell.org/package/tasty-dejafu-0.3.0.0
---------------------------------------------------------------------------------------------------
0.2.0.0 [2015-12-01] (git tag: [0.2.0.0][])
0.2.0.0
-------
https://hackage.haskell.org/package/tasty-dejafu-0.2.0.0
- **Date** 2015-12-01
- **Git tag** [0.2.0.0][]
- **Hackage** https://hackage.haskell.org/package/tasty-dejafu-0.2.0.0
Initial release. Go read the API docs.