On 21 December 2011 11:46, Kenneth Graunke <span dir="ltr">&lt;<a href="mailto:kenneth@whitecape.org">kenneth@whitecape.org</a>&gt;</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>
&lt;interrupt the test run somehow&gt;<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=&lt;skip|retry&gt; option<br>
to automatically skip or retry any tests that didn&#39;t finish.<br>
Conveniently, these are exactly the ones that needed JSON repair.<br>
<br>
Signed-off-by: Kenneth Graunke &lt;<a href="mailto:kenneth@whitecape.org">kenneth@whitecape.org</a>&gt;<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>
+                       &#39;options&#39;,<br>
                        &#39;name&#39;,<br>
                        &#39;tests&#39;,<br>
                        &#39;glxinfo&#39;,<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 = &quot;&quot;&quot;\<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 &#39;glean&#39; group or whose path contains<br>
                 the substring &#39;tex&#39;<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>
 &quot;&quot;&quot;<br>
        print USAGE % {&#39;progName&#39;: sys.argv[0]}<br>
        sys.exit(1)<br>
@@ -71,25 +78,33 @@ def main():<br>
                option_list = [<br>
                         &quot;help&quot;,<br>
                         &quot;dry-run&quot;,<br>
+                        &quot;resume&quot;,<br>
                         &quot;tests=&quot;,<br>
                         &quot;name=&quot;,<br>
                         &quot;exclude-tests=&quot;,<br>
                         &quot;concurrent=&quot;,<br>
                         ]<br>
-               options, args = getopt(sys.argv[1:], &quot;hdt:n:x:c:&quot;, option_list)<br>
+               options, args = getopt(sys.argv[1:], &quot;hdrt:n:x:c:&quot;, option_list)<br>
        except GetoptError:<br>
                usage()<br>
<br>
        OptionName = &#39;&#39;<br>
+       OptionResume = False<br>
+       test_filter = []<br>
+       exclude_filter = []<br>
<br>
        for name, value in options:<br>
                if name in (&#39;-h&#39;, &#39;--help&#39;):<br>
                        usage()<br>
                elif name in (&#39;-d&#39;, &#39;--dry-run&#39;):<br>
                        env.execute = False<br>
+               elif name in (&#39;-r&#39;, &#39;--resume&#39;):<br>
+                       OptionResume = True<br>
                elif name in (&#39;-t&#39;, &#39;--tests&#39;):<br>
+                       test_filter[:0] = [value]<br>
                        env.filter[:0] = [re.compile(value)]<br>
                elif name in (&#39;-x&#39;, &#39;--exclude-tests&#39;):<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&#39;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 (&#39;-n&#39;, &#39;--name&#39;):<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 &quot;-r is not compatible with -t or -n.&quot;<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[&#39;profile&#39;]<br>
+               for value in old_results.options[&#39;filter&#39;]:<br>
+                       env.filter[:0] = [re.compile(value)]<br>
+               for value in old_results.options[&#39;exclude_filter&#39;]:<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&#39;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(&#39;options&#39;)<br>
+       json_writer.open_dict()<br>
+       json_writer.write_dict_item(&#39;profile&#39;, profileFilename)<br>
+       json_writer.write_dict_key(&#39;filter&#39;)<br>
+       result_file.write(json.dumps(test_filter))<br>
+       json_writer.write_dict_key(&#39;exclude_filter&#39;)<br>
+       result_file.write(json.dumps(exclude_filter))<br>
+       json_writer.close_dict()<br>
+<br>
        json_writer.write_dict_item(&#39;name&#39;, <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(&#39;tests&#39;)<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 &quot;key&quot; 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 &lt;<a href="mailto:stereotype441@gmail.com">stereotype441@gmail.com</a>&gt;<br>