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

Extend PropEr with parallel execution #263

Merged
merged 37 commits into from
May 15, 2021

Conversation

pablocostass
Copy link
Contributor

@pablocostass pablocostass commented Mar 25, 2021

This PR extends PropEr with parallel execution. It is based on the work I did for my undergraduate thesis and has some limitations at the moment (e.g., targeted properties). Nonetheless, it should pretty much work (famous last words) and one can find the relevant options to test with parallel PropEr in the documentation of this branch, so please do take a look at it too.

The PR has two extra commits that I considered relevant but might be dropped upon request, those are:

  • Add verbosity to make test output (df7a7d8)
    Adds verbosity when running the tests, as that way it is easier to check at what point parallel PropEr failed in the suite.

  • Add a workflow to test with parallel PropEr (4c81af9)
    This commit adds a new workflow to be ran with Github Actions that uses 2 workers to run the test suite and ignores failures in the workflow; still, the warnings/failures are recorded by GA, so they will not be lost. This way, no PR would be blocked by some flakyness coming from parallel PropEr.

In short, this PR brings four new options to the table, all related to this new kind of execution:

  1. {numworkers, <Non_negative_number>} sets the number of workers to use during testing.
  2. pure (side effect free) or impure (with side effects) tells PropEr the kind of property it is going to test; impure properties start nodes to isolate the workers from the others and avoid possible test clashes.
  3. {strategy_fun, <Strategy_function>} overrides the default function that divides the workload among the total of workers.
  4. {stop_nodes, true | false} tells PropEr whether it should stop the nodes (if any was started) after finishing testing. This one is mainly used for proper:module/1,2, but it will probably be useful for some (e.g., build tools).

@pablocostass pablocostass changed the title Parallel proper Extend PropEr with parallel execution Mar 25, 2021
@codecov-io
Copy link

codecov-io commented Mar 25, 2021

Codecov Report

Merging #263 (6bf55fe) into master (275c218) will increase coverage by 0.30%.
The diff coverage is 94.02%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #263      +/-   ##
==========================================
+ Coverage   88.65%   88.95%   +0.30%     
==========================================
  Files          14       14              
  Lines        4408     4601     +193     
==========================================
+ Hits         3908     4093     +185     
- Misses        500      508       +8     
Impacted Files Coverage Δ
src/proper.erl 88.92% <94.02%> (+1.58%) ⬆️
src/proper_statem.erl 94.67% <0.00%> (-0.39%) ⬇️
src/proper_typeserver.erl 79.90% <0.00%> (+0.11%) ⬆️
src/proper_types.erl 95.09% <0.00%> (+0.65%) ⬆️
src/proper_arith.erl 92.70% <0.00%> (+1.04%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 275c218...6bf55fe. Read the comment docs.

@kostis kostis self-requested a review March 26, 2021 08:43
kostis added a commit that referenced this pull request Mar 30, 2021
Before GitHub actions, we separated some lengthy (and flaky) examples tests out of `proper_tests` and placed them in a separate test target in the `Makefile`.  For increased coverage, some of the examples were still tested in `proper_tests` as whole modules.

Both for consistency and in preparation for using the examples as tests for #263, we now have a _single_ place (GitHub action) where the complete set of examples is tested.
Makefile Outdated Show resolved Hide resolved
src/proper.erl Outdated Show resolved Hide resolved
src/proper.erl Outdated Show resolved Hide resolved
@pablocostass
Copy link
Contributor Author

@kostis I had forgotten to add the new make target to the Github Action workflow, so I did that after rebasing on top of the latest changes on the main branch.

I see that the Github Action is still failing due to the cache, however this time it is because the current cache is from the main branch and in said branch we do not build the PLT file with tools too, so I guess that in 7 days from now on we will have to trigger another run :/

@kostis
Copy link
Collaborator

kostis commented Apr 12, 2021

I've pushed a small change to the Makefile that subsumes the addition of tools to the PLT -- so please rebase.

In order to achieve some progress, perhaps it's a good idea to temporarily disable the -Wunknown from the dialyzer call so that testing can continue with the tests.

@pablocostass
Copy link
Contributor Author

Yup, that commit of yours helped @kostis, thanks! I rebased and pushed with the addition of a commit that disables the unknown warning, as you suggested. We can drop it in the future whenever we want :)

@codecov-commenter
Copy link

codecov-commenter commented Apr 25, 2021

Codecov Report

Merging #263 (a2497b2) into master (e3a12c4) will decrease coverage by 3.30%.
The diff coverage is 13.22%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #263      +/-   ##
==========================================
- Coverage   88.47%   85.17%   -3.31%     
==========================================
  Files          14       14              
  Lines        4408     4586     +178     
==========================================
+ Hits         3900     3906       +6     
- Misses        508      680     +172     
Impacted Files Coverage Δ
src/proper.erl 70.38% <13.22%> (-17.27%) ⬇️
src/proper_statem.erl 92.77% <0.00%> (-1.91%) ⬇️
src/proper_gen.erl 86.69% <0.00%> (-0.50%) ⬇️
src/proper_typeserver.erl 78.76% <0.00%> (-0.46%) ⬇️
src/proper_erlang_abstract_code.erl 94.24% <0.00%> (+0.25%) ⬆️
src/proper_gen_next.erl 77.41% <0.00%> (+0.26%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e3a12c4...a2497b2. Read the comment docs.

pablocostass and others added 14 commits May 3, 2021 20:42
Modify the `user_opts` type and `opts` record to
allow the possibility of spawning multiple
processes to perform the tests

Update `inner_test` function: each process is
assigned a cerain number of tests to run from
the total count. The main process aggregates the
output received in case of success, otherwise
returns early with the error or fail

Some type specs were updated to reflect the changes
needed to use the `spawn_link_migrate` function
This way, when using workers to distribute tests
among processes, PropEr will first create a node
for them to be spawned on, avoiding crashing the
whole VM if something goes wrong.
…sions

When testing stateful programs, the node where
the tests were being performed would crash because
normally stateful programs use gen_servers or unique
elements, which made processes modify each other's
states.

To avoid that now each process is spawned on a
different node, which makes using many processes
a bit more expensive.
As previously stated, for stateful properties each worker
would need a node of his own to avoid clashing with
the other workers, whereas on stateless properties all the
workers could be performing tests on the same node.

This commit fixes last one by starting only one node if it
detects a stateless property is being tested, or a new node
per worker in the case of a stateful property.
As `lists:zip/2` expects two lists of the same length,
it would crash on stateful properties when `numtests` was
less than `num_processes`. In that case, we only need to
spawn `numtests` processes (since otherwise we would end up
having idle workers).
Also did some refactor, mainly changin from
*processes* to *workers* (and so `num_processes`
to `num_workers`, etc), and added some documentation.
Also enforce a more strict pattern matching when trying
to determine if a property is stateless or stateful.
Instead of adding a workflow to run the test suite with parallel PropEr,
we should have a target in the Makefile to run the examples with it.

So, this commit removes all references to the `num_workers` option in
the suite except for the test case that runs the examples,
`examples_are_ok_test_/0`, as we do want to test them in parallel
and the test case will be removed in the future.
This commit can and will be dropped at a future point in time, and is
only being added to help continue testing parallel PropEr in Github Actions.
.github/workflows/ci.yml Outdated Show resolved Hide resolved
src/proper.erl Outdated Show resolved Hide resolved
src/proper.erl Outdated Show resolved Hide resolved
src/proper.erl Outdated Show resolved Hide resolved
src/proper.erl Outdated Show resolved Hide resolved
src/proper.erl Outdated Show resolved Hide resolved
src/proper.erl Outdated Show resolved Hide resolved
src/proper.erl Outdated
-spec parallel_perform(test(), opts()) -> imm_result().
parallel_perform(Test, #opts{property_type = pure, numtests = NumTests,
numworkers = NumWorkers, strategy_fun = StrategyFun} = Opts) ->
TestsPerWorker = StrategyFun(NumTests, NumWorkers),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to factor out all lines between

    TestsPerWorker = StrategyFun(NumTests, NumWorkers),
    ....
    ok = maybe_stop_cover_server([]),

into a separate function and use it both for this clause and the next one (by passing it [] or NodeList).

This will help both for maintainability and for understanding what is common and what is not.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also need to construct and pass to this function the appropriate SpawnFun, of course.

perform(Passed, NumTests, Test, Opts) ->
Size = size_at_nth_test(Passed, Opts),
put('$size', Size),
perform(Passed, NumTests, 3 * ?MAX_TRIES_FACTOR * NumTests, Test, none, none, Opts).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably add a comment where this magic number 3 comes from... what is its role.

src/proper.erl Outdated Show resolved Hide resolved
src/proper.erl Outdated

%% @private
-spec update_worker_node_ref({node(), {already_running, boolean()}}) -> list(node()).
update_worker_node_ref(Node) ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node is a confusing variable name here...

src/proper.erl Outdated Show resolved Hide resolved
src/proper.erl Outdated
%% @doc Starts multiple (NumNodes) remote nodes.
-spec start_nodes(non_neg_integer()) -> list(node()).
start_nodes(NumNodes) ->
StartNode =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StartNode -> StartNodeFun

Copy link
Collaborator

@kostis kostis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the suggested changes, and then I will merge this.

@pablocostass
Copy link
Contributor Author

@kostis As the commit that had added the get_all_application_env() function also fixed some typos, I rebased to change it to only bring in the latter and also pushed some commits to address your comments.

As I am not really happy with how I did the refactoring of the common code that parallel_perform/2 had, I left that as a different commit. Review it and tell me if you thing something else should be changed.

src/proper.erl Outdated
-spec parallel_perform(test(), opts()) -> imm_result().
parallel_perform(Test, #opts{property_type = pure, numtests = NumTests,
numworkers = NumWorkers, strategy_fun = StrategyFun} = Opts) ->
_ = maybe_start_cover_server([]),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line / call is not needed here; it will be done by spawn_workers_and_get_result. (Or am I missing something?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh no, that was totally my mistake, thanks for pointing it out!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amended the last commit to remove the line.

@pablocostass pablocostass force-pushed the parallel_proper branch 2 times, most recently from da99645 to 0cebf8a Compare May 15, 2021 17:00
@kostis kostis merged commit 6481269 into proper-testing:master May 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants