%%% @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 == %%% %%% %%% %%% == 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 == %%%
    %%%
  1. All options have a long name.
  2. %%%
  3. Some options also have a short name.
  4. %%%
  5. Options marked with an asterisk * are considered %%% experimental and may be brittle and disappear in future versions.
  6. %%%
%%% %%% === 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: %%% %%%
    %%%
  1. A header line containing the version used and starting time.
  2. %%%
  3. A list of all the options used in the particular run.
  4. %%%
  5. Zero or more {@section Error Reports} about erroneous %%% interleavings.
  6. %%%
  7. Diagnostic messages (see {@section Standard Error Printout}).
  8. %%%
  9. Summary about the analysis, including %%% %%%
  10. %%%
%%% %%% === Error Reports === %%% %%% An error report corresponds to an interleaving that lead to errors and %%% contains at least the following sections: %%% %%% %%% %%% If the program produce any output, this is also included. %%% %%% By default, Concuerror reports the following 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, "", []), 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 %%

    -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