-
Notifications
You must be signed in to change notification settings - Fork 117
[wip] [feat] Add support for running interdependent tests with the serial execution policy #944
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
[wip] [feat] Add support for running interdependent tests with the serial execution policy #944
Conversation
|
Hello @ChristopherBignamini, Thank you for updating! Cheers! There are no PEP8 issues in this Pull Request!Do see the ReFrame Coding Style Guide Comment last updated at 2019-10-25 14:37:28 UTC |
|
@ChristopherBignamini Unit tests fail. Can you fix them? |
|
I have a two tests that fail (randomly) with Python 3.5 test_kbd_interrupt_in_wait_with_limited_concurrency and test_kbd_interrupt_in_setup_with_concurrency In both cases there are no dependencies but I am calling the toposort method on the test list independently of the presence of dependencies. The resulting test list is reordered wrt the original list specified in the unit test and this cause, sometimes, the failure of the test. I could check if there is any dependency and if not skip the toposort step. Alternatively, I can think of a possible fix for the unit tests. |
reframe/frontend/cli.py
Outdated
| exec_policy.sched_nodelist = options.nodelist | ||
| exec_policy.sched_exclude_nodelist = options.exclude_nodes | ||
| exec_policy.sched_options = options.job_options | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
reframe/frontend/dependency.py
Outdated
| # TODO: create unit test | ||
| deps_count = {} | ||
| for test, deps in graph.items(): | ||
| try: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this try/except is needed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need all the checks that will be executed in a reframe run in the ref_count: let's say that I've completed the task for check C, I need to know if I can delete the corresponding output and I do that when ref_count[c] goes to 0 as I do with checks C dependencies. For this reason I need to include C in the list and I'm doing it in the same way of its dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, this I understand. My point here was about whether the double try/except clause is actually needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I don't understand the first += 1? Sth like the following should be enough I guess:
for test, deps in graph.items():
ref_count.setdefault(test, 0)
for d in deps:
try:
ref_count[test] += 1
except KeyError:
ref_count[test] = 0There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you are right, I don't need the +1.
By the way, I see that you are using the test variable as key in the inner loop instead of using dep, but currently the content of ref_count[c], for a given check c, is the number of checks depending on c. Should I change the ref_count data meaning?
reframe/frontend/dependency.py
Outdated
| deps_count[test] += 1 | ||
| except KeyError: | ||
| deps_count[test] = 0 | ||
| for dep in deps: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| for dep in deps: | |
| for adj in deps: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
reframe/frontend/dependency.py
Outdated
|
|
||
| # TODO: create unit test | ||
| deps_count = {} | ||
| for test, deps in graph.items(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| for test, deps in graph.items(): | |
| for node, deps in graph.items(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
unittests/test_cli.py
Outdated
| self.assertEqual(0, returncode) | ||
|
|
||
| def test_dependency_cli(self): | ||
| self.checkpath = ['unittests/resources/checks/frontend_checks.py'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't put these tests in the frontend_checks.py but rather in a different subdirectory next to the resources/checks. You wouldn't need then to update the loader unit tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can put them inside checks_unlisted/.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I have to put everything in /unittests/resources/checks_unlisted/? I mean the main test file and the single tests? There is a dependencies subfolder, should I put everything in there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| self.stats = None | ||
|
|
||
| # Check dependencies data | ||
| self.dependency_tree = collections.OrderedDict() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why you initialise it like that. Check my comment above on how to redesign it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| dependency_clean_up_task.cleanup( | ||
| not self.keep_stage_files, | ||
| False) | ||
| break |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why all this has to be in the finally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to be sure that the cleanup step is always executed, also in case of exceptions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it should be that? If a test fails, its cleanup phase is not executed by just throwing the exception. Also I think that this part might be simplified if you make the serial policy also a TaskEventListener so that it gets notified when a task finishes. Then you can adjust the reference count in a cleaner way.
| dependency.print_deps(dependency_graph) | ||
| testcases = dependency.toposort(dependency_graph) | ||
| self._policy.dependency_tree = dependency_graph | ||
| self._policy.dependency_count = dependency.create_deps_count( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like very much the dependency_count as a name. I would call it ref_count and have a comment explaining it. In practice, you are doing a reference counting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| self.dependency_count[dep] -= 1 | ||
| if self.dependency_count[dep] == 0: | ||
| # Check if dep has failed before cleaning | ||
| for t in self.stats.tasks(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to associate the reference counter to the RegressionTask so that you avoid this for loop here and the use of stats.
| # Check if dep has failed before cleaning | ||
| for t in self.stats.tasks(): | ||
| if t.testcase == dep and t.failed is False: | ||
| dependency_clean_up_task = RegressionTask(dep) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you create a new "task" here? I'd just call the deferred cleanup() on the original one.
Also cleanup may throw, so it has to be treated correctly. That's why I don't think putting this loop in the finally is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
@ChristopherBignamini Can you fix the conflicts with the master? |
|
|
||
|
|
||
| @rfm.simple_test | ||
| class DependencyT0(rfm.RunOnlyRegressionTest): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't self.sourcesdir = None missing from these tests?
|
Closing this one. Replaced by #1015. |
No description provided.