From ec68d34e146f82faab30ef765c242a3fdc62e160 Mon Sep 17 00:00:00 2001 From: Michael Walker Date: Tue, 15 Aug 2017 19:58:57 +0100 Subject: [PATCH 1/9] Initial commit for sphinx docs --- doc/.gitignore | 1 + doc/Makefile | 20 +++++ doc/changelog_concurrency.md | 1 + doc/changelog_dejafu.md | 1 + doc/changelog_hunit-dejafu.md | 1 + doc/changelog_tasty-dejafu.md | 1 + doc/conf.py | 160 ++++++++++++++++++++++++++++++++++ doc/contributing.md | 1 + doc/index.rst | 24 +++++ 9 files changed, 210 insertions(+) create mode 100644 doc/.gitignore create mode 100644 doc/Makefile create mode 120000 doc/changelog_concurrency.md create mode 120000 doc/changelog_dejafu.md create mode 120000 doc/changelog_hunit-dejafu.md create mode 120000 doc/changelog_tasty-dejafu.md create mode 100644 doc/conf.py create mode 120000 doc/contributing.md create mode 100644 doc/index.rst diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..e35d885 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1 @@ +_build diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..b69e402 --- /dev/null +++ b/doc/Makefile @@ -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) \ No newline at end of file diff --git a/doc/changelog_concurrency.md b/doc/changelog_concurrency.md new file mode 120000 index 0000000..a787ea1 --- /dev/null +++ b/doc/changelog_concurrency.md @@ -0,0 +1 @@ +../concurrency/CHANGELOG.markdown \ No newline at end of file diff --git a/doc/changelog_dejafu.md b/doc/changelog_dejafu.md new file mode 120000 index 0000000..ee9c0d7 --- /dev/null +++ b/doc/changelog_dejafu.md @@ -0,0 +1 @@ +../dejafu/CHANGELOG.markdown \ No newline at end of file diff --git a/doc/changelog_hunit-dejafu.md b/doc/changelog_hunit-dejafu.md new file mode 120000 index 0000000..6993057 --- /dev/null +++ b/doc/changelog_hunit-dejafu.md @@ -0,0 +1 @@ +../hunit-dejafu/CHANGELOG.markdown \ No newline at end of file diff --git a/doc/changelog_tasty-dejafu.md b/doc/changelog_tasty-dejafu.md new file mode 120000 index 0000000..da0032f --- /dev/null +++ b/doc/changelog_tasty-dejafu.md @@ -0,0 +1 @@ +../tasty-dejafu/CHANGELOG.markdown \ No newline at end of file diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..0a8d736 --- /dev/null +++ b/doc/conf.py @@ -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'), +] + + + diff --git a/doc/contributing.md b/doc/contributing.md new file mode 120000 index 0000000..b2e3b4d --- /dev/null +++ b/doc/contributing.md @@ -0,0 +1 @@ +../CONTRIBUTING.markdown \ No newline at end of file diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..ff09d7e --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,24 @@ +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: Release Notes + + concurrency + dejafu + hunit-dejafu + tasty-dejafu + +.. toctree:: + :maxdepth: 2 + :caption: Developer Documentation + + contributing From 2b922089477d4aa49b5de0baad494e281f158d4d Mon Sep 17 00:00:00 2001 From: Michael Walker Date: Tue, 15 Aug 2017 21:24:13 +0100 Subject: [PATCH 2/9] Sections and headings --- doc/advanced.rst | 11 +++++++++++ doc/getting_started.rst | 11 +++++++++++ doc/index.rst | 9 +++++++++ doc/refinement_testing.rst | 5 +++++ doc/typeclass.rst | 11 +++++++++++ doc/unit_testing.rst | 5 +++++ 6 files changed, 52 insertions(+) create mode 100644 doc/advanced.rst create mode 100644 doc/getting_started.rst create mode 100644 doc/refinement_testing.rst create mode 100644 doc/typeclass.rst create mode 100644 doc/unit_testing.rst diff --git a/doc/advanced.rst b/doc/advanced.rst new file mode 100644 index 0000000..314910b --- /dev/null +++ b/doc/advanced.rst @@ -0,0 +1,11 @@ +Advanced Usage +============== + +The Test.DejaFu.Conc module +--------------------------- + +The Test.DejaFu.SCT module +-------------------------- + +Performance tuning +------------------ diff --git a/doc/getting_started.rst b/doc/getting_started.rst new file mode 100644 index 0000000..82939f8 --- /dev/null +++ b/doc/getting_started.rst @@ -0,0 +1,11 @@ +Getting Started +=============== + +Installation +------------ + +Your first test +--------------- + +HUnit and Tasty integration +--------------------------- diff --git a/doc/index.rst b/doc/index.rst index ff09d7e..83643a1 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -7,6 +7,15 @@ This is Déjà Fu **Terry Pratchett, Thief of Time** +.. toctree:: + :maxdepth: 2 + :caption: User Documentation + + getting_started + typeclass + unit_testing + refinement_testing + advanced .. toctree:: :maxdepth: 2 diff --git a/doc/refinement_testing.rst b/doc/refinement_testing.rst new file mode 100644 index 0000000..2958261 --- /dev/null +++ b/doc/refinement_testing.rst @@ -0,0 +1,5 @@ +Refinement Testing +================== + +Using HUnit and Tasty +--------------------- diff --git a/doc/typeclass.rst b/doc/typeclass.rst new file mode 100644 index 0000000..0507348 --- /dev/null +++ b/doc/typeclass.rst @@ -0,0 +1,11 @@ +A Typeclass for Concurrency +=========================== + +The MonadConc typeclass +----------------------- + +The MonadSTM typeclass +---------------------- + +Porting +------- diff --git a/doc/unit_testing.rst b/doc/unit_testing.rst new file mode 100644 index 0000000..8d35ca9 --- /dev/null +++ b/doc/unit_testing.rst @@ -0,0 +1,5 @@ +Unit Testing +============ + +Using HUnit and Tasty +--------------------- From c06c32e7903100a9e0eaac519e18af8eb559ee0c Mon Sep 17 00:00:00 2001 From: Michael Walker Date: Tue, 15 Aug 2017 22:41:09 +0100 Subject: [PATCH 3/9] Getting Started --- doc/getting_started.rst | 178 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 4 deletions(-) diff --git a/doc/getting_started.rst b/doc/getting_started.rst index 82939f8..1174159 100644 --- a/doc/getting_started.rst +++ b/doc/getting_started.rst @@ -1,11 +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 ------------ -Your first test ---------------- +Install from Hackage globally: -HUnit and Tasty integration ---------------------------- +.. 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) From 581ab8339aba4a13484694157cd852bfa889115e Mon Sep 17 00:00:00 2001 From: Michael Walker Date: Thu, 17 Aug 2017 17:18:43 +0100 Subject: [PATCH 4/9] Typeclasses --- doc/typeclass.rst | 79 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 8 deletions(-) diff --git a/doc/typeclass.rst b/doc/typeclass.rst index 0507348..f4ba5b9 100644 --- a/doc/typeclass.rst +++ b/doc/typeclass.rst @@ -1,11 +1,74 @@ -A Typeclass for Concurrency -=========================== +Typeclasses +=========== -The MonadConc typeclass ------------------------ +We don't use the regular ``Control.Concurrent`` and +``Control.Exception`` modules, we use typeclass-generalised ones +instead from the `concurrency`_ and `exceptions`_ packages. -The MonadSTM typeclass ----------------------- +.. _concurrency: https://hackage.haskell.org/package/concurrency +.. _exceptions: https://hackage.haskell.org/package/exceptions -Porting -------- + +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. From d53b689c5bccea53ddae8a5b073e58b93ba6b588 Mon Sep 17 00:00:00 2001 From: Michael Walker Date: Thu, 17 Aug 2017 22:19:35 +0100 Subject: [PATCH 5/9] Unit Testing --- doc/advanced.rst | 7 ++ doc/unit_testing.rst | 165 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) diff --git a/doc/advanced.rst b/doc/advanced.rst index 314910b..ece3cf7 100644 --- a/doc/advanced.rst +++ b/doc/advanced.rst @@ -7,5 +7,12 @@ The Test.DejaFu.Conc module The Test.DejaFu.SCT module -------------------------- +.. _settings: + +Execution settings +------------------ + +.. _performance: + Performance tuning ------------------ diff --git a/doc/unit_testing.rst b/doc/unit_testing.rst index 8d35ca9..97af398 100644 --- a/doc/unit_testing.rst +++ b/doc/unit_testing.rst @@ -1,5 +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``. From a9a6689334cb2a446411a796321cd423aee55882 Mon Sep 17 00:00:00 2001 From: Michael Walker Date: Sat, 19 Aug 2017 12:22:51 +0100 Subject: [PATCH 6/9] Refinement Testing --- doc/refinement_testing.rst | 154 +++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/doc/refinement_testing.rst b/doc/refinement_testing.rst index 2958261..8ff21fc 100644 --- a/doc/refinement_testing.rst +++ b/doc/refinement_testing.rst @@ -1,5 +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 From ce7b0fc9ec6ed53231d4218a0c0819652880874c Mon Sep 17 00:00:00 2001 From: Michael Walker Date: Sat, 19 Aug 2017 16:18:16 +0100 Subject: [PATCH 7/9] Advanced Usage --- doc/advanced.rst | 197 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 4 deletions(-) diff --git a/doc/advanced.rst b/doc/advanced.rst index ece3cf7..59d4f83 100644 --- a/doc/advanced.rst +++ b/doc/advanced.rst @@ -1,18 +1,207 @@ Advanced Usage ============== -The Test.DejaFu.Conc module ---------------------------- +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. -The Test.DejaFu.SCT module --------------------------- .. _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) From 1ac2f42540b524034b63b8d89bcc2f4c9bb19520 Mon Sep 17 00:00:00 2001 From: Michael Walker Date: Sat, 19 Aug 2017 16:28:41 +0100 Subject: [PATCH 8/9] Change use of titlecase in CONTRIBUTING file --- CONTRIBUTING.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.markdown b/CONTRIBUTING.markdown index 0e0068d..683d85f 100644 --- a/CONTRIBUTING.markdown +++ b/CONTRIBUTING.markdown @@ -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**, From b0f6aa5c7e27879e51d1c29dae62f217a9d74e7d Mon Sep 17 00:00:00 2001 From: Michael Walker Date: Sat, 19 Aug 2017 16:38:23 +0100 Subject: [PATCH 9/9] Adjust changelog format --- concurrency/CHANGELOG.markdown | 30 +++++--- dejafu/CHANGELOG.markdown | 126 +++++++++++++++++++++----------- hunit-dejafu/CHANGELOG.markdown | 66 +++++++++++------ tasty-dejafu/CHANGELOG.markdown | 54 +++++++++----- 4 files changed, 184 insertions(+), 92 deletions(-) diff --git a/concurrency/CHANGELOG.markdown b/concurrency/CHANGELOG.markdown index 5b6e06f..4236ce4 100644 --- a/concurrency/CHANGELOG.markdown +++ b/concurrency/CHANGELOG.markdown @@ -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. diff --git a/dejafu/CHANGELOG.markdown b/dejafu/CHANGELOG.markdown index 972797b..08b0b29 100644 --- a/dejafu/CHANGELOG.markdown +++ b/dejafu/CHANGELOG.markdown @@ -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. diff --git a/hunit-dejafu/CHANGELOG.markdown b/hunit-dejafu/CHANGELOG.markdown index 7ce1eb4..5dab44e 100644 --- a/hunit-dejafu/CHANGELOG.markdown +++ b/hunit-dejafu/CHANGELOG.markdown @@ -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. diff --git a/tasty-dejafu/CHANGELOG.markdown b/tasty-dejafu/CHANGELOG.markdown index 31a16d8..ecc9dcc 100644 --- a/tasty-dejafu/CHANGELOG.markdown +++ b/tasty-dejafu/CHANGELOG.markdown @@ -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.