Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle configuration in a consistent manner #138

Open
alfert opened this issue Dec 2, 2019 · 10 comments
Open

Handle configuration in a consistent manner #138

alfert opened this issue Dec 2, 2019 · 10 comments

Comments

@alfert
Copy link
Owner

alfert commented Dec 2, 2019

Currently we have several approaches for handling configuration, mostly for historical reasons. This is ugly and should refactored to a consistent, logical approach.

Sources of configuration are:

Let's discuss approaches!

@x4lldux
Copy link
Contributor

x4lldux commented Dec 3, 2019

To sum up, you can pass options to PropEr, using this methods:

  1. directly via property running functions property/4 / quickcheck/3 (all options),
  2. directly via property definition functions forall/3, exists/2, etc. (all options),
  3. setting module-global options via use PropCheck, default_opts: ... (all options,
    a) setting list of options
    b) setting a function that returns a list of options
  4. setting global options via config file / application environment (only 2 options supported),
  5. setting global options via system environment variables (only 2 options supported).

There's no clear order in what overrides what.

Edit:
updated the list above.

@x4lldux
Copy link
Contributor

x4lldux commented Dec 4, 2019

Maybe this:
Leaving 1, 2 and 3b. Ditching 3a, 4 and 5. But providing a default implementation for default_opts function (configurable via config file / app env) which would read some options from system environment (similarly as I did in #136 with DefaultOpts.config/0).

Order of priority would be 3b, 1, 2 (outer to inner).

@evnu
Copy link
Collaborator

evnu commented Dec 4, 2019

I would be opposed to removing 5. When debugging failing tests, allowing to override module-local configuration using an environment variable is very nice instead of changing the options manually in the source code.

@x4lldux
Copy link
Contributor

x4lldux commented Dec 4, 2019

@evnu:

But providing a default implementation for default_opts function (configurable via config file / app env) which would read some options from system environment

That's 5 implemented in default_opts function. If user would like to implement their own version, then they would loose 5 unless they also explicitly call our default implementation and merger their opts with the one returned by our implementation.

@alfert
Copy link
Owner Author

alfert commented Dec 5, 2019

I like the approach 5 with default_opts. As already discussed somewhere else, we should provide a general approach to collect the options. Default options cry for the Application Environment, since this the reason why this exists at all. Any other kind of storage implementation is superfluous (unless you convince me - the process dictionary is seen as a nasty, unethical option and is only used by not-enlighted developers ;-) ).

@x4lldux
Copy link
Contributor

x4lldux commented Dec 5, 2019

@alfert might not work when running in async mode. A test module with its own default_opts function might override app env config for all other tests. We would need to create some cache, which stores configuration per test process. I'm not sure it's worth it.

@alfert
Copy link
Owner Author

alfert commented Dec 5, 2019

might not work when running in async mode.

I need to be more precise, default options is the wrong name. What I meant are global configurations, which are independent from a specific test case (= global) and should be constant over a test run. They are injected by OS environment, mix or application configuration and the like. These settings should be parsed and then stored in the Application Environment centrally such that any evaluation of these global settings can happen independent from the source of injection. In this way we have only place where parsing of parameters and values happens. E.g. encoding of parameter names and values are different if done in mix and by the os environment.

@alfert
Copy link
Owner Author

alfert commented Dec 6, 2019 via email

@x4lldux
Copy link
Contributor

x4lldux commented Dec 8, 2019

I'm not sure I understand you correctly.
There is a hierarchy of option overriding in PropCheck:
global default opts (currently set by 5, 4 and some hard coded defaults) -> options returned by default_opts function (run on each test run by property/3 macro) -> options set on property/3 -> options set on forall/3. Each step can override previous options. And because options passed to forall are built on top of those passed to property there needs to be a some per process cache (like process dictionary) unless we drop setting options on property and just allow setting them only on forall.

@alfert
Copy link
Owner Author

alfert commented Dec 9, 2019

Hmm, let me try to explain my thoughts. What I understand from your remarks, that we really need a good semantics how the various levels are detected and how they override each other such that

  • the user is clear about the settings applied
  • there is a good developer workflow for using defaults and overriding if required.

With this in mind, let us think about the settings

  • Outer level is the os environment, mapped during startup into the App Env (mode 5). It should only contain the variables PROPCHECK_VERBOSE and PROPCHECK_DETECT_EXCEPTIONS for controlling the overall test process with changing any code or configuration file or similar. I don't think that we would need mode 4 (config file).
  • Inside, we have the default_opts function handling the App Env properly, i.e. AppEnv overrides the respective values defined in the local options.
  • Finally, we have the options set directly at the property and the forall macro. They override the default _opts. There are two exceptions: verbosity and exception handling shall be set from the outside.

To handle this properly, we need a function for proper merging of three lists of options: per property, per module and the App Env. This is at least partially available in #126.

Regarding options on the property and forall and the like, I would suggest to allow it only on the property level. Currently, our docs only discuss :quiet and :verbose as an option, so we would not miss much.

This should also remove the necessity of the process dictionary, since in the property macro the module-local and the global configurations are accessible and the merged options are fed downwards into the property execution. We can provide an undocumented complex data structure for our properly managed options (e.g as tuple: {:propcheck_internal_options, opts}, so that we can detect options set directly on the forall macro to warn the user that this is no longer the intended use (i.e. a deprecation warning).

What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants