Hy macros for Hypothesis framework
==================================

Archimedes is a set of Hy macros that are used to make writing tests with
Hypothesis easier. ``fact`` specifies a test case, ``variants`` and
``sample`` specify rules for test data generation, ``profile`` specifies
test settings, ``background`` and ``with-background`` specify common data
between tests. ``assert-macro-error`` checks that ``macro-error`` is
called with given message during macro expansion. For regular errors and
exceptions, use ``assert-error``.

In case you're using PyHamcrest, there's two macros to help you define
matchers: ``defmatcher`` and ``attribute-matcher``. And for Hymn, there
is ``assert-right``.

For interactive mode, it's sometimes easier to execute ``fact``
immediately. For this case, use ``check``.

Only Hypothesis is installed as a dependency. Hamcrest and Hymn have to
be installed separately.

Examples are good:
------------------

.. code-block:: hy

   (require archimedes)
   
   (import [hypothesis.strategies [integers]]
           [math [pow]])

   (fact "true is always true"
         (assert true))

   (background some-numbers
         [a 3]
         [b 4]
         [c 5])

   (fact "Pythagorean theorem holds in this specific case"
         (with-background some-numbers [a b c]
           (assert (= (+ (pow a 2) (pow b 2)) (pow c 2)))))

   (fact "sum of two positive numbers is larger than either one of them"
         (variants :a (integers :min-size 1)
                   :b (integers :min-size 1))
         (assert (> (+ a b) a))
         (assert (> (+ a b) b)))

   (fact "example can clarify things"
         (variants :a (integers :min-size 0 :max-size 10)
                   :b (integers :min-size 0 :max-size 10))
         (sample :a 0 :b 0)
         (assert (<= 0 (+ a b) 20)))

   (fact "profile controls test settings"
         (variants :a (integers :min-size 0))
         (profile :max-examples 500)
         (assert (<= 0 a)))

   (fact "macro errors can be asserted"
         (assert-macro-error "cond branches need to be a list"
                             (cond (= 1 1) true)))

   (fact "even fact can be asserted for macro errors"
         (assert-macro-error "too many variants forms"
                             (fact "I'm incorrect"
                                   (variants :a (integers))
                                   (variants :a (integers))
                                   (assert (= a a)))))

   (fact "errors can be asserted"
         (assert-error "error"
                       (raise (ValueError "error"))))

   (check "this is executed immediately"
          (assert (= 1 1)))

   (defmacther is-zero? []
               :match? (= value 0)
               :match! "a zero"
               :no-match! (.format "was a value of {0}" item))

   (assert-that 0 (is-zero?))

   (attribute-matcher item-with-length?
                      len =
                      "an item with length {0}"
                      "was an item with length {0}")

   (assert-that "foo" (is- (item-with-length? 3)))


Syntax:
-------

.. code-block::

           <background> ::= "(" "background" <symbol> <symbol-value>* ")"
         <symbol-value> ::= "[" <symbol> <sexp> "]"
                 <fact> ::= "(" "fact" <string>
                                [<variants> [<sample>] [<profile>]]
                                [<with-background>]
                                <sexp>* ")"
                <check> ::= "(" "fact" <string>
                                [<variants> [<sample>] [<profile>]]
                                [<with-background>]
                                <sexp>* ")"
             <variants> ::= "(" "variants" <variant-spec>* ")"
               <sample> ::= "(" "sample" <keyword-sexp>* ")"
              <profile> ::= "(" "profile" <keyword-sexp>* ")"
         <variant-spec> ::= <keyword> <strategy>
         <keyword-sexp> ::= <keyword> <sexp>
      <with-background> ::= "(" "with-background" <symbol> "[" <symbol>* "]" 
                             <sexp>* ")"
   <assert-macro-error> ::= "(" "assert-macro-error" <string> <sexp> ")"
         <assert-error> ::= "(" "assert-error" <string> <sexp> ")"
         <assert-right> ::= "(" "assert-right" <sexp> <sexp> ")"
          <def-matcher> ::= "(" "def-matcher" <symbol> "[" <symbol>* "]"
                                <keyword> <sexp>* ")"
    <attribute-matcher> ::= "(" "attribute-matcher" <symbol> 
                                <symbol> <symbol>
                                <string> <string> ")"

Details are needed sometimes:
-----------------------------

``(background name elements)`` defines setup function. Name is symbol. Name
of the test function will be ``"setup_" + name``. ``elements`` is one or more
two element lists with first element as symbol and second as value of it.
The setup function will return a dictionary with keywordified symbols as keys
and corresponding values as their values.

``(fact description code)`` specifies a test function. ``description`` is a
string describing what the test is about. The generated function will have a
name ``"test_" + description`` and no arguments. Docstring of the function
will be value of ``description``. ``code`` can be one or more forms of code,
they are inserted inside of the test function as is.

``(check description code)`` works just like ``fact``, except that the
resulting test function is immediately executed. This is useful when working
in interactive envinroment, like Jupyter or Hy repl.

``(with-background name symbols code)`` generates a let binding with code to call
background specified by ``name``. ``symbols`` is list of symbols that should
be retrieved from dictionary returned by setup function and bound to local
context. ``code`` is one or more elements of code, used to test things.

``(variants keyword specification)`` is used to specify test data that should
be generated by Hypothesis. It accepts arbitrary, but even, amount of
parameters. First specifies keywordified symbol and second strategy used to
generate value. If this form is present, test function's parameter list is
modified to have named parameters specified by keywords and is also wrapped
in ``given`` decorator.

``(sample keyword value)`` specifies sample set of values. Keyword specifies
symbol and value holds the value bound to it. It should have same amount of
keywords as ``variants`` form and can't be used without ``variants`` form.

``(profile keyword value)`` specifies test settings. They match directly to
parameters given to ``settings`` decorator.

``(assert-macro-error message code)`` asserts that during macro expansion of
``code`` an error is raised with a message of ``message``.

``(assert-error message code)`` asserts that code raises an error, which
string representation is equal to message.

``(def-matcher name parameters :match? code :match! string :no-match string)``
is used to create matcher function for hamcrest library. The resulting
matcher can then be used in assertions. Since the macro creates a behind the
scenes class, all parameters passed to it are accessible as instance
attributes. In ``match?``, ``match!`` and ``no-match!`` blocks, symbol
``item`` is bound to item currently under comparison.

.. code-block::

   (defmatcher length-of? [value]
               :match? (= (len item) self.value)
               :match! (.format "an item with length of {0} 
                                self.value)
               :no-match (.format "was an item with length of {0}"
                                  (len item)))

   (assert-that value (is- (lenght-of? 5)))

``(attribute-matcher name function predicate string string)`` is a special
case for matcher, where function is used to check a value of some matched
item and then compared to given value using predicate. Thus, the previous
example can be written as:

.. code-block::

   (attribute-matcher length-of?
                      len =
                      "an item with length of {0}"
                      "was an item with length of {0}")

   (assert-that value (is- (length-of? 5)))

``assert-right`` is used with Hymn library's ``Either`` monad. It first
checks that ``right`` was returned as a result of computation and then
proceeds to run assertion block:

.. code-block::

   (assert-right (do-monad [status (advance-time-m society)]
                            status)
                 (assert-that society
                              (has-less-resources-than? old-resources)))


Note about test framework:
--------------------------

Archimedes is geared towards Nose, but it might work with other frameworks
that rely on naming conventions to discover tests to be executed.

License:
--------

Licensed under MIT license
