Repository: HypothesisWorks/hypothesis

Issue:
`debug_data` takes a lot of time doing nothing useful
When I profile a hypothesis run (with use_coverage off), I see that 11% of the time is taken by `hypothesis.internal.conjecture.engine:398:debug_data`. Looking more closely, this function takes time generating data for the debugging log, even when that log is turned off. Let's please make this more lazy.

A very simple (but not elegant) solution is:

```diff
--- hypothesis/internal/conjecture/engine.py
+++ hypothesis/internal/conjecture/engine.py
@@ -26,6 +26,7 @@ from collections import defaultdict
 import attr
 
 from hypothesis import settings as Settings
+from hypothesis import Verbosity
 from hypothesis import Phase, HealthCheck
 from hypothesis.reporting import debug_report
 from hypothesis.internal.compat import EMPTY_BYTES, Counter, ceil, \
@@ -396,6 +397,8 @@ class ConjectureRunner(object):
             debug_report(message)
 
     def debug_data(self, data):
+        if self.settings.verbosity < Verbosity.debug:
+          return
         buffer_parts = [u"["]
         for i, (u, v) in enumerate(data.blocks):
             if i > 0:
```


Discussion:
Hmm.  I've just traced this through, and we *could* pass a thunk to make string construction lazy.  I tend to think though that with only two calls it's more elegant just to check verbosity before calling the method.  I'll open a pull.
Thanks! I'm eagerly awaiting.

Resolving patch:
diff --git a/src/hypothesis/internal/conjecture/engine.py b/src/hypothesis/internal/conjecture/engine.py
--- a/src/hypothesis/internal/conjecture/engine.py
+++ b/src/hypothesis/internal/conjecture/engine.py
@@ -26,7 +26,7 @@
 import attr
 
 from hypothesis import settings as Settings
-from hypothesis import Phase, HealthCheck
+from hypothesis import Phase, Verbosity, HealthCheck
 from hypothesis.reporting import debug_report
 from hypothesis.internal.compat import Counter, ceil, hbytes, hrange, \
     int_to_text, int_to_bytes, benchmark_time, int_from_bytes, \
@@ -165,7 +165,8 @@ def test_function(self, data):
 
         self.target_selector.add(data)
 
-        self.debug_data(data)
+        if self.settings.verbosity >= Verbosity.debug:
+            self.debug_data(data)
 
         tags = frozenset(data.tags)
         data.tags = self.tag_intern_table.setdefault(tags, tags)
@@ -393,12 +394,12 @@ def run(self):
                 self._run()
             except RunIsComplete:
                 pass
-            for v in self.interesting_examples.values():
-                self.debug_data(v)
-            self.debug(
-                u'Run complete after %d examples (%d valid) and %d shrinks' % (
-                    self.call_count, self.valid_examples, self.shrinks,
-                ))
+            if self.settings.verbosity >= Verbosity.debug:
+                for v in self.interesting_examples.values():
+                    self.debug_data(v)
+                self.debug(
+                    u'Run complete after %d examples (%d valid) and %d shrinks'
+                    % (self.call_count, self.valid_examples, self.shrinks))
 
     def _new_mutator(self):
         target_data = [None]


Test patch:
diff --git a/tests/cover/test_conjecture_engine.py b/tests/cover/test_conjecture_engine.py
--- a/tests/cover/test_conjecture_engine.py
+++ b/tests/cover/test_conjecture_engine.py
@@ -17,13 +17,14 @@
 
 from __future__ import division, print_function, absolute_import
 
+import re
 import time
 from random import seed as seed_random
 from random import Random
 
 import pytest
 
-from hypothesis import Phase, HealthCheck, settings, unlimited
+from hypothesis import Phase, Verbosity, HealthCheck, settings, unlimited
 from hypothesis.errors import FailedHealthCheck
 from tests.common.utils import all_values, checks_deprecated_behaviour
 from hypothesis.database import ExampleDatabase, InMemoryExampleDatabase
@@ -670,6 +671,19 @@ def f(data):
     assert runner.event_call_counts[str(event)] == runner.call_count > 0
 
 
+@pytest.mark.parametrize('count', [1, 3])
+def test_debug_data(capsys, count):
+    with settings(verbosity=Verbosity.debug):
+        @run_to_buffer
+        def f(data):
+            for _ in hrange(count):
+                data.draw_bytes(1)
+            if sum(data.buffer) > 10:
+                data.mark_interesting()
+    out, _ = capsys.readouterr()
+    assert re.match(u'\\d+ bytes \\[.*\\] -> ', out)
+
+
 def test_zeroes_bytes_above_bound():
     def f(data):
         if data.draw_bits(1):