Skip to content

Actually allow parallel test execution under CMake.#8484

Merged
scheibelp merged 2 commits intospack:developfrom
greenc-FNAL:feature/cmake-parallel-test
Sep 4, 2018
Merged

Actually allow parallel test execution under CMake.#8484
scheibelp merged 2 commits intospack:developfrom
greenc-FNAL:feature/cmake-parallel-test

Conversation

@greenc-FNAL
Copy link
Copy Markdown
Member

@greenc-FNAL greenc-FNAL commented Jun 14, 2018

Closes #4503 (implementing the spirit of the request—parallelism for CMake tests—rather than the spirit—using the ctest command itself).

The existing implementation of the test target when building a CMake package using the Makefiles generator invokes make test, which does not honor the the -j option for parallelism. One can either set an environment variable (difficult to handle the overrides currently available) or one can invoke ctest instead of make test. This implementation (now) does the latter former in order to accommodate packages (see below) for which make test runs a set of tests which is distinct from that run by ctest.

[Description has been edited from the original due to the evolution of implementation to satisfy issues raised), with some phrases struck through to suggest the original meaning. See the editing history for full details.

Copy link
Copy Markdown
Member

@davydden davydden left a comment

Choose a reason for hiding this comment

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

I am sorry, but

or one can invoke ctest instead of make test.

that will break dealii, we have make test which is separate from ctest. The former are quick tests, whereas ctest will run ~10k unit tests.

We need some mechanism to switch between the two cases. Could be a flag in Package class with default being what you suggest.

@greenc-FNAL
Copy link
Copy Markdown
Member Author

@davydden We can go the other route (setting an environment variable), but in my initial analysis was unable to find a way to ensure that the environment variable is set to the correct parallelism value, taking into account the various ways it could be overridden. I can take another look.

@adamjstewart adamjstewart added tests General test capability(ies) cmake labels Jun 14, 2018
@adamjstewart
Copy link
Copy Markdown
Member

@lgarren and @mathstuf would be interested in this PR.

inspect.getmodule(self).make(target)

def _if_ninja_target_execute(self, target):
def _if_ninja_target(self, target):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In general I sympathize with @davydden here. Also a minor naming comment: I think the splitted functions should be named something like _make_has_target and ninja_has_target instead of _if_ninja_target. That in my opinion sounds better when reading the code. For instance:

if self._make_has_target('test'):
    inspect.getmodule(self).ctest()

versus:

if self._if_make_target('test'):
    inspect.getmodule(self).ctest()

dump_packages(self.spec, packages_dir)

def _if_make_target_execute(self, target):
def _if_make_target(self, target):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These changes would make #8223 much easier to unit test.

@greenc-FNAL greenc-FNAL force-pushed the feature/cmake-parallel-test branch from 9502108 to d5133fc Compare June 15, 2018 20:06
@greenc-FNAL
Copy link
Copy Markdown
Member Author

greenc-FNAL commented Jun 15, 2018

@davydden @adamjstewart @alalazo Per Denis' request, I have implemented more flexibility in the MakeExecutable system to allow an invocation of a MakeExecutable (e.g. via _if_make_target_execute) to specify an alternative job argument to pass to the executable and/or the name of an environment variable to add to the environment for the particular invocation.

Note that this new implementation invalidates previous comments regarding the new functions _if_make_target(), etc., since they are no longer needed specifically to solve this problem. I would be happy to submit suitably-renamed versions of these functions as a separate pull request since it has been indicated that they would be useful elsewhere.

if self.generator == 'Unix Makefiles':
self._if_make_target_execute('test')
self._if_make_target_execute('test',
jobs_env='CTEST_PARALLEL_LEVEL')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

how does one use it? Could you please add a sentence in class documentation?

there is a dedicated PR on documentation for build systems #5015 but it is not merged yet.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@davydden If you're talking specifically about the cmake.py change you noted above, "one" doesn't "use" it. It's a change in the implementation to provide the parallelism that one could reasonably expect for the test target in the event that it is CMake-generated (i.e. invokes ctest). This implementation uses the expanded functionality of MakeExecutable, which is actually explained in the class documentation therefor.
In your case, where the test target is hand-provided by your package rather than generated by CMake, there will be no effect unless (of course) your target invokes ctest.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@davydden I'm happy that it does what it set out to do. If everyone else thinks it's a reasonable implementation, I'm happy for it to be merged.

Copy link
Copy Markdown
Member

@davydden davydden left a comment

Choose a reason for hiding this comment

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

presumably the default behaviour stays the same as before?

@greenc-FNAL
Copy link
Copy Markdown
Member Author

@adamjstewart @davydden @alalazo Currently trying to understand the Python 2.6 failure. Insights welcome ...

@greenc-FNAL
Copy link
Copy Markdown
Member Author

greenc-FNAL commented Jun 15, 2018

@davydden The default behavior is that CMakePackage tests via a CMake-generated test target are now parallel, just like they would be if they were MakePackage tests (or a manually-created test target generated with CMake). If a package writer needs to serialize a build, they should already be doing that the "normal" way (parallel = False). This is a change from the previous behavior, since I inferred that the previous behavior was unintentional (-jXX was always passed if parallelism was not selected using the usual Spack method, but the CMake-generated test target ignored it). CMakePackage's CMake-generated test target was the odd one out with respect to MakePackage and every other CMake target (whether generated for make or ninja).

@greenc-FNAL greenc-FNAL reopened this Jun 18, 2018
@greenc-FNAL greenc-FNAL force-pushed the feature/cmake-parallel-test branch from 4f503d6 to 0948ec5 Compare June 18, 2018 18:28
@greenc-FNAL
Copy link
Copy Markdown
Member Author

As of right now the Python 2.6 issue has been fixed and the only unit test failure is transient (==> Error: ChecksumError: md5 checksum failed for /home/travis/build/spack/spack/var/spack/stage/mpich-3.2.1-ti52nxv6jsu76blexdrb6jv2h7xa3iof/mpich-3.2.1.tar.gz) @davydden does this formulation work for you now?

@davydden
Copy link
Copy Markdown
Member

davydden commented Jun 18, 2018

@davydden does this formulation work for you now?

I am AFK for 3 weeks, but I don’t mind merging this if you think it works

@greenc-FNAL greenc-FNAL force-pushed the feature/cmake-parallel-test branch from 0948ec5 to 9de1797 Compare June 18, 2018 21:47
@greenc-FNAL
Copy link
Copy Markdown
Member Author

@adamjstewart Looks like someone with access to travis might need to delete the cached mpich tar file for the 3.6 package build.

@adamjstewart
Copy link
Copy Markdown
Member

Unfortunately I don't have access (or I don't know how to clear the cache). @tgamblin might. Rebasing also seems to help.

@greenc-FNAL greenc-FNAL force-pushed the feature/cmake-parallel-test branch from 9de1797 to 3b6b49b Compare June 28, 2018 15:13
greenc-FNAL added a commit to greenc-FNAL/spack that referenced this pull request Jun 28, 2018
Split off from _if_make_target_execute() and _if_ninja_target_execute(), respectively.

Intended to facilitate testing of other functionality, per spack#8484 (comment).

spack#8484 (comment) was considered, but this word order maintains consistency with existing functions.
greenc-FNAL added a commit to greenc-FNAL/spack that referenced this pull request Jul 23, 2018
Split off from _if_make_target_execute() and _if_ninja_target_execute(), respectively.

Intended to facilitate testing of other functionality, per spack#8484 (comment).

spack#8484 (comment) was considered, but this word order maintains consistency with existing functions.
@greenc-FNAL greenc-FNAL force-pushed the feature/cmake-parallel-test branch from 3b6b49b to 23f7ef2 Compare July 23, 2018 18:36
@greenc-FNAL
Copy link
Copy Markdown
Member Author

Rebase against spack:develop to incorporate @adamjstewart's merged PR #8223.

@greenc-FNAL greenc-FNAL force-pushed the feature/cmake-parallel-test branch from 23f7ef2 to 588e069 Compare August 20, 2018 20:33
@greenc-FNAL
Copy link
Copy Markdown
Member Author

@davydden Is this PR ready for merge? I'd be grateful if you had the time to look this over one last time and merge if you're happy.

@davydden
Copy link
Copy Markdown
Member

I approved it but I don’t have merge rights. You need to ping others

@scheibelp
Copy link
Copy Markdown
Member

Hi @chissg, I can merge this but there is now a conflict (probably because I merged #8819). If that gets resolved today I can check back on it tomorrow morning.

@greenc-FNAL greenc-FNAL force-pushed the feature/cmake-parallel-test branch from 588e069 to 9e7b5e9 Compare August 20, 2018 22:23
@greenc-FNAL
Copy link
Copy Markdown
Member Author

I believe I have resolved the conflicts and rebased on develop including #8819 (pending the outcome of the tests, of course).

if parallel:
jobs = "-j%d" % self.jobs
args = (jobs,) + args
jobs = kwargs.pop('jobs_arg', '-j{0}')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO the following would be more straightforward (I don't see anything setting the new jobs_arg variable so IMO it isn't needed):

jobs_arg = 'j{0}'.format(self.jobs)
args = (jobs_arg,) + args
if jobs_env:
  ...

(I also think this avoids setting multiple jobs arguments, which may be valid and appears to be handled here, but I'm not aware of any system that needs it so I'd prefer to avoid the complexity)

Does it make sense to avoid adding a -jN argument when jobs_env is set?

I think it's worth adding a note that you are using kwargs.pop because jobs_env is intended for use by this function specifically and that the remaining kwargs are to be passed to the make invocation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

jobs_arg is there for a little more flexibility for new make-like execs, for wrappers using long-form options (ctest has a --parallel alias, for example) or separating the N from the -j, and for the case where one actually does want to avoid -jN when job_env is set. Note that in the case jobs_env was put in place to address (make test for CMake projects) we can't assume for the general CMake case because any given project can provide its own make test target which may or may not honor -jN. @davydden mentioned dealii as a package for which this is done.

I can certainly add a note about the use of kwargs.pop(). I'll defer to you on whether I should go for simplicity or flexibility on jobs_arg -- let me know what you think after my explanation.

Copy link
Copy Markdown
Member

@adamjstewart adamjstewart Aug 21, 2018

Choose a reason for hiding this comment

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

Is there any known case where we can't just supply a flag? Like could we do make -j 4 instead of make -j4? That would make things a bit simpler in my mind. I suppose there could be a flag that means "use all cores" and no option to supply a specific number. But I think that would break this anyway? That wouldn't need to be a MakeExecutable anyway.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@adamjstewart The generated test target for CMake projects requires the CTEST_PARALLEL_LEVEL environment variable to be set for parallelism (ignoring the -j option to make), which is the whole reason for this PR. Different make-like applications handle non-specific parallelism differently anyway: ninja defaults to core-level parallelism without any -j, whereas make omits the number for "infinite" parallelism. It's messy. I could always go back to the old system of assuming -j{0} in the absence of use cases, at the cost of flexibility for the future. Is that the consensus?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Personally, I would be fine with the previous behavior, the proposed behavior, or the behavior I'm suggesting. To be clear, what I'm suggesting is replacing jobs_arg='-j{0}' with jobs_flag='-j'. The only difference from our current behavior would be adding a space after the -j.

But if you want to avoid debate and just get this merged, I would avoid making changes to the UI that aren't absolutely necessary until we find a use case for those changes. Although @alalazo and I are generally fans of making things as generic as possible...

Copy link
Copy Markdown
Member

@scheibelp scheibelp Aug 22, 2018

Choose a reason for hiding this comment

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

for wrappers using long-form options (ctest has a --parallel alias, for example)

Like a wrapper around ctest, called ctest, which only accepts --parallel?

separating the N from the -j

Do any systems require this?

and for the case where one actually does want to avoid -jN when job_env is set

How does one accomplish that? e.g. if jobs_arg=None the next line the line args = tuple([j.format(self.jobs) for j in jobs]) + args fails. The only way I see to do that now is to pass an empty iterable container, which IMO is not straightforward.

Also, I didn't see any of those cases explaining the handling of jobs_arg as a tuple (other than my inference on the last one)

Overall I'd be fine if jobs_arg was a single argument like jobs_env.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have removed the currently-unneeded flexibility, leaving us with the minimum changes required to provide parallel test execution under CMake.

@greenc-FNAL greenc-FNAL force-pushed the feature/cmake-parallel-test branch from 9e7b5e9 to 54664e1 Compare August 23, 2018 16:45
@greenc-FNAL
Copy link
Copy Markdown
Member Author

Currently-unneeded flexibility has been removed.

Copy link
Copy Markdown
Member

@scheibelp scheibelp left a comment

Choose a reason for hiding this comment

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

I have a request to clarify the docstring and a couple questions/requests about handling of env/make_env

parallelism options on a per-invocation basis. Specifying
'parallel' to the call will override whatever the package's
global setting is, so you can either default to true or false and
override particular calls. Specifying 'jobs_env' to a particular
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO the following is clearer:

If specified, jobs_env names the environment variable which should be used to control the level of parallelism

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sounds reasonable, but I wanted to avoid giving the impression that the -j{} behavior would be turned off. How about:

If specified, jobs_env names an environment variable to be set to the number of parallel jobs used.
?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That sounds good to me, although IMO if you want to make sure the user knows that jobs_env doesn't turn off the -j argument, that stating it explicitly would benefit either explanation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@scheibelp I think the latest commit resolves all your issues. Please let me know whether you concur.

if jobs_env:
# Caller wants us to set an environment variable to
# control the parallelism.
make_env = kwargs.pop('env', []).copy()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks strange to me comparing to the next line: it looks like make_env can be a list. If jobs_env is a string, it looks like the next line will fail (attempt to index a list with a string). Should the default be {}?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The default should be {}, correct. Apparently the existing tests don't attempt to run the tests for any CMake packages with parallelism enabled.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@scheibelp Should I try to invoke a package build somehow that runs the package's tests? I'm not at all familiar with this part of the testing system.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For this sort of error (and to just cover the lines of code) you could add a simple test to the make_executable python test module (where jobs_env is set).

# control the parallelism.
make_env = kwargs.pop('env', []).copy()
make_env[jobs_env] = str(self.jobs)
kwargs['env'] = make_env
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In Executable.__call__, if env is set at all (including if it's an empty container), then Executable will use it. Overall the effect for MakeExecutable is that if the user:

  • sets job_env
  • does not set env

that the Executable.__call__ will run with an empty environment, which changes the semantics (normally if you don't set env, it uses the current environment). Was that what you were going for?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@scheibelp Genuinely not sure what to do here. In executable.py:

   env_arg = kwargs.get('env', None)
   if env_arg is None:
       env = os.environ.copy()
       env.update(self.default_env)
   else:
       env = self.default_env.copy()
       env.update(env_arg)

In other words, if the environment is not specified, we overlay our defaults on the current environment; otherwise, we overlay our specified environment on the defaults. It would seem that the desirable behavior in the case of specifying jobs_env would be, "use the environment you were always going to use, but with this added. In the case where env was not specified this would have to be, "The current environment with default_env overlaid on top and then jobs_env added to it." This would not, of course, be robust against changes to executable.py. Thoughts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would seem that the desirable behavior in the case of specifying jobs_env would be, "use the environment you were always going to use, but with this added["]

IMO that is the most sensible behavior (e.g. to keep CC etc.)

but with this added. In the case where env was not specified this would have to be, "The current environment with default_env overlaid on top and then jobs_env added to it."

I think I see what you mean: to get the semantics of default_env, you would need to give it priority in MakeExecutable when env was not specified, if Executable were to change that later, MakeExecutable may get left behind.

I don't think you have to deal with that here. However, if desired, you could make it more robust by adding an extra_environment argument to Executable.__call__ that doesn't define the whole environment but rather overlays the specified arguments on top. In that case MakeExecutable would be simplified.

@greenc-FNAL greenc-FNAL force-pushed the feature/cmake-parallel-test branch from 54664e1 to e531bf7 Compare August 30, 2018 16:09

Keyword Arguments:
env (dict): The environment to run the executable with
env_dump (dict): Dict to be set to the environment actually used
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A couple requests:

  • I think this should be dump_env (there are no references to env_dump, but there are to dump_env)
  • This appears to be used only for testing. IMO it's worth clarifying that in the explanation and also adding a _ to the prefix, i.e. _dump_env.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@scheibelp I wasn't sure what the policy on testing-only was, but I can do that.

@greenc-FNAL greenc-FNAL force-pushed the feature/cmake-parallel-test branch from e531bf7 to 39a7111 Compare August 30, 2018 20:33
@scheibelp
Copy link
Copy Markdown
Member

LGTM: but can you update the PR description
#8484 (comment) given that the implementation has changed since (so that if someone comes back to this they can understand what was changed)? IMO it would be good to keep the original description but either "crossed out" (with ~ ~) or by prefacing with a title like "OLD DESCRIPTION".

@greenc-FNAL
Copy link
Copy Markdown
Member Author

@scheibelp Description updated.

@scheibelp scheibelp merged commit dd27662 into spack:develop Sep 4, 2018
@scheibelp
Copy link
Copy Markdown
Member

Thanks!

anderson2981 pushed a commit to anderson2981/spack that referenced this pull request Sep 7, 2018
* Add 'extra_env' argument to Executable.__call__: this will be added
  to the environment but does not affect whether the current
  environment is reused. If 'env' is not set, then the current
  environment is copied and the variables from 'extra_env' are added
  to it.
* MakeExecutable can take a 'jobs_env' parameter that specifies the
  name of an environment variable used to set the level of parallelism.
  This is added to 'extra_env' (so does not affect whether the current
  environment is reused).
* CMake-based Spack packages set 'jobs_env' when executing the 'test'
  target for make and ninja (which does not use -j)
@greenc-FNAL greenc-FNAL deleted the feature/cmake-parallel-test branch September 17, 2018 20:17
ptbremer pushed a commit to ptbremer/spack that referenced this pull request Oct 12, 2018
* Add 'extra_env' argument to Executable.__call__: this will be added
  to the environment but does not affect whether the current
  environment is reused. If 'env' is not set, then the current
  environment is copied and the variables from 'extra_env' are added
  to it.
* MakeExecutable can take a 'jobs_env' parameter that specifies the
  name of an environment variable used to set the level of parallelism.
  This is added to 'extra_env' (so does not affect whether the current
  environment is reused).
* CMake-based Spack packages set 'jobs_env' when executing the 'test'
  target for make and ninja (which does not use -j)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests General test capability(ies)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants