mirror of
https://github.com/barrucadu/dejafu.git
synced 2024-12-18 19:11:37 +03:00
Merge branch 'docs'
This commit is contained in:
commit
91fa969fc2
@ -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**,
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
1
doc/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
_build
|
20
doc/Makefile
Normal file
20
doc/Makefile
Normal 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
207
doc/advanced.rst
Normal 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)
|
1
doc/changelog_concurrency.md
Symbolic link
1
doc/changelog_concurrency.md
Symbolic link
@ -0,0 +1 @@
|
||||
../concurrency/CHANGELOG.markdown
|
1
doc/changelog_dejafu.md
Symbolic link
1
doc/changelog_dejafu.md
Symbolic link
@ -0,0 +1 @@
|
||||
../dejafu/CHANGELOG.markdown
|
1
doc/changelog_hunit-dejafu.md
Symbolic link
1
doc/changelog_hunit-dejafu.md
Symbolic link
@ -0,0 +1 @@
|
||||
../hunit-dejafu/CHANGELOG.markdown
|
1
doc/changelog_tasty-dejafu.md
Symbolic link
1
doc/changelog_tasty-dejafu.md
Symbolic link
@ -0,0 +1 @@
|
||||
../tasty-dejafu/CHANGELOG.markdown
|
160
doc/conf.py
Normal file
160
doc/conf.py
Normal 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
1
doc/contributing.md
Symbolic link
@ -0,0 +1 @@
|
||||
../CONTRIBUTING.markdown
|
181
doc/getting_started.rst
Normal file
181
doc/getting_started.rst
Normal 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
33
doc/index.rst
Normal 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
159
doc/refinement_testing.rst
Normal 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
74
doc/typeclass.rst
Normal 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
170
doc/unit_testing.rst
Normal 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``.
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user