On 21 December 2011 11:46, Kenneth Graunke <span dir="ltr"><<a href="mailto:kenneth@whitecape.org">kenneth@whitecape.org</a>></span> wrote:<br><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
GPUs like to hang, especially when barraged with lots of mean Piglit<br>
tests. Usually this results in the poor developer having to figure out<br>
what test hung, blacklist it via -x, and start the whole test run over.<br>
This can waste a huge amount of time, especially when many tests hang.<br>
<br>
This patch adds the ability to resume a Piglit run where you left off.<br>
<br>
The workflow is:<br>
$ piglit-run.py -t foo tests/quick.tests results/foobar-1<br>
<interrupt the test run somehow><br>
$ piglit-run.py -r -x bad-test results/foobar-1<br>
<br>
To accomplish this, piglit-run.py now stores the test profile<br>
(quick.tests) and -t/-x options in the JSON results file so it can tell<br>
what you were originally running. When run with the --resume option, it<br>
re-reads the results file to obtain this information (repairing broken<br>
JSON if necessary), rewrites the existing results, and runs any<br>
remaining tests.<br>
<br>
Suggested future work is to add an --incomplete=<skip|retry> option<br>
to automatically skip or retry any tests that didn't finish.<br>
Conveniently, these are exactly the ones that needed JSON repair.<br>
<br>
Signed-off-by: Kenneth Graunke <<a href="mailto:kenneth@whitecape.org">kenneth@whitecape.org</a>><br>
---<br>
framework/core.py | 5 ++++<br>
piglit-run.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++-----<br>
2 files changed, 59 insertions(+), 6 deletions(-)<br>
<br>
diff --git a/framework/core.py b/framework/core.py<br>
index e64edbd..ea24ea3 100644<br>
--- a/framework/core.py<br>
+++ b/framework/core.py<br>
@@ -266,6 +266,7 @@ class ResultFileInOldFormatError(Exception):<br>
class TestrunResult:<br>
def __init__(self):<br>
self.serialized_keys = [<br>
+ 'options',<br>
'name',<br>
'tests',<br>
'glxinfo',<br>
@@ -392,6 +393,7 @@ class Environment:<br>
self.execute = True<br>
self.filter = []<br>
self.exclude_filter = []<br>
+ self.exclude_tests = []<br></blockquote><div><br>Since this is potentially going to have thousands of items in it, it would probably be better to make it a set than a list (self.exclude_tests = set()).<br>
</div><blockquote class="gmail_quote" style="margin:0pt 0pt 0pt 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
def run(self, command):<br>
try:<br>
@@ -457,6 +459,9 @@ class Test:<br>
if True in map(lambda f: f.search(path) != None, env.exclude_filter):<br>
return None<br>
<br>
+ if path in env.exclude_tests:<br>
+ return None<br>
+<br>
def status(msg):<br>
log(msg = msg, channel = path)<br>
<br>
diff --git a/piglit-run.py b/piglit-run.py<br>
index 39e2ee0..5f50b3d 100755<br>
--- a/piglit-run.py<br>
+++ b/piglit-run.py<br>
@@ -28,6 +28,7 @@ import re<br>
import sys, os<br>
import time<br>
import traceback<br>
+import json<br>
<br>
sys.path.append(path.dirname(path.realpath(sys.argv[0])))<br>
import framework.core as core<br>
@@ -39,10 +40,12 @@ from framework.threads import synchronized_self<br>
def usage():<br>
USAGE = """\<br>
Usage: %(progName)s [options] [profile.tests] [results]<br>
+ %(progName)s [options] -r [results]<br>
<br>
Options:<br>
-h, --help Show this message<br>
-d, --dry-run Do not execute the tests<br>
+ -r, --resume Resume an interrupted test run.<br>
-t regexp, --tests=regexp Run only matching tests (can be used more<br>
than once)<br>
-x regexp, --exclude-tests=regexp Excludey matching tests (can be used<br>
@@ -60,6 +63,10 @@ Example:<br>
%(progName)s -t ^glean/ -t tex tests/all.tests results/all<br>
Run all tests that are in the 'glean' group or whose path contains<br>
the substring 'tex'<br>
+<br>
+ %(progName)s -r -x bad-test results/all<br>
+ Resume an interrupted test run whose results are stored in the<br>
+ directory results/all, skipping bad-test.<br>
"""<br>
print USAGE % {'progName': sys.argv[0]}<br>
sys.exit(1)<br>
@@ -71,25 +78,33 @@ def main():<br>
option_list = [<br>
"help",<br>
"dry-run",<br>
+ "resume",<br>
"tests=",<br>
"name=",<br>
"exclude-tests=",<br>
"concurrent=",<br>
]<br>
- options, args = getopt(sys.argv[1:], "hdt:n:x:c:", option_list)<br>
+ options, args = getopt(sys.argv[1:], "hdrt:n:x:c:", option_list)<br>
except GetoptError:<br>
usage()<br>
<br>
OptionName = ''<br>
+ OptionResume = False<br>
+ test_filter = []<br>
+ exclude_filter = []<br>
<br>
for name, value in options:<br>
if name in ('-h', '--help'):<br>
usage()<br>
elif name in ('-d', '--dry-run'):<br>
env.execute = False<br>
+ elif name in ('-r', '--resume'):<br>
+ OptionResume = True<br>
elif name in ('-t', '--tests'):<br>
+ test_filter[:0] = [value]<br>
env.filter[:0] = [re.compile(value)]<br>
elif name in ('-x', '--exclude-tests'):<br>
+ exclude_filter[:0] = [value]<br>
env.exclude_filter[:0] = [re.compile(value)]<br></blockquote><div><br>The standard Python way to add an item to a list is<br><br>my_list.append(item)<br><br>rather than<br><br>my_list[:0] = [item]<br>
<br>The latter has two problems: (1) inefficiency (it forces the list to be copied), and (2) it reverses the list order.<br><br>As long as we're changing this code, can we build up the lists using append?<br> </div><blockquote class="gmail_quote" style="margin:0pt 0pt 0pt 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
elif name in ('-n', '--name'):<br>
OptionName = value<br>
@@ -101,11 +116,26 @@ def main():<br>
else:<br>
usage()<br>
<br>
- if len(args) != 2:<br>
- usage()<br>
+ if OptionResume:<br>
+ if test_filter or OptionName:<br>
+ print "-r is not compatible with -t or -n."<br>
+ usage()<br>
+ if len(args) != 1:<br>
+ usage()<br>
+ resultsDir = args[0]<br>
<br>
- profileFilename = args[0]<br>
- resultsDir = args[1]<br>
+ # Load settings from the old results JSON<br>
+ old_results = core.loadTestResults(resultsDir)<br>
+ profileFilename = old_results.options['profile']<br>
+ for value in old_results.options['filter']:<br>
+ env.filter[:0] = [re.compile(value)]<br>
+ for value in old_results.options['exclude_filter']:<br>
+ env.exclude_filter[:0] = [re.compile(value)]<br></blockquote><div><br>I think these two loops need to also populate test_filter and exclude_filter. Otherwise, if a test is resumed twice, the second resume will lose the filter information. Also my above comments about append apply here too.<br>
<br></div><blockquote class="gmail_quote" style="margin:0pt 0pt 0pt 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+ else:<br>
+ if len(args) != 2:<br>
+ usage()<br>
+ profileFilename = args[0]<br>
+ resultsDir = args[1]<br>
<br>
# Change to the piglit's path<br>
piglit_dir = path.dirname(path.realpath(sys.argv[0]))<br>
@@ -127,15 +157,33 @@ def main():<br>
json_writer = core.JSONWriter(result_file)<br>
json_writer.open_dict()<br>
<br>
+ # Write out command line options for use in resuming.<br>
+ json_writer.write_dict_key('options')<br>
+ json_writer.open_dict()<br>
+ json_writer.write_dict_item('profile', profileFilename)<br>
+ json_writer.write_dict_key('filter')<br>
+ result_file.write(json.dumps(test_filter))<br>
+ json_writer.write_dict_key('exclude_filter')<br>
+ result_file.write(json.dumps(exclude_filter))<br>
+ json_writer.close_dict()<br>
+<br>
json_writer.write_dict_item('name', <a href="http://results.name" target="_blank">results.name</a>)<br>
for (key, value) in env.collectData().items():<br>
json_writer.write_dict_item(key, value)<br>
<br>
profile = core.loadTestProfile(profileFilename, resultsDir)<br>
- time_start = time.time()<br>
<br>
json_writer.write_dict_key('tests')<br>
json_writer.open_dict()<br>
+ # If resuming an interrupted test run, re-write all of the existing<br>
+ # results since we clobbered the results file. Also, exclude them<br>
+ # from being run again.<br>
+ if OptionResume:<br>
+ for (key, value) in old_results.tests.items():<br>
+ json_writer.write_dict_item(key, value)<br>
+ env.exclude_tests[:0] = [key]<br></blockquote><div><br>Assuming we change env.exclude_tests to a set, the proper way to add "key" to it is<br><br>env.exclude_tests.add(key)<br> </div><blockquote class="gmail_quote" style="margin:0pt 0pt 0pt 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+<br>
+ time_start = time.time()<br>
profile.run(env, json_writer)<br>
json_writer.close_dict()<br>
<span class="HOEnZb"><font color="#888888"><br>
--<br>
1.7.7.3<br>
<br>
_______________________________________________<br>
Piglit mailing list<br>
<a href="mailto:Piglit@lists.freedesktop.org">Piglit@lists.freedesktop.org</a><br>
<a href="http://lists.freedesktop.org/mailman/listinfo/piglit" target="_blank">http://lists.freedesktop.org/mailman/listinfo/piglit</a><br>
</font></span></blockquote></div><br>Other than the above comments, this patch series is:<br><br>Reviewed-by: Paul Berry <<a href="mailto:stereotype441@gmail.com">stereotype441@gmail.com</a>><br>