%%% @doc
%%% Concuerror's options module
%%%
%%% The `_option()' functions listed on this page all correspond to
%%% valid configuration options.
%%%
%%% For general documentation go to the Overview page.
%%%
%%% == Table of Contents ==
%%%
%%%
%%%
{@section Help}
%%%
{@section Options}
%%%
{@section Standard Error Printout}
%%%
{@section Report File}
%%%
%%%
%%% == Help ==
%%%
%%% You can also access documentation about options using the {@link
%%% help_option/0. `help'} option. You can get more help with {@link
%%% help_option/0. `concuerror --help help'}. In the future even more
%%% help might be added.
%%%
%%% If you invoke Concuerror without an argument, `--help' is assumed
%%% as an argument.
%%%
%%% == Options ==
%%%
%%%
All options have a long name.
%%%
Some options also have a short name.
%%%
Options marked with an asterisk * are considered
%%% experimental and may be brittle and disappear in future versions.
%%%
%%%
%%% === Arguments ===
%%%
%%% The type of each options' argument is listed at the option's
%%% specification below. When specifying {@type integer()} or {@type
%%% boolean()} options in the command line you can omit `true' or 1
%%% as values.
%%%
%%% === Module Attributes ===
%%%
%%% You can use the following attributes in the module specified by `--module'
%%% to pass options to Concuerror:
%%%
%%%
`-concuerror_options(Options)'
%%%
%%% A list of Options that can be overriden by other options.
%%%
%%%
`-concuerror_options_forced(Options)'
%%%
`error' (exit status: 1)
%%%
%%% A list of Options that override any other options.
%%%
%%%
%%%
%%% This information is also available via {@link
%%% help_option/0. `concuerror --help attributes'}
%%%
%%% === Keywords ===
%%%
%%% Each option is associated with one or more
%%% keywords. These can be used with {@link help_option/0. `help'}
%%% to find related options.
%%%
%%% If you invoke {@link help_option/0. `help'} without an argument,
%%% you will only see options with the keyword `basic'. To see all
%%% options use {@link help_option/0. `--help all'}.
%%%
%%% === Multiple Arguments ===
%%%
%%% Some options can be specified multiple times, each time with a
%%% different argument. For those that don't the last value is kept
%%% (this makes invocation via command line easier). Concuerror
%%% reports any overrides.
%%%
%%% == Standard Error Printout ==
%%%
%%% By default, Concuerror prints diagnostic messages in the standard
%%% error stream. Such messages are also printed at the bottom of the
%%% {@section Report File} after the analysis is completed. You can
%%% find explanation of the classification of these messages in the
%%% {@link verbosity_option/0. `verbosity'} option.
%%%
%%% By default, Concuerror also prints progress information in the
%%% standard error stream. You can find what is the meaning of each field
%%% by running `concuerror --help progress'.
%%%
%%% The printout can be reduced or disabled (see {@link
%%% verbosity_option/0. `verbosity'} option). Diagnostic messages are
%%% always printed in the {@section Report File}.
%%%
%%% == Report File ==
%%%
%%% By default, Concuerror prints analysis findings in a report file.
%%%
%%% This file contains:
%%%
%%%
%%%
A header line containing the version used and starting time.
%%%
A list of all the options used in the particular run.
%%%
Zero or more {@section Error Reports} about erroneous
%%% interleavings.
%%%
Diagnostic messages (see {@section Standard Error Printout}).
%%%
%%%
%%% === Error Reports ===
%%%
%%% An error report corresponds to an interleaving that lead to errors and
%%% contains at least the following sections:
%%%
%%%
%%%
Description of all errors encountered.
%%%
Linear trace of all events in the interleaving. This contains only
%%% the operations that read/write shared information.
%%%
%%%
%%% If the program produce any output, this is also included.
%%%
%%% By default, Concuerror reports the following errors:
%%%
%%%
A process exited abnormally.
%%%
One or more processes are 'stuck' at a receive statement.
%%%
The trace exceeded a (configurable but finite) number of events.
%%%
Abnormal errors.
%%%
%%%
%%% If the {@link show_races_option/0. `show_races'} option is used,
%%% the pairs of racing events that justify the exploration of new
%%% interleavings are also shown. These are shown for all
%%% interleavings, not only the ones with errors.
-module(concuerror_options).
-export(
[ after_timeout_option/0
, assertions_only_option/0
, assume_racing_option/0
, depth_bound_option/0
, disable_sleep_sets_option/0
, dpor_option/0
, exclude_module_option/0
, file_option/0
, first_process_errors_only_option/0
, graph_option/0
, help_option/0
, ignore_error_option/0
, instant_delivery_option/0
, interleaving_bound_option/0
, keep_going_option/0
, module_option/0
, no_output_option/0
, non_racing_system_option/0
, observers_option/0
, optimal_option/0
, output_option/0
, pa_option/0
, print_depth_option/0
, pz_option/0
, quiet_option/0
, scheduling_bound_option/0
, scheduling_bound_type_option/0
, scheduling_option/0
, show_races_option/0
, strict_scheduling_option/0
, symbolic_names_option/0
, test_option/0
, timeout_option/0
, treat_as_normal_option/0
, use_receive_patterns_option/0
, verbosity_option/0
, version_option/0
]).
-export([parse_cl/1, finalize/1]).
-export_type(
[ option_spec/0
, options/0
]).
-export_type(
[ bound/0
, dpor/0
, scheduling/0
, scheduling_bound_type/0
]).
-ifdef(DOC).
-export([generate_option_docfiles/1]).
-endif.
%%%-----------------------------------------------------------------------------
-include("concuerror.hrl").
%%%-----------------------------------------------------------------------------
-type options() :: proplists:proplist().
%% Concuerror's configuration options are given as a `proplist()'.
%% See the list of functions in this module for valid configuration
%% options.
-type bound() :: 'infinity' | non_neg_integer().
%% If you want to pass `infinity' as option from the command-line, use `-1'.
-type dpor() :: 'none' | 'optimal' | 'persistent' | 'source'.
%% See {@link dpor_option/0} for the meaning of values.
-type scheduling() :: 'oldest' | 'newest' | 'round_robin'.
%% See {@link scheduling_option/0} for the meaning of values.
-type scheduling_bound_type() :: 'bpor' | 'delay' | 'none' | 'ubpor'.
%% See {@link scheduling_bound_option/0} for the meaning of values.
%%%-----------------------------------------------------------------------------
-define(MINIMUM_TIMEOUT, 500).
-define(DEFAULT_VERBOSITY, ?linfo).
-define(DEFAULT_PRINT_DEPTH, 20).
-define(DEFAULT_OUTPUT, "concuerror_report.txt").
%%%-----------------------------------------------------------------------------
-define(ATTRIBUTE_OPTIONS, concuerror_options).
-define(ATTRIBUTE_FORCED_OPTIONS, concuerror_options_forced).
-define(ATTRIBUTE_TIP_THRESHOLD, 8).
%%%-----------------------------------------------------------------------------
-type long_name() :: atom().
-type keywords() ::
[ 'advanced' |
'basic' |
'bound'|
'console' |
'erlang' |
'errors' |
'experimental' |
'input' |
'output' |
'por' |
'visual'
].
-type short_name() :: char() | undefined.
-type extra_type() :: 'bound' | 'dpor' | 'scheduling' | 'scheduling_bound_type'.
-type arg_spec() :: getopt:arg_spec() | extra_type() | {extra_type(), term()}.
-type short_help() :: string().
-type long_help() :: string() | 'nolong'.
-opaque option_spec() ::
{ long_name()
, keywords()
, short_name()
, arg_spec()
, short_help()
, long_help()
}.
%% This is used internally to specify option components and is
%% irrelevant for a user of Concuerror.
-define(OPTION_KEY, 1).
-define(OPTION_KEYWORDS, 2).
-define(OPTION_SHORT, 3).
-define(OPTION_GETOPT_TYPE_DEFAULT, 4).
-define(OPTION_GETOPT_SHORT_HELP, 5).
-define(OPTION_GETOPT_LONG_HELP, 6).
options() ->
[ module_option()
, test_option()
, output_option()
, no_output_option()
, verbosity_option()
, quiet_option()
, graph_option()
, symbolic_names_option()
, print_depth_option()
, show_races_option()
, file_option()
, pa_option()
, pz_option()
, exclude_module_option()
, depth_bound_option()
, interleaving_bound_option()
, dpor_option()
, optimal_option()
, scheduling_bound_type_option()
, scheduling_bound_option()
, disable_sleep_sets_option()
, after_timeout_option()
, instant_delivery_option()
, use_receive_patterns_option()
, observers_option()
, scheduling_option()
, strict_scheduling_option()
, keep_going_option()
, ignore_error_option()
, treat_as_normal_option()
, assertions_only_option()
, first_process_errors_only_option()
, timeout_option()
, assume_racing_option()
, non_racing_system_option()
, help_option()
, version_option()
].
%%%-----------------------------------------------------------------------------
-ifdef(DOC).
%% @private
-spec generate_option_docfiles(filename:filename()) -> ok.
generate_option_docfiles(Dir) ->
lists:foreach(fun(O) -> generate_option_docfile(O, Dir) end, options()).
-spec generate_option_docfile(option_spec(), filename:filename()) -> ok.
generate_option_docfile(Option, Dir) ->
OptionName = element(?OPTION_KEY, Option),
OptionShortHelp = element(?OPTION_GETOPT_SHORT_HELP, Option),
OptionShort = element(?OPTION_SHORT, Option),
OptionArg = element(?OPTION_GETOPT_TYPE_DEFAULT, Option),
OptionKeywords = element(?OPTION_KEYWORDS, Option),
OptionLongHelp = element(?OPTION_GETOPT_LONG_HELP, Option),
Filename = filename:join([Dir, atom_to_list(OptionName) ++ "_option.edoc"]),
{ok, File} = file:open(Filename, [write]),
print_docfile_preamble(File),
io:format(File, "@doc ~s~n~n", [OptionShortHelp]),
io:format(File, "
", []),
item(
File,
"Name: `--~p Value' or `@{~p, Value@}'",
[OptionName, OptionName]),
case OptionShort =:= undefined of
true -> ok;
false -> item(File, "Short: `-~c'", [OptionShort])
end,
{Type, DefaultVal} =
case OptionArg of
{T, D} -> {T, {true, D}};
T -> {T, false}
end,
item(File, "Argument type: {@type ~w()}", [Type]),
case DefaultVal of
{true, DV} -> item(File, "Default value: `~p'", [DV]);
false -> ok
end,
AllowedInModuleAttributes =
not lists:member(OptionName, not_allowed_in_module_attributes()),
item(
File, "Allowed in {@section Module Attributes}: ~p",
[to_yes_or_no(AllowedInModuleAttributes)]),
MultipleAllowed =
lists:member(OptionName, multiple_allowed()),
item(
File, "{@section Multiple Arguments}: ~p",
[to_yes_or_no(MultipleAllowed)]),
case OptionKeywords =:= [] of
true -> ok;
false ->
StringKeywords =
string:join([atom_to_list(K) || K <- OptionKeywords], ", "),
item(File, "{@section Keywords}: ~s", [StringKeywords])
end,
io:format(File, "
", []),
case OptionLongHelp =:= nolong of
true -> ok;
false -> io:format(File, OptionLongHelp ++ "~n", [])
end,
file:close(File).
print_docfile_preamble(File) ->
Format =
"%% ATTENTION!~n"
"%% This file is generated by ~w:generate_option_docfile/2~n"
"~n",
io:format(File, Format, [?MODULE]).
item(File, Format, Args) ->
io:format(File, "
" ++ Format ++ "
", Args).
to_yes_or_no(true) -> yes;
to_yes_or_no(false) -> no.
-endif.
%%%-----------------------------------------------------------------------------
%% @docfile "doc/module_option.edoc"
-spec module_option() -> option_spec().
module_option() ->
{ module
, [basic, input]
, $m
, atom
, "Module containing the test function"
, "Concuerror begins exploration from a test function located in the module"
" specified by this option.~n"
"~n"
"There is no need to specify modules used in the test if they are in"
" Erlang's code path. Otherwise use `--file', `--pa' or `--pz'."
}.
%% @docfile "doc/test_option.edoc"
-spec test_option() -> option_spec().
test_option() ->
{ test
, [basic, input]
, $t
, {atom, test}
, "Name of test function"
, "This must be a 0-arity function located in the module specified by"
" `--module'. Concuerror will start the test by spawning a process that"
" calls this function."
}.
%% @docfile "doc/output_option.edoc"
-spec output_option() -> option_spec().
output_option() ->
{ output
, [basic, output]
, $o
, {string, ?DEFAULT_OUTPUT}
, "Filename to use for the analysis report"
, "This is where Concuerror writes the results of the analysis."
}.
%% @docfile "doc/no_output_option.edoc"
-spec no_output_option() -> option_spec().
no_output_option() ->
{ no_output
, [basic, output]
, undefined
, boolean
, "Do not produce an analysis report"
, "Concuerror will not produce an analysis report."
}.
%% @docfile "doc/verbosity_option.edoc"
-spec verbosity_option() -> option_spec().
verbosity_option() ->
{ verbosity
, [advanced, basic, console]
, $v
, {integer, ?DEFAULT_VERBOSITY}
, io_lib:format("Verbosity level (0-~w)", [?MAX_LOG_LEVEL])
, "The value of verbosity determines what is shown on standard error."
" Messages up to info are always also shown in the output file."
" The available levels are the following:~n~n"
"0 (quiet) Nothing is printed (equivalent to `--quiet')~n"
"1 (error) Critical, resulting in early termination~n"
"2 (warn) Non-critical, notifying about weak support for a feature or~n"
" the use of an option that alters the output~n"
"3 (tip) Notifying of a suggested refactoring or option to make~n"
" testing more efficient~n"
"4 (info) Normal operation messages, can be ignored~n"
"5 (time) Timing messages~n"
"6 (debug) Used only during debugging~n"
"7 (trace) Everything else"
}.
%% @docfile "doc/quiet_option.edoc"
%% @see verbosity_option/0
-spec quiet_option() -> option_spec().
quiet_option() ->
{ quiet
, [basic, console]
, $q
, boolean
, "Synonym for `--verbosity 0'"
, "Do not write anything to standard error."
}.
%% @docfile "doc/graph_option.edoc"
-spec graph_option() -> option_spec().
graph_option() ->
{ graph
, [output, visual]
, $g
, string
, "Produce a DOT graph in the specified file"
, "The DOT graph can be converted to an image with"
" e.g. `dot -Tsvg -o graph.svg graph'"
}.
%% @docfile "doc/symbolic_names_option.edoc"
-spec symbolic_names_option() -> option_spec().
symbolic_names_option() ->
{ symbolic_names
, [erlang, output, visual]
, $s
, {boolean, true}
, "Use symbolic process names"
, "Replace PIDs with symbolic names in outputs. The format used is:~n"
" `<[symbolic name]/[last registered name]>'~n"
"where [symbolic name] is:~n~n"
" - `P', for the first process and~n"
" - `[parent symbolic name].[ordinal]', for any other process,"
" where [ordinal] shows the order of spawning (e.g. `' is the"
" second process spawned by `
').~n"
"The `[last registered name]' part is shown only if applicable."
}.
%% @docfile "doc/print_depth_option.edoc"
-spec print_depth_option() -> option_spec().
print_depth_option() ->
{ print_depth
, [output, visual]
, undefined
, {integer, ?DEFAULT_PRINT_DEPTH}
, "Print depth for log/graph"
, "Specifies the max depth for any terms printed in the log (behaves"
" just as the additional argument of `~~W' and `~~P' argument of"
" `io:format/3'). If you want more info about a particular piece"
" of data in an interleaving, consider using `erlang:display/1'"
" and checking the standard output section in the error reports"
" of the analysis report instead."
}.
%% @docfile "doc/show_races_option.edoc"
-spec show_races_option() -> option_spec().
show_races_option() ->
{ show_races
, [output, por, visual]
, undefined
, {boolean, false}
, "Show races in log/graph",
"Determines whether information about pairs of racing instructions will be"
" included in the logs of erroneous interleavings and the graph."
}.
%% @docfile "doc/file_option.edoc"
-spec file_option() -> option_spec().
file_option() ->
{ file
, [input]
, $f
, string
, "Load specified file (.beam or .erl)"
, "Explicitly load the specified file(s) (.beam or .erl)."
" Source (.erl) files should not require any command line compile options."
" Use a .beam file (preferably compiled with `+debug_info') if special"
" compilation is needed.~n"
"~n"
"It is recommended to rely on Erlang's load path rather than using"
" this option."
}.
%% @docfile "doc/pa_option.edoc"
-spec pa_option() -> option_spec().
pa_option() ->
{ pa
, [input]
, undefined
, string
, "Add directory to Erlang's code path (front)"
, "Works exactly like `erl -pa'."
}.
%% @docfile "doc/pz_option.edoc"
-spec pz_option() -> option_spec().
pz_option() ->
{ pz
, [input]
, undefined
, string
, "Add directory to Erlang's code path (rear)"
, "Works exactly like `erl -pz'."
}.
%% @docfile "doc/exclude_module_option.edoc"
-spec exclude_module_option() -> option_spec().
exclude_module_option() ->
{ exclude_module
, [advanced, experimental, input]
, $x
, atom
, "* Modules that should not be instrumented"
, "Experimental. Concuerror needs to instrument all code in a test to be able"
" to reset the state after each exploration. You can use this option to"
" exclude a module from instrumentation, but you must ensure that any state"
" is reset correctly, or Concuerror will complain that operations have"
" unexpected results."
}.
%% @docfile "doc/depth_bound_option.edoc"
-spec depth_bound_option() -> option_spec().
depth_bound_option() ->
{ depth_bound
, [bound]
, $d
, {integer, 500}
, "Maximum number of events"
, "The maximum number of events allowed in an interleaving. Concuerror will"
" stop exploring an interleaving that has events beyond this limit."
}.
%% @docfile "doc/interleaving_bound_option.edoc"
-spec interleaving_bound_option() -> option_spec().
interleaving_bound_option() ->
{ interleaving_bound
, [bound]
, $i
, {bound, infinity}
, "Maximum number of interleavings"
, "The maximum number of interleavings that will be explored. Concuerror will"
" stop exploration beyond this limit."
}.
%% @docfile "doc/dpor_option.edoc"
-spec dpor_option() -> option_spec().
dpor_option() ->
{ dpor
, [por]
, undefined
, {dpor, optimal}
, "DPOR technique"
, "Specifies which Dynamic Partial Order Reduction technique will be used."
" The available options are:~n"
"- `none': Disable DPOR. Not recommended.~n"
"- `optimal': Using source sets and wakeup trees.~n"
"- `source': Using source sets only. Use this if the rate of~n"
" exploration is too slow. Use `optimal' if a lot of~n"
" interleavings are reported as sleep-set blocked.~n"
"- `persistent': Using persistent sets. Not recommended."
}.
%% @docfile "doc/optimal_option.edoc"
%% @see dpor_option/0
-spec optimal_option() -> option_spec().
optimal_option() ->
{ optimal
, [por]
, undefined
, boolean
, "Synonym for `--dpor optimal (true) | source (false)'"
, nolong
}.
%% @docfile "doc/scheduling_bound_type_option.edoc"
-spec scheduling_bound_type_option() -> option_spec().
scheduling_bound_type_option() ->
{ scheduling_bound_type
, [bound, experimental]
, $c
, {scheduling_bound_type, none}
, "* Schedule bounding technique"
, "Enables scheduling rules that prevent interleavings from being explored."
" The available options are:~n"
"- `none': no bounding~n"
"- `bpor': how many times per interleaving the scheduler is allowed~n"
" to preempt a process.~n"
" * Not compatible with Optimal DPOR.~n"
"- `delay': how many times per interleaving the scheduler is allowed~n"
" to skip the process chosen by default in order to schedule~n"
" others.~n"
"- `ubpor': same as `bpor' but without conservative backtrack points.~n"
" * Experimental, unsound, not compatible with Optimal DPOR.~n"
}.
%% @docfile "doc/scheduling_bound_option.edoc"
-spec scheduling_bound_option() -> option_spec().
scheduling_bound_option() ->
{ scheduling_bound
, [bound]
, $b
, integer
, "Scheduling bound value"
, "The maximum number of times the rule specified in"
" `--scheduling_bound_type' can be violated."
}.
%% @docfile "doc/disable_sleep_sets_option.edoc"
-spec disable_sleep_sets_option() -> option_spec().
disable_sleep_sets_option() ->
{ disable_sleep_sets
, [advanced, por]
, undefined
, {boolean, false}
, "Disable sleep sets"
, "This option is only available with `--dpor none'."
}.
%% @docfile "doc/after_timeout_option.edoc"
-spec after_timeout_option() -> option_spec().
after_timeout_option() ->
{ after_timeout
, [erlang]
, $a
, {bound, infinity}
, "Threshold for treating timeouts as infinity"
, "Assume that `after' clauses with timeouts higher or equal to the"
" specified value cannot be triggered. Concuerror treats all"
" lower values as triggerable"
}.
%% @docfile "doc/instant_delivery_option.edoc"
-spec instant_delivery_option() -> option_spec().
instant_delivery_option() ->
{ instant_delivery
, [erlang]
, undefined
, {boolean, true}
, "Make messages and signals arrive instantly"
, "Assume that messages and signals are delivered immediately."
" Setting this to false enables \"true\" Erlang message-passing"
" semantics, in which message delivery is distinct from sending."
}.
%% @docfile "doc/use_receive_patterns_option.edoc"
-spec use_receive_patterns_option() -> option_spec().
use_receive_patterns_option() ->
{ use_receive_patterns
, [advanced, erlang, por]
, undefined
, {boolean, true}
, "Use receive patterns for racing sends"
, "If true, Concuerror will only consider two"
" message deliveries as racing when the first message is really"
" received and the patterns used could also match the second"
" message."
}.
%% @docfile "doc/observers_option.edoc"
%% @see use_receive_patterns_option/0
-spec observers_option() -> option_spec().
observers_option() ->
{ observers
, [advanced, erlang, por]
, undefined
, boolean
, "Synonym for `--use_receive_patterns'"
, nolong
}.
%% @docfile "doc/scheduling_option.edoc"
-spec scheduling_option() -> option_spec().
scheduling_option() ->
{ scheduling
, [advanced]
, undefined
, {scheduling, round_robin}
, "Scheduling order"
, "How Concuerror picks the next process to run. The available options are"
" `oldest', `newest' and `round_robin', with the expected semantics."
}.
%% @docfile "doc/strict_scheduling_option.edoc"
-spec strict_scheduling_option() -> option_spec().
strict_scheduling_option() ->
{ strict_scheduling
, [advanced]
, undefined
, {boolean, false}
, "Force preemptions when scheduling"
, "Whether Concuerror should enforce the scheduling strategy strictly or let"
" a process run until blocked before reconsidering the scheduling policy."
}.
%% @docfile "doc/keep_going_option.edoc"
-spec keep_going_option() -> option_spec().
keep_going_option() ->
{ keep_going
, [basic, errors]
, $k
, {boolean, false}
, "Keep running after an error is found"
, "Concuerror stops by default when the first error is found. Enable this"
" option to keep looking for more errors.~n"
"~n"
" It is usually recommended to modify the test, or"
" use the `--ignore_error' / `--treat_as_normal' options, instead of"
" this one."
}.
%% @docfile "doc/ignore_error_option.edoc"
-spec ignore_error_option() -> option_spec().
ignore_error_option() ->
{ ignore_error
, [errors]
, undefined
, atom
, "Error categories that should be ignored",
"Concuerror will not report errors of the specified kind:~n"
"- `abnormal_exit': processes exiting with any abnormal reason;"
" check `-h treat_as_normal' and `-h assertions_only' for more refined"
" control~n"
"- `abnormal_halt': processes executing erlang:halt/1,2 with status /= 0~n"
"- `deadlock': processes waiting at a receive statement~n"
"- `depth_bound': reaching the depth bound; check `-h depth_bound'"
}.
%% @docfile "doc/treat_as_normal_option.edoc"
-spec treat_as_normal_option() -> option_spec().
treat_as_normal_option() ->
{ treat_as_normal
, [errors]
, undefined
, atom
, "Exit reason treated as `normal' (i.e., not reported as an error)"
, "A process that exits with the specified atom as reason, or with a reason"
" that is a tuple with the specified atom as a first element, will not be"
" reported as exiting abnormally. Useful e.g. when analyzing supervisors"
" (`shutdown' is usually a normal exit reason in this case)."
}.
%% @docfile "doc/assertions_only_option.edoc"
-spec assertions_only_option() -> option_spec().
assertions_only_option() ->
{ assertions_only
, [errors]
, undefined
, {boolean, false}
, "Report only abnormal exits due to `?asserts'",
"Only processes that exit with a reason of form `{{assert*, _}, _}' are"
" considered errors. Such exit reasons are generated e.g. by the"
" macros defined in the `stdlib/include/assert.hrl' header file."
}.
%% @docfile "doc/first_process_errors_only_option.edoc"
-spec first_process_errors_only_option() -> option_spec().
first_process_errors_only_option() ->
{ first_process_errors_only
, [errors]
, undefined
, {boolean, false}
, "Report only errors that involve the first process"
, "All errors involving only children processes will be ignored."
}.
%% @docfile "doc/timeout_option.edoc"
-spec timeout_option() -> option_spec().
timeout_option() ->
{ timeout
, [advanced, erlang]
, undefined
, {bound, 5000}
, "How long to wait for an event (>= " ++
integer_to_list(?MINIMUM_TIMEOUT) ++ "ms)"
, "How many ms to wait before assuming that a process is stuck in an infinite"
" loop between two operations with side-effects. Setting this to `infinity'"
" will make Concuerror wait indefinitely. Otherwise must be >= " ++
integer_to_list(?MINIMUM_TIMEOUT) ++ "."
}.
%% @docfile "doc/assume_racing_option.edoc"
-spec assume_racing_option() -> option_spec().
assume_racing_option() ->
{ assume_racing
, [advanced, por]
, undefined
, {boolean, true}
, "Do not crash if race info is missing"
, "Concuerror has a list of operation pairs that are known to be non-racing."
" If there is no info about whether a specific pair of built-in operations"
" may race, assume that they do indeed race. If this option is set to"
" false, Concuerror will exit instead. Useful only for detecting missing"
" racing info."
}.
%% @docfile "doc/non_racing_system_option.edoc"
-spec non_racing_system_option() -> option_spec().
non_racing_system_option() ->
{ non_racing_system
, [erlang]
, undefined
, atom
, "No races due to 'system' messages"
, "Assume that any messages sent to the specified (by registered name) system"
" process are not racing with each-other. Useful for reducing the number of"
" interleavings when processes have calls to e.g. io:format/1,2 or"
" similar."
}.
%% @docfile "doc/help_option.edoc"
-spec help_option() -> option_spec().
help_option() ->
{ help
, [basic]
, $h
, atom
, "Display help (use `-h h' for more help)"
, "Without an argument, prints info for basic options.~n~n"
"With `all' as argument, prints info for all options.~n~n"
"With `attributes' as argument, prints info about passing options using"
" module attributes.~n~n"
"With `progress' as argument, prints info about what the items in the"
" progress info mean.~n~n"
"With an option name as argument, prints more help for that option.~n~n"
"Options have keywords associated with them (shown in their help)."
" With a keyword as argument, prints a list of all the options that are"
" associated with the keyword.~n~n"
"If a boolean or integer argument is omitted, `true' or `1' is the implied"
" value."
}.
%% @docfile "doc/version_option.edoc"
-spec version_option() -> option_spec().
version_option() ->
{ version
, [basic]
, undefined
, undefined
, "Display version information"
, nolong
}.
%%%-----------------------------------------------------------------------------
synonyms() ->
[ {{observers, true}, {use_receive_patterns, true}}
, {{observers, false}, {use_receive_patterns, false}}
, {{optimal, true}, {dpor, optimal}}
, {{optimal, false}, {dpor, source}}
, {{ignore_error, crash}, {ignore_error, abnormal_exit}}
].
groupable() ->
[ exclude_module
, ignore_error
, non_racing_system
, treat_as_normal
].
multiple_allowed() ->
[ pa
, pz
] ++
groupable().
not_allowed_in_module_attributes() ->
[ exclude_module
, file
, help
, module
, pa
, pz
, version
].
derived_defaults() ->
[ {{disable_sleep_sets, true}, [{dpor, none}]}
, {scheduling_bound, [{scheduling_bound_type, delay}]}
, {{scheduling_bound_type, bpor}, [{dpor, source}, {scheduling_bound, 1}]}
, {{scheduling_bound_type, delay}, [{scheduling_bound, 1}]}
, {{scheduling_bound_type, ubpor}, [{dpor, source}, {scheduling_bound, 1}]}
] ++
[{{dpor, NotObsDPOR}, [{use_receive_patterns, false}]}
|| NotObsDPOR <- [none, persistent, source]].
check_validity(Key) ->
case Key of
_
when
Key =:= after_timeout;
Key =:= depth_bound;
Key =:= print_depth
->
{fun(V) -> V > 0 end, "a positive integer"};
dpor ->
[none, optimal, persistent, source];
ignore_error ->
Valid = [abnormal_halt, abnormal_exit, deadlock, depth_bound],
{fun(V) -> [] =:= (V -- Valid) end,
io_lib:format("one or more of ~w", [Valid])};
scheduling ->
[newest, oldest, round_robin];
scheduling_bound ->
{fun(V) -> V >= 0 end, "a non-negative integer"};
scheduling_bound_type ->
[bpor, delay, none, ubpor];
_ -> skip
end.
%%------------------------------------------------------------------------------
%% @doc Converts command-line arguments to a proplist using getopt
%%
%% This function also augments the interface of getopt, allowing
%%
%%
{@section multiple arguments} to options
%%
correction of common errors
%%
-spec parse_cl([string()]) ->
{'run', options()} | {'return', concuerror:analysis_result()}.
parse_cl(CommandLineArgs) ->
try
%% CL parsing uses some version-dependent functions
check_otp_version(),
parse_cl_aux(CommandLineArgs)
catch
throw:opt_error -> options_fail()
end.
options_fail() ->
[concuerror_logger:print_log_message(Level, Format, Args)
|| {Level, Format, Args} <- get_logs()],
{return, fail}.
parse_cl_aux([]) ->
{run, [help]};
parse_cl_aux(RawCommandLineArgs) ->
CommandLineArgs = fix_common_errors(RawCommandLineArgs),
case getopt:parse(getopt_spec_no_default(), CommandLineArgs) of
{ok, {Options, OtherArgs}} ->
case OtherArgs of
[] -> {run, Options};
[MaybeFilename] ->
Msg = "Converting dangling argument to '--file ~s'",
opt_info(Msg, [MaybeFilename]),
{run, Options ++ [{file, MaybeFilename}]};
_ ->
Msg = "Unknown argument(s)/option(s): ~s",
opt_error(Msg, [?join(OtherArgs, " ")])
end;
{error, Error} ->
case Error of
{missing_option_arg, help} ->
cl_usage(basic),
{return, ok};
{missing_option_arg, Option} ->
opt_error("No argument given for '--~s'.", [Option], Option);
_Other ->
opt_error(getopt:format_error([], Error))
end
end.
fix_common_errors(RawCommandLineArgs) ->
FixSingle = lists:map(fun fix_common_error/1, RawCommandLineArgs),
fix_multiargs(FixSingle).
fix_common_error("--" ++ [C] = Option) ->
opt_info("\"~s\" converted to \"-~c\"", [Option, C]),
"-" ++ [C];
fix_common_error("--" ++ Text = Option) ->
Underscored = lists:map(fun dash_to_underscore/1, lowercase(Text)),
case Text =:= Underscored of
true -> Option;
false ->
opt_info("\"~s\" converted to \"--~s\"", [Option, Underscored]),
"--" ++ Underscored
end;
fix_common_error("-p" ++ [A] = Option) when A =:= $a; A=:= $z ->
opt_info("\"~s\" converted to \"-~s\"", [Option, Option]),
fix_common_error("-" ++ Option);
fix_common_error("-" ++ [Short|[_|_] = MaybeArg] = MaybeMispelledOption) ->
maybe_warn_about_mispelled_option(Short, MaybeArg),
MaybeMispelledOption;
fix_common_error("infinity" = Infinity) ->
%% We can't just convert, cause maybe a module or function is named infinity.
opt_warn("use -1 instead of `infinity' for bounds on the command line", []),
Infinity;
fix_common_error(OptionOrArg) ->
OptionOrArg.
dash_to_underscore($-) -> $_;
dash_to_underscore(Ch) -> Ch.
maybe_warn_about_mispelled_option(Short, [_|_] = MaybeArg) ->
ShortWithArgToLong =
[{element(?OPTION_SHORT, O), element(?OPTION_KEY, O)}
|| O <- options(),
element(?OPTION_SHORT, O) =/= undefined,
not (option_type(O) =:= boolean),
not (option_type(O) =:= integer),
not (option_type(O) =:= bound)
],
case lists:keyfind(Short, 1, ShortWithArgToLong) of
{_, Long} ->
opt_info(
"Parsing '-~s' as '--~w ~s' (add a dash if this is not desired)",
[[Short|MaybeArg], Long, MaybeArg]);
_ -> ok
end.
option_type(Option) ->
case element(?OPTION_GETOPT_TYPE_DEFAULT, Option) of
{Type, _Default} -> Type;
Type -> Type
end.
fix_multiargs(CommandLineArgs) ->
fix_multiargs(CommandLineArgs, []).
fix_multiargs([], Fixed) ->
lists:reverse(Fixed);
fix_multiargs([Option1, Arg1, Arg2 | Rest], Fixed)
when hd(Option1) =:= $-, hd(Arg1) =/= $-, hd(Arg2) =/= $- ->
opt_info(
"\"~s ~s ~s\" converted to \"~s ~s ~s ~s\"",
[Option1, Arg1, Arg2, Option1, Arg1, Option1, Arg2]),
fix_multiargs([Option1, Arg2|Rest], [Arg1, Option1|Fixed]);
fix_multiargs([Other|Rest], Fixed) ->
fix_multiargs(Rest, [Other|Fixed]).
%%%-----------------------------------------------------------------------------
getopt_spec(Options) ->
getopt_spec_map_type(Options, fun(X) -> X end).
%% Defaults are stripped and inserted in the end to allow for overrides from an
%% input file or derived defaults.
getopt_spec_no_default() ->
getopt_spec_map_type(options(), fun no_default/1).
%% An option's long name is the same as the inner representation atom for
%% consistency.
getopt_spec_map_type(Options, Fun) ->
[{Key, Short, atom_to_list(Key), Fun(Type), Help} ||
{Key, _Keywords, Short, Type, Help, _Long} <- Options].
no_default({Type, _Default}) -> no_default(Type);
no_default(bound) -> integer;
no_default(dpor) -> atom;
no_default(scheduling) -> atom;
no_default(scheduling_bound_type) -> atom;
no_default(Type) -> Type.
%%%-----------------------------------------------------------------------------
cl_usage(all) ->
Sort = fun(A, B) -> element(?OPTION_KEY, A) =< element(?OPTION_KEY, B) end,
getopt:usage(getopt_spec(lists:sort(Sort, options())), "./concuerror"),
print_suffix(all);
cl_usage(Attribute)
when Attribute =:= attribute;
Attribute =:= attributes ->
%% KEEP IN SYNC WITH MODULE'S OVERVIEW EDOC
Msg =
"~n"
"Passing options using module attributes:~n"
"----------------------------------------~n"
"You can use the following attributes in the module specified by `--module'"
" to pass options to Concuerror:~n"
"~n"
" -~s(Options).~n"
" A list of Options that can be overriden by other options.~n"
" -~s(Options).~n"
" A list of Options that override any other options.~n"
,
to_stderr(Msg, [?ATTRIBUTE_OPTIONS, ?ATTRIBUTE_FORCED_OPTIONS]);
cl_usage(progress) ->
Msg =
"~n"
"Progress bar item explanations:~n"
"-------------------------------~n"
"~n"
"~s"
,
to_stderr(Msg, [concuerror_logger:progress_help()]);
cl_usage(Name) ->
Optname =
case lists:keyfind(Name, ?OPTION_KEY, options()) of
false ->
Str = atom_to_list(Name),
Name =/= undefined andalso
length(Str) =:= 1 andalso
lists:keyfind(hd(Str), ?OPTION_SHORT, options());
R -> R
end,
case Optname of
false ->
MaybeKeyword = options(Name),
case MaybeKeyword =/= [] of
true ->
KeywordWarningFormat =
"~n"
"NOTE: Only showing options with the keyword `~p'.~n"
" Use `--help all' to see all available options.~n",
to_stderr(KeywordWarningFormat, [Name]),
getopt:usage(getopt_spec(MaybeKeyword), "./concuerror"),
print_suffix(Name);
false ->
ListName = atom_to_list(Name),
case [dash_to_underscore(L) || L <- ListName] of
"_" ++ Rest -> cl_usage(list_to_atom(Rest));
Other when Other =/= ListName -> cl_usage(list_to_atom(Other));
_ ->
Msg = "invalid option/keyword (as argument to `--help'): '~w'.",
opt_error(Msg, [Name], help)
end
end;
Tuple ->
getopt:usage(getopt_spec([Tuple]), "./concuerror"),
case element(?OPTION_GETOPT_LONG_HELP, Tuple) of
nolong -> to_stderr("No additional help available.~n");
String -> to_stderr(String ++ "~n")
end,
{Keywords, Related} = get_keywords_and_related(Tuple),
to_stderr("Option Keywords: ~p~n", [Keywords]),
to_stderr("Related Options: ~p~n", [Related]),
to_stderr("For general help use `-h' without an argument.~n")
end.
options(Keyword) ->
[T || T <- options(), lists:member(Keyword, element(?OPTION_KEYWORDS, T))].
print_suffix(Keyword) ->
case Keyword =:= basic of
false -> to_stderr("Options with '*' are experimental.~n");
true -> ok
end,
to_stderr("More info & keywords about a specific option: `-h