From d1f16b34c72827924473472778fa075669a8e376 Mon Sep 17 00:00:00 2001
From: eirini
Date: Fri, 3 Jun 2011 16:08:05 +0300
Subject: [PATCH] move documentation from README to PropEr modules
---
README | 34 ++---
src/proper.erl | 283 ++++++++++++++++++++++++++++++++++++++
src/proper_gen.erl | 6 +-
src/proper_symb.erl | 64 +++++++++
src/proper_types.erl | 112 ++++++++++++++-
src/proper_typeserver.erl | 128 ++++++++++++++++-
6 files changed, 603 insertions(+), 24 deletions(-)
diff --git a/README b/README
index 4107795e..7bb44156 100644
--- a/README
+++ b/README
@@ -68,7 +68,7 @@ Using PropEr under Erlang/OTP R13B03 or older:
--------------------------------------------------------------------------------
-Introduction
+Introduction -> moved the last paragraph to proper.erl
--------------------------------------------------------------------------------
Traditional testing methodologies essentially involve software testers writing a
@@ -107,7 +107,7 @@ way to provide tighter integration with Erlang's built-in type system.
--------------------------------------------------------------------------------
-How to write properties (test case generators)
+How to write properties (test case generators) -> moved to proper.erl
--------------------------------------------------------------------------------
The simplest properties that PropEr can test consist of a single boolean
@@ -220,7 +220,7 @@ testing modules in the tests directory may also be of interest.
--------------------------------------------------------------------------------
-Program behaviour
+Program behaviour -> moved to proper.erl
--------------------------------------------------------------------------------
When running in verbose mode (this is the default), each sucessful test prints
@@ -259,7 +259,7 @@ controls the return value format of proper:module itself (see the
--------------------------------------------------------------------------------
-Counterexamples
+Counterexamples -> moved to proper.erl
--------------------------------------------------------------------------------
A counterexample for a property is represented as a list of terms; each such
@@ -305,7 +305,7 @@ to verify that actually corresponds to .
--------------------------------------------------------------------------------
-Options
+Options -> moved to proper.erl
--------------------------------------------------------------------------------
Options can be provided as an extra argument to testing functions exported from
@@ -363,7 +363,7 @@ The available options are:
--------------------------------------------------------------------------------
-Basic types
+Basic types -> moved to proper_types.erl
--------------------------------------------------------------------------------
The following basic types are provided:
@@ -495,7 +495,7 @@ example of the type of a tagged tuple: {'result', integer()}.
--------------------------------------------------------------------------------
-How to construct types
+How to construct types -> moved to proper_types.erl
--------------------------------------------------------------------------------
The following macros and functions can be applied to types in order to produce
@@ -602,7 +602,7 @@ resize(, )
--------------------------------------------------------------------------------
-Native types support
+Native types support -> moved to proper_typeserver.erl
--------------------------------------------------------------------------------
PropEr can parse types expressed in Erlang's native type format and convert them
@@ -711,7 +711,7 @@ The use of native types in properties is subject to the following usage rules:
--------------------------------------------------------------------------------
-Spec testing
+Spec testing -> moved to proper.erl
--------------------------------------------------------------------------------
You can test the accuracy of an exported function's spec by running:
@@ -762,7 +762,7 @@ treats these the same way as the non-parametric versions.
--------------------------------------------------------------------------------
-Symbolic datatypes
+Symbolic datatypes -> moved to proper_symb.erl
--------------------------------------------------------------------------------
When writing properties that involve abstract data types, such as dicts or sets,
@@ -814,7 +814,7 @@ instances:
--------------------------------------------------------------------------------
-Auto-ADT
+Auto-ADT -> move to proper_symb.erl
--------------------------------------------------------------------------------
To simplify the symbolic testing of ADTs, PropEr comes with the Auto-ADT
@@ -877,7 +877,7 @@ proper/proper_dict module, which wraps the STDLIB dict ADT.
--------------------------------------------------------------------------------
-Testing stateful code
+Testing stateful code -> moved to proper_statem.erl
--------------------------------------------------------------------------------
PropEr provides the module proper_statem to facilitate testing of stateful
@@ -995,7 +995,7 @@ respectively.
--------------------------------------------------------------------------------
-Parallel testing
+Parallel testing -> moved to proper_statem.erl
--------------------------------------------------------------------------------
After ensuring that a system's behaviour can be described via an abstract state
@@ -1042,7 +1042,7 @@ examples/ets_statem, where we test concurrent operations on a public ets table.
--------------------------------------------------------------------------------
-PropEr fsm
+PropEr fsm -> moved to proper_fsm.erl
--------------------------------------------------------------------------------
PropEr provides another useful module for testing stateful systems, in
@@ -1155,7 +1155,7 @@ containing a finite state machine specification for an elevator.
--------------------------------------------------------------------------------
-Errors
+Errors -> moved to proper.erl
--------------------------------------------------------------------------------
The following errors may be encountered during testing. The term provided for
@@ -1194,7 +1194,7 @@ occurs. Normaly, a message is also printed on screen describing the error.
--------------------------------------------------------------------------------
-Demo functions
+Demo functions -> moved to proper_gen.erl and proper_typeserver.erl
--------------------------------------------------------------------------------
You can use the following functions to try out the random instance generation,
@@ -1230,7 +1230,7 @@ for demonstration purposes only.
--------------------------------------------------------------------------------
-Incompatibilities with QuviQ's QuickCheck
+Incompatibilities with QuviQ's QuickCheck -> moved to proper.erl
--------------------------------------------------------------------------------
We have generaly tried to keep PropEr's notation and output as compatible as
diff --git a/src/proper.erl b/src/proper.erl
index c3a79641..00f21cb5 100644
--- a/src/proper.erl
+++ b/src/proper.erl
@@ -22,7 +22,16 @@
%%% and Kostis Sagonas
%%% @version {@version}
%%% @author Manolis Papadakis
+
%%% @doc This is the main PropEr module.
+%%% PropEr is a property-based testing tool, designed to test programs written
+%%% in the Erlang programming language. Its focus is on testing the behaviour
+%%% of pure functions. On top of that, it is equipped with two library modules
+%%% that can be used for testing stateful code. The input domain of functions is
+%%% specified through the use of a type system, modeled closely after the type
+%%% system of the language itself. Properties are written using Erlang
+%%% expressions, with the help of a few predefined macros. Work is currently under
+%%% way to provide tighter integration with Erlang's built-in type system.
%%%
%%% == How to write properties (test case generators) ==
%%% The simplest properties that PropEr can test consist of a single boolean
@@ -77,6 +86,280 @@
%%% any more wrappers.
%%%
%%%
+%%% Additionaly, there is a wrapper that allows to test a conjunction of
+%%% sub-properties, see {@link conjunction/1} for more details. There are also
+%%% multiple wrappers that can be used to collect statistics about test case
+%%% distribution. See {@link collect/2}, {@link collect/3}, {@link aggregate/2},
+%%% {@link aggregate/3}, {@link classify/3}, {@link measure/3} for more details.
+%%%
+%%% Last but not least, a property may be wrapped with one or more of the
+%%% following outer-level wrappers, which control the behaviour of the testing
+%%% subsystem: {@link numtests/2}, {@link fails/2}, {@link on_output/2}.
+%%% If the same wrapper appears more than once in a property, the innermost
+%%% takes precedence.
+%%%
+%%% Note that failure of a property may also be signified by throwing an
+%%% exception, error or exit.
+%%%
+%%% For some actual usage examples, see the code in the examples directory. The
+%%% testing modules in the tests directory may also be of interest.
+%%%
+%%% == Program behaviour ==
+%%% When running in verbose mode (this is the default), each sucessful test
+%%% prints a '.' on screen. If a test fails, a '!' is printed, along with the
+%%% failing test case (the instances of the types in every `?FORALL') and the
+%%% cause of the failure, if it was not simply the falsification of the property.
+%%% Then, unless the test was expected to fail, PropEr attempts to produce a
+%%% minimal test case that fails the property in the same way. This process is
+%%% called shrinking. During shrinking, a '.' is printed for each
+%%% successful simplification of the failing test case. When PropEr reaches its
+%%% shrinking limit or realizes that the instance cannot be shrunk further while
+%%% still failing the test, it prints the minimal failing test case and failure
+%%% reason and exits.
+%%%
+%%% The return value of PropEr can be one of the following:
+%%%
+%%%
`true': The property held for all valid produced inputs.
+%%%
`false': The property failed for some input.
+%%%
`{error, }': An error occured - see the {@section Errors}
+%%% section for a description of possible errors.
+%%%
+%%%
+%%% You can also run PropEr in pure mode by using {@link proper:pure_check/1} or
+%%% {@link proper:pure_check/2} instead of {@link proper:quickcheck/1} and
+%%% {@link proper:quickcheck/2}. Under this mode, PropEr will perform no I/O and
+%%% will not access the caller's process dictionary in any way. Please note that
+%%% PropEr will not actually run as a pure function under this mode.
+%%%
+%%% To test all properties exported from a module (a property is a 0-arity function
+%%% whose name begins with "prop_"), you can use {@link proper:module/1} or
+%%% {@link proper:module/2}. This returns a list of all failing properties,
+%%% represented by mfas. Testing progress is also printed on screen (unless 'quiet'
+%%% mode is active). The provided options are passed on to each property, except for
+%%% 'long_result', which controls the return value format of `proper:module' itself
+%%% (see the {@section Counterexamples} section).
+%%%
+%%% == Counterexamples ==
+%%% A counterexample for a property is represented as a list of terms; each such
+%%% term corresponds to the type in a `?FORALL'. The instances are provided in
+%%% the same order as the `?FORALL' wrappers in the property, i.e. the instance
+%%% at the head of the list corresponds to the outermost `?FORALL' etc. Instances
+%%% generated inside a failing sub-property of a conjunction are marked with the
+%%% sub-property's tag.
+%%%
+%%% The last (simplest) counterexample produced by PropEr during a (failing) run
+%%% can be retrieved after testing has finished, by running
+%%% {@link proper:counterexample/0}. When testing a whole module, run
+%%% {@link proper:counterexamples/0} to get a counterexample for each failing
+%%% property, as a list of {`mfa()', {@type counterexample()}} tuples. To enable
+%%% this functionality, some information has to remain in the process dictionary
+%%% even after PropEr has returned. To completely clean up the process dictionary
+%%% of PropEr-produced entries, run {@link proper:clean_garbage/0}.
+%%%
+%%% Counterexamples can also be retrieved by running PropEr in long-result mode,
+%%% where counterexamples are returned as part of the return value. Specifically,
+%%% when testing a single property under long-result mode (activated by supplying
+%%% the option 'long_result', or by calling {@link proper:counterexample/1},
+%%% {@link proper:counterexample/2} instead of {@link proper:quickcheck/1},
+%%% {@link proper:quickcheck/2}). PropEr will return a counterexample in case of
+%%% failure (instead of simply returning `false'). When testing a whole module
+%%% under long-result mode (activated by supplying the option `long_result' to
+%%% {@link proper:module/2}), PropEr will return a list of
+%%% {`mfa()', {@type counterexample()}} tuples, one for each failing property.
+%%%
+%%% You can re-check a specific counterexample against the property that it
+%%% previously falsified by running {@link proper:check/2} or
+%%% {@link proper:check/3}. This will return one of the following (both in short-
+%%% and long-result mode):
+%%%
+%%%
`true': The property now holds for this test case.
+%%%
`false': The test case still fails (although not necessarily for the same
+%%% reason as before).
+%%%
`{error, }': An error occured - see the {@section Errors}
+%%% section for a description of possible errors.
+%%%
+%%% Proper will not attempt to shrink the input in case it fails the property.
+%%% Unless silent mode is active, PropEr will also print a message on screen,
+%%% describing the result of the re-checking. Note that PropEr can do very little
+%%% to verify that the counterexample actually corresponds to the property that it
+%%% is tested against.
+%%%
+%%% == Options ==
+%%% Options can be provided as an extra argument to testing functions exported
+%%% from the main proper module (such as {@link proper:quickcheck/1}). A single
+%%% option can be written stand-alone, or multiple options can be provided in a
+%%% list. When two settings conflict, the one that comes first in the list takes
+%%% precedence. Settings given inside external wrappers to a property (see the
+%%% {@section How to write properties (test case generators)} section) override
+%%% any conflicting settings provided as options.
+%%%
+%%% The available options are:
+%%%
+%%%
`quiet'
+%%%
Enables quiet mode - no output is printed on screen while PropEr is
+%%% running.
+%%%
`verbose'
+%%%
Enables verbose mode - this is the default mode of operation.
+%%%
{`to_file', `'}
+%%%
Redirects all of PropEr's output to `', which should be an IO
+%%% device associated with a file opened for writing.
+%%%
{`on_output', `'}
+%%%
PropEr will use the supplied function for all output printing. This
+%%% function should accept two arguments in the style of `io:format/2'.
+%%% CAUTION: The above output control options are incompatible with each
+%%% other.
+%%%
`long_result'
+%%%
Enables long-result mode (see the {@section Counterexamples} section for
+%%% details).
+%%%
{`numtests', `'} or simply `'
+%%%
This is equivalent to the {@link numtests/1} property wrapper. Any
+%%% `numtests/1' wrappers in the actual property will overwrite this setting.
+%%%
+%%%
{`start_size', `'}
+%%%
Specifies the initial value of the `size' parameter (default is 1), see
+%%% the {@section PropEr types} section for details.
+%%%
{`max_size', `'}
+%%%
Specifies the maximum value of the `size' parameter (default is 42), see
+%%% the {@section PropEr types} section for details.
+%%%
{`max_shrinks', `'}
+%%%
Specifies the maximum number of times a failing test case should be
+%%% shrunk before returning. Note that the shrinking may stop before so many
+%%% shrinks are achieved if the shrinking subsystem deduces that it cannot
+%%% shrink the failing test case further. Default is 500.
+%%%
`noshrink'
+%%%
Instructs PropEr to not attempt to shrink any failing test cases.
+%%%
+%%%
{`constraint_tries', `'}
+%%%
Specifies the maximum number of tries before the generator subsystem
+%%% gives up on producing an instance that satisfies a `?SUCHTHAT' constraint.
+%%% Default is 50.
+%%%
`fails'
+%%%
This is equivalent to the {@link fails/1} property wrapper.
+%%%
{`spec_timeout', `infinity | '}
+%%%
When testing a spec, PropEr will consider an input to be failing if the
+%%% function under test takes more that the specified amount of milliseconds
+%%% to return for that input.
+%%%
`any_to_integer'
+%%%
All generated instances of the type {@link proper_types:any/0} will be
+%%% integers. This is provided as a means to speed up the testing of specs,
+%%% where `proper_types:any/0' is a commonly used type (see the
+%%% {@section Spec testing} section for details).
+%%%
+%%%
+%%% == PropEr types ==
+%%% For information on basic PropEr types and how to construct new types please
+%%% refer to the documentation of the {@link proper_types} module.
+%%%
+%%% == Native types support ==
+%%% For information on native types support please refer to the documentation
+%%% of the {@link proper_typeserver} module.
+%%%
+%%% == Spec testing ==
+%%% You can test the accuracy of an exported function's spec by running
+%%% {@link proper:check_spec/1} or {@link proper:check_spec/2}.
+%%% Under this mode of operation, PropEr will call the provided function with
+%%% increasingly complex valid inputs (according to its spec) and test that no
+%%% unexpected value is returned. If an input is found that violates the spec,
+%%% it will be saved as a counterexample and PropEr will attempt to shrink it.
+%%%
+%%% You can test all exported functions of a module against their spec by
+%%% running {@link proper:check_specs/1} or {@link proper:check_specs/2}.
+%%%
+%%% The use of `check_spec' is subject to the following usage rules:
+%%%
+%%%
Currently, PropEr can't test functions whose range contains a type
+%%% that exhibits a certain kind of self-reference: it is (directly or
+%%% indirectly) self-recursive and at least one recursion path contains only
+%%% unions and type references. E.g. these types are acceptable:
+%%% ``` -type a(T) :: T | {'bar',a(T)}.
+%%% -type b() :: 42 | [c()].
+%%% -type c() :: {'baz',b()}.'''
+%%% while these are not:
+%%% ``` -type a() :: 'foo' | b().
+%%% -type b() :: c() | [integer()].
+%%% -type c() :: 'bar' | a().
+%%% -type d(T) :: T | d({'baz',T}).'''
+%%%
Throwing any exception or raising an `error:badarg' is considered
+%%% normal behaviour. Currently, users cannot fine-tune this setting.
+%%%
Only the first clause of the function's spec is considered.
+%%%
The only spec constraints we accept are 1is_subtype' constraints whose
+%%% first argument is a simple, non-'_' variable. It is not checked whether or
+%%% not these variables actually appear in the spec. The second argument of an
+%%% `is_subtype' constraint cannot contain any non-'_' variables. Multiple
+%%% constraints for the same variable are not supported.
+%%%
+%%%
+%%% PropEr comes with an extra header file,
+%%% `proper/include/proper_param_adts.hrl', which contains definitions of
+%%% parametric wrappers for some STDLIB ADTs, namely `array/1', `dict/2',
+%%% `gb_set/1', `gb_tree/2', `queue/1', `set/1', `orddict/2' and `ordset/1'. By
+%%% including this header file, you can use the above parametric types to better
+%%% document your code and facilitate spec testing (PropEr automatically
+%%% recognizes these types and produces valid instances). Please note that
+%%% Dialyzer currenty treats these the same way as the non-parametric versions.
+%%%
+%%% == Errors ==
+%%% The following errors may be encountered during testing. The term provided
+%%% for each error is the error type returned by proper:quickcheck in case such
+%%% an error occurs. Normaly, a message is also printed on screen describing
+%%% the error.
+%%%
+%%%
`arity_limit'
+%%%
The random instance generation subsystem has failed to produce
+%%% a function of the desired arity. Please recompile PropEr with a suitable
+%%% value for `?MAX_ARITY' (defined in `proper_internal.hrl'). This error
+%%% should only be encountered during normal operation.
+%%%
`cant_generate'
+%%%
The random instance generation subsystem has failed to
+%%% produce an instance that satisfies some `?SUCHTHAT' constraint. You
+%%% should either increase the `constraint_tries' limit, loosen the failing
+%%% constraint, or make it non-strict. This error should only be encountered
+%%% during normal operation.
+%%%
`cant_satisfy'
+%%%
All the tests were rejected because no produced test case
+%%% would pass all `?IMPLIES' checks. You should loosen the failing `?IMPLIES'
+%%% constraint(s). This error should only be encountered during normal
+%%% operation.
+%%%
`non_boolean_result'
+%%%
The property code returned a non-boolean result. Please
+%%% fix your property.
+%%%
`rejected'
+%%%
Only encountered during re-checking, the counterexample does not
+%%% match the property, since the counterexample doesn't pass an `?IMPLIES'
+%%% check.
+%%%
`too_many_instances'
+%%%
Only encountered during re-checking, the counterexample
+%%% does not match the property, since the counterexample contains more
+%%% instances than there are `?FORALLs' in the property.
+%%%
`type_mismatch'
+%%%
The variables' and types' structures inside a `?FORALL' don't
+%%% match. Please check your properties.
+%%%
{`typeserver', `'}
+%%%
The typeserver encountered an error. The `' field contains
+%%% specific information regarding the error.
+%%%
{`unexpected', `'}
+%%%
A test returned an unexpected result during normal operation. If you ever
+%%% get this error, it means that you have found a bug in PropEr - please send an
+%%% error report to the maintainers and remember to include both the failing test
+%%% case and the output of the program, if possible.
+%%%
{`unrecognized_option', `
+%%%
+%%%
+%%% == Incompatibilities with QuviQ's QuickCheck ==
+%%% We have generaly tried to keep PropEr's notation and output as compatible as
+%%% possible with QuviQ's QuickCheck, to allow for the reuse of existing testing
+%%% code written for QuickCheck. However, incompatibilities are to be expected,
+%%% since the two programs probably bear little resemblance under the hood. Here
+%%% we provide a nonexhaustive list of known incompatibilities:
+%%%
+%%%
`?SUCHTHATMAYBE' behaves differently in PropEr.
+%%%
`proper_gen:pick' differs from `eqc_gen:pick' in return value format.
+%%%
PropEr handles `size' differently from QuickCheck.
+%%%
`proper:module/2' accepts options in the second argument instead of the
+%%% first.
+%%%
+%%% @end
-module(proper).
-export([quickcheck/1, quickcheck/2, counterexample/1, counterexample/2,
diff --git a/src/proper_gen.erl b/src/proper_gen.erl
index 250ba9a5..d1d55bc1 100644
--- a/src/proper_gen.erl
+++ b/src/proper_gen.erl
@@ -23,7 +23,11 @@
%%% @version {@version}
%%% @author Manolis Papadakis
%%% @doc The generator subsystem and generators for basic types are contained
-%%% in this module.
+%%% in this module. You can use the these functions
+%%% to try out the random instance generation and shrinking subsystems.
+%%%
+%%% CAUTION: These functions should never be used
+%%% inside properties. They are meant for demonstration purposes only.
-module(proper_gen).
-export([pick/1, pick/2, sample/1, sample/3, sampleshrink/1, sampleshrink/2]).
diff --git a/src/proper_symb.erl b/src/proper_symb.erl
index 04bfb664..25aa7d96 100644
--- a/src/proper_symb.erl
+++ b/src/proper_symb.erl
@@ -60,6 +60,70 @@
%%% When including the "proper/include/proper.hrl" header file,
%%% all API functions of {@module} are automatically
%%% imported, unless `PROPER_NO_IMPORTS' is defined.
+%%%
+%%% == Auto-ADT ==
+%%% To simplify the symbolic testing of ADTs, PropEr comes with the Auto-ADT
+%%% subsystem: An opaque native type, if exported from its module, is assumed
+%%% to be an abstract data type, causing PropEr to ignore its internal
+%%% representation and instead construct symbolic instances of the type. The
+%%% API functions used in these symbolic instances are extracted from the ADT's
+%%% definition module, which is expected to contain one or more '-spec'ed
+%%% exported functions that can be used to construct instances of the ADT.
+%%% Specifically, PropEr will use all functions that return at least one
+%%% instance of the ADT. As with recursive native types, the base case is
+%%% automatically detected (in the case of ADTs, calls to functions like
+%%% `new/0' and `from_list/1' would be considered the base case). The produced
+%%% symbolic calls will be `$call' tuples, which are automatically evaluated,
+%%% thus no call to `eval/1' is required inside the property. Produced instances
+%%% are guaranteed to evaluate successfully. Parametric ADTs are fully supported,
+%%% as long as they appear instantiated inside `?FORALLs'. ADTs hard-coded in the
+%%% Erlang type system (array, dict, digraph, gb_set, gb_tree, queue, and set)
+%%% are automatically detected and handled as such. PropEr also accepts
+%%% parametric versions of the above ADTs in `?FORALLs' (`array/1', `dict/2',
+%%% `gb_set/1', `gb_tree/2', `queue/1', `set/1', also `orddict/2' and
+%%% `ordset/1').
+%%%
+%%% The use of Auto-ADT is currently subject to the following limitations:
+%%%
+%%%
In the ADT's `-opaque' declaration, as in all types' declarations,
+%%% only type variables should be used as parameters in the LHS. None of
+%%% these variables can be the special `_' variable and no variable should
+%%% appear more than once in the parameters.
+%%%
ADTs inside specs can only have simple variables as parameters. These
+%%% variables cannot be bound by any is_subtype constraint. Also, the special
+%%% `_' variable is not allowed in ADT parameters. If this would result in
+%%% singleton variables, as in the specs of functions like `new/0', use
+%%% variable names that begin with an underscore.
+%%%
Specs that introduce an implicit binding among the parameters of an
+%%% ADT are rejected, e.g.:
+%%% ``` -spec foo(mydict(T,S),mydict(S,T)) -> mydict(T,S). '''
+%%% This includes using the same type variable twice in the parameters of
+%%% an ADT.
+%%%
While parsing the return type of specs in search of ADT references,
+%%% PropEr only recurses into tuples, unions and lists - all other constructs
+%%% are ignored. This prohibits, among others, indirect references to the ADT
+%%% through other custom types and records.
+%%%
When encountering a union in the return type, PropEr will pick the
+%%% first choice that can return an ADT. This choice must be distinguishable
+%%% from the others either by having a unique term structure or by having a
+%%% unique tag (if it's a tagged tuple).
+%%%
When parsing multi-clause specs, only the first clause is considered.
+%%%
+%%%
The only spec constraints we accept are `is_subtype' constraints whose
+%%% first argument is a simple, non-`_' variable. It is not checked whether or
+%%% not these variables actually appear in the spec. The second argument of an
+%%% `is_subtype' constraint cannot contain any non-`_' variables. Multiple
+%%% constraints for the same variable are not supported.
+%%%
Unexported opaques and opaques with no suitable specs to serve as API
+%%% calls are silently discarded. Those will be treated like ordinary types.
+%%%
Unexported or unspecced functions are silently rejected.
+%%%
Functions with unsuitable return values are silently rejected.
+%%%
Specs that make bad use of variables are silently rejected.
+%%%
+%%%
+%%% For an example on how to write Auto-ADT-compatible parametric specs, see
+%%% the `examples/stack' module, which contains a simple implementation of a stack,
+%%% or the `proper/proper_dict module', which wraps the STDLIB dict ADT.
%%% @end
-module(proper_symb).
diff --git a/src/proper_types.erl b/src/proper_types.erl
index 75a340c2..e4cce0d5 100644
--- a/src/proper_types.erl
+++ b/src/proper_types.erl
@@ -22,8 +22,113 @@
%%% and Kostis Sagonas
%%% @version {@version}
%%% @author Manolis Papadakis
+
%%% @doc Type manipulation functions and predefined types are contained in this
-%%% module.
+%%% module.
+%%%
+%%% == How to construct types ==
+%%% The following macros can be applied to basic types in order to produce new
+%%% ones:
+%%%
+%%%
`?LET(, , )'
+%%%
To produce an instance of this type, all appearances of the variables
+%%% in `' inside `' are replaced by their corresponding values in a
+%%% randomly generated instance of `'. It's OK for the `' part to
+%%% evaluate to a type - in that case, an instance of the inner type is
+%%% generated recursively.
+%%%
+%%%
`?SUCHTHAT(, , )'
+%%%
This produces a specialization of `', which only includes those
+%%% members of `' that satisfy the constraint `' - that is,
+%%% those members for which the function `fun() -> end' returns
+%%% `true'. If the constraint is very strict - that is, only a small percentage
+%%% of instances of `' pass the test - it will take a lot of tries for
+%%% the instance generation subsystem to randomly produce a valid instance of
+%%% the specialized type. This will result in slower testing - testing may even
+%%% be stopped short, in case the `constraint_tries' limit is reached (see the
+%%% 'Options' section in {@link proper} module documentation). If this is the
+%%% case, it would be more appropriate to generate valid instances of the
+%%% specialized type using the ?LET macro. Also make sure that even small
+%%% instances can satisfy the constraint, since PropEr will only try small
+%%% instances at the start of testing. If this is not possible, you can
+%%% instruct PropEr to start at a larger size (`start_size' option - see the
+%%% 'Options' section in {@link proper} module documentation for details).
+%%%
+%%%
`?SUCHTHATMAYBE(, , )'
+%%%
Equivalent to the `?SUCHTHAT' macro, but the constraint `' is
+%%% considered non-strict: if the `constraint_tries' limit is reached, the
+%%% generator will just return an instance of `' instead of failing,
+%%% even if that instance doesn't satisfy the constraint.
+%%%
+%%%
`?SHRINK(, )'
+%%%
This creates a type whose instances are generated by evaluating the
+%%% statement block `' (this may evaluate to a type, which is
+%%% generated recursively). If an instance of such a type is to be shrunk,
+%%% the generators in `' are first run to
+%%% produce hopefully simpler instances of the type. Thus, the generators in
+%%% the second argument should be simpler than the default. The simplest ones
+%%% should be at the front of the list, since those are the generators preferred
+%%% by the shrinking subsystem. Like the main `', the alternatives may
+%%% also evaluate to a type, which is generated recursively.
+%%%
+%%%
`?LETSHRINK(, , )'
+%%%
This is created by combining a `?LET' and a `?SHRINK' macro. Instances
+%%% are generated by applying a randomly generated list of values inside
+%%% `' (just like a `?LET', with the added constraint that the
+%%% variables and types must be provided in a list - alternatively,
+%%% `' may be a list or vector type). When shrinking instances of
+%%% such a type, the sub-instances that were combined to produce it are first
+%%% tried in place of the failing instance.
+%%%
+%%%
`?LAZY()'
+%%%
This construct returns a type whose only purpose is to delay the
+%%% evaluation of `' (`' can return a type, which will
+%%% be generated recursively). Using this, you can simulate the lazy generation
+%%% of instances:
+%%% ``` stream() -> ?LAZY(frequency([ {1,[]}, {3,[0|stream()]} ])). '''
+%%% The above type produces lists of zeroes with an average length of 3. Note
+%%% that, had we not enclosed the generator with a `?LAZY' macro, the evaluation
+%%% would continue indefinitely, due to the eager evaluation of the Erlang
+%%% language.
+%%%
+%%%
+%%% Additionally, the functions {@link non_empty/1}, {@link noshrink/1},
+%%% {@link with_parameter/3}, {@link with_parameters/2} can be used to add extra
+%%% attributes to a type.
+%%%
+%%% == Size-related issues ==
+%%% The following functions and macros are related to the `size' parameter,
+%%% which controls the maximum size of produced instances. The actual size of
+%%% a produced instance is chosen randomly, but can never exceed the value of
+%%% the `size' parameter at the moment of generation. A more accurate definition
+%%% is the following: the maximum instance of `size S' can never be smaller than
+%%% then maximum instance of `size S-1'. The actual size of an instance is
+%%% measured differently for each type: the actual size of a list is its length,
+%%% while the actual size of a tree may be the number of its internal nodes.
+%%% Some types, e.g. unions, have no notion of size, thus their generation is not
+%%% influenced by the value of `size'. The `size' parameter starts at `0' and
+%%% grows automatically during testing.
+%%%
+%%%
`?SIZED(, )'
+%%%
Creates a new type, whose instances are produced by replacing all
+%%% appearances of the `' parameter inside the statement block `'
+%%% with the value of the `size' parameter. It's OK for the `' to
+%%% return a type - in that case, an instance of the inner type is generated
+%%% recursively.
+%%%
+%%%
`resize(, )'
+%%%
Overrides the `size' parameter used when generating instances of
+%%% `' with `'. Has no effect on size-less types, such as
+%%% unions. Also, this will not affect the generation of any internal types
+%%% contained in `', such as the elements of a list - those will still be
+%%% generated using the test-wide value of `size'. One use of this function is
+%%% to modify types to grow faster or slower, like so:
+%%% ```?SIZED(Size, resize(Size * 2, list(integer()))'''
+%%% The above specifies a list type that grows twice as fast as normal lists.
+%%%
+%%%
+%%%
+%%% @end
-module(proper_types).
-export([is_inst/2, is_inst/3]).
@@ -1047,10 +1152,7 @@ weighted_default(Default, OtherType) ->
%% `RawType' with `NewSize'. It has no effect on size-less types, such as
%% unions. Also, this will not affect the generation of any internal types
%% contained in `RawType', such as the elements of a list - those will still be
-%% generated using the test-wide value of `size'. One use of this function is
-%% to modify types to grow faster or slower, like so:
-%% ```?SIZED(Size, resize(Size * 2, list(integer()))'''
-%% The above specifies a list type that grows twice as fast as normal lists.
+%% generated using the test-wide value of `size'.
-spec resize(size(), raw_type()) -> proper_types:type().
resize(NewSize, RawType) ->
Type = cook_outer(RawType),
diff --git a/src/proper_typeserver.erl b/src/proper_typeserver.erl
index 933dd02d..66bf914c 100644
--- a/src/proper_typeserver.erl
+++ b/src/proper_typeserver.erl
@@ -22,8 +22,134 @@
%%% and Kostis Sagonas
%%% @version {@version}
%%% @author Manolis Papadakis
+
%%% @doc This module contains the subsystem responsible for integration with
-%%% Erlang's built-in type system.
+%%% Erlang's built-in type system. PropEr can parse types expressed in Erlang's
+%%% native type format and convert them to its own type format. Such expressions
+%%% can be used instead of regular type constructors in the second argument of
+%%% `?FORALLs'. No extra notation is required -PropEr will detect which calls
+%%% correspond to native types by applying a parse transform during compilation.
+%%% This parse transform is automatically applied to any module that includes
+%%% the `proper.hrl' header file. You can disable this feature by compiling your
+%%% modules with `-DPROPER_NO_TRANS'. Note that this will currently also disable
+%%% the automatic exporting of properties.
+%%%
+%%% The use of native types in properties is subject to the following usage
+%%% rules:
+%%%
+%%%
Native types cannot be used outside of `?FORALLs'.
+%%%
Inside `?FORALLs', native types can be combined with other native
+%%% types, and even with PropEr types, inside tuples and lists (the constructs
+%%% `[...]', `{...}' and `++' are all allowed).
+%%%
All other constructs of Erlang's built-in type system (e.g. `|' for union,
+%%% `_' as an alias of `any()', `<<_:_>>' binary type syntax or
+%%% `fun((...) -> ...)' function type syntax) are not allowed in `?FORALLs',
+%%% since they are rejected by the Erlang parser.
+%%%
Anything other than a tuple constructor, list constructor, `++' application,
+%%% local or remote call will automatically be considered a PropEr type
+%%% constructor and not be processed further by the parse transform.
+%%%
Parametric native types are fully supported - of course, they can only
+%%% appear instantiated in a `?FORALL'. The arguments of parametric native types
+%%% are always interpreted as native types.
+%%%
Parametric PropEr types, on the other hand, can take any kind of argument.
+%%% You can even mix native and PropEr types in the arguments of a PropEr type.
+%%% For example, assuming that the following declarations are present:
+%%% ``` my_proper_type() -> ?LET(...).
+%%% -type my_native_type() :: ... .'''
+%%% Then the following expressions are all legal:
+%%% ``` vector(2, my_native_type())
+%%% function(0, my_native_type())
+%%% union([my_proper_type(), my_native_type()])'''
+%%%
Some type constructors can take native types as arguments (but only
+%%% inside `?FORALLs'):
+%%%
+%%%
`?SUCHTHAT', `?SUCHTHATMAYBE', `non_empty', `noshrink': these work with
+%%% native types too
+%%%
`?LAZY', `?SHRINK', `resize', `?SIZED': these don't work with native
+%%% types
+%%%
`?LET', `?LETSHRINK': only the top-level base type can be a native type
+%%%
+%%%
+%%%
Native type declarations in the `?FORALLs' of a module can reference any
+%%% custom type declared in a `-type' or `-opaque' attribute of the same module,
+%%% as long as no module identifier is used.
+%%%
Typed records cannot be referenced inside `?FORALLs' using the `#rec_name{}'
+%%% syntax. To use a typed record in a `?FORALL', enclose the record in a custom
+%%% type like so:
+%%% ``` -type rec_name() :: #rec_name{}. '''
+%%% and use the custom type instead.
+%%%
`?FORALLs' may contain references to self-recursive or mutually recursive
+%%% native types, so long as each type in the hierarchy has a clear base case.
+%%% Currently, PropEr requires that the toplevel of any recursive type
+%%% declaration is either a (maybe empty) list or a union containing at least
+%%% one choice that doesn't reference the type (it may, however, reference any
+%%% of the types that are mutually recursive with it). This means, for example,
+%%% that some valid recursive type declarations, such as this one:
+%%% ``` ?FORALL(..., a(), ...) ''' where:
+%%% ``` -type a() :: {'a','none' | a()}. '''
+%%% are not accepted by PropEr. However, such types can be rewritten in a way
+%%% that allows PropEr to parse them:
+%%% ``` ?FORALL(..., a(), ...) ''' where:
+%%% ``` -type a() :: {'a','none'} | {'a',a()}. '''
+%%% This also means that recursive record declarations are not allowed:
+%%% ``` ?FORALL(..., rec(), ...) ''' where:
+%%% ``` -type rec() :: #rec{}.
+%%% -record(rec, {a = 0 :: integer(), b = 'nil' :: 'nil' | #rec{}}). '''
+%%% A little rewritting can usually remedy this problem as well:
+%%% ``` ?FORALL(..., rec(), ...) ''' where:
+%%% ``` -type rec() :: #rec{b :: 'nil'} | #rec{b :: rec()}.
+%%% -record(rec, {a = 0 :: integer(), b = 'nil' :: 'nil' | #rec{}}). '''
+%%%
Remote types may be referenced in a `?FORALL', as long as they are
+%%% exported from the remote module. Currently, PropEr requires that any remote
+%%% modules whose types are directly referenced from within properties are
+%%% present in the code path at compile time, either compiled with `debug_info'
+%%% enabled or in source form. If PropEr cannot find a remote module at all,
+%%% finds only a compiled object file with no debug information or fails to
+%%% compile the source file, all calls to that module will automatically be
+%%% considered calls to PropEr type constructors.
+%%%
For native types to be translated correctly, both the module that contains
+%%% the `?FORALL' declaration as well as any module that contains the declaration
+%%% of a type referenced (directly or indirectly) from inside a `?FORALL' must be
+%%% present in the code path at runtime, either compiled with debug_info enabled
+%%% or in source form.
+%%%
Local types with the same name as an auto-imported BIF are not accepted by
+%%% PropEr, unless the BIF in question has been declared in a `no_auto_import'
+%%% option.
+%%%
When an expression can be interpreted both as a PropEr type and as a native
+%%% type, the former takes precedence. This means that a function `foo()' will
+%%% shadow a type `foo()' if they are both present in the module. The same rule
+%%% applies to remote functions and types as well.
+%%%
The above may cause some confusion when list syntax is used:
+%%%
+%%%
The expression `[integer()]' can be interpreted both ways, so the
+%%% PropEr way applies. Therefore, instances of this type will always be
+%%% lists of length 1, not arbitrary integer lists, as would be expected when
+%%% interpreting the expression as a native type.
+%%%
Assuming that a custom type foo/1 has been declared, the expression
+%%% `foo([integer()])' can only be interpreted as a native type declaration,
+%%% which means that the generic type of integer lists will be passed to
+%%% `foo/1'.
+%%%
+%%%
+%%%
Currently, PropEr does not detect the following mistakes:
+%%%
+%%%
inline record-field specializations that reference non-existent
+%%% fields
+%%%
type parameters that are not present in the RHS of a `-type'
+%%% declaration
+%%%
using `_' as a type variable in the LHS of a `-type' declaration
+%%%
using the same variable in more than one position in the LHS of a
+%%% `-type' declaration
+%%%
+%%%
+%%%
+%%%
+%%% You can use the these functions to try out the type
+%%% translation subsystem.
+%%%
+%%% CAUTION: These functions should never be used inside
+%%% properties. They are meant for demonstration purposes only.
+%%% @end
-module(proper_typeserver).
-behaviour(gen_server).