Repository: HypothesisWorks/hypothesis

Issue:
Postgres Django backend doesn't allow the null character in non-null strings
Generating Model instances with `@given` using a postgres backend db for testing gives me the following error. 

It's making it really hard to test my stuff with Hypothesis because I'll have to manually specify all text fields to use something like `text(alphabet=characters(blacklisted_categories=('Cc', 'Cs')))`

```
Error
Traceback (most recent call last):
  File "/vagrant/savings_champion/products/tests/models.py", line 33, in test_check_bonus
    user=models(User),
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/core.py", line 1001, in wrapped_test
    state.run()
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/core.py", line 725, in run
    runner.run()
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/engine.py", line 393, in run
    self._run()
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/engine.py", line 791, in _run
    self.generate_new_examples()
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/engine.py", line 706, in generate_new_examples
    self.test_function(last_data)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/engine.py", line 153, in test_function
    self._test_function(data)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/core.py", line 691, in evaluate_test_data
    escalate_hypothesis_internal_error()
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/core.py", line 663, in evaluate_test_data
    result = self.execute(data, collect=True)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/core.py", line 578, in execute
    result = self.test_runner(data, run)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/executors.py", line 78, in <lambda>
    lambda: function(data)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/executors.py", line 33, in execute
    return function()
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/executors.py", line 78, in <lambda>
    lambda: function(data)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/core.py", line 552, in run
    args, kwargs = data.draw(self.search_strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 121, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 136, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/core.py", line 180, in do_draw
    return self.base.do_draw(data)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/lazy.py", line 158, in do_draw
    return data.draw(self.wrapped_strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/collections.py", line 60, in do_draw
    data.draw(e) for e in self.element_strategies
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/collections.py", line 56, in newtuple
    return tuple(xs)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/collections.py", line 60, in <genexpr>
    data.draw(e) for e in self.element_strategies
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/strategies.py", line 507, in do_draw
    return self.pack(data.draw(self.mapped_strategy))
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/lazy.py", line 158, in do_draw
    return data.draw(self.wrapped_strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/strategies.py", line 507, in do_draw
    return self.pack(data.draw(self.mapped_strategy))
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/collections.py", line 60, in do_draw
    data.draw(e) for e in self.element_strategies
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/collections.py", line 56, in newtuple
    return tuple(xs)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/collections.py", line 60, in <genexpr>
    data.draw(e) for e in self.element_strategies
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/lazy.py", line 158, in do_draw
    return data.draw(self.wrapped_strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/strategies.py", line 1550, in do_draw
    return f(draw, *args, **kwargs)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/extra/django/models.py", line 161, in _models_impl
    return draw(strat)[0]
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/strategies.py", line 1548, in draw
    return data.draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/strategies.py", line 507, in do_draw
    return self.pack(data.draw(self.mapped_strategy))
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/lazy.py", line 158, in do_draw
    return data.draw(self.wrapped_strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/collections.py", line 60, in do_draw
    data.draw(e) for e in self.element_strategies
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/collections.py", line 56, in newtuple
    return tuple(xs)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/collections.py", line 60, in <genexpr>
    data.draw(e) for e in self.element_strategies
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/strategies.py", line 507, in do_draw
    return self.pack(data.draw(self.mapped_strategy))
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/collections.py", line 60, in do_draw
    data.draw(e) for e in self.element_strategies
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/collections.py", line 56, in newtuple
    return tuple(xs)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/collections.py", line 60, in <genexpr>
    data.draw(e) for e in self.element_strategies
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/strategies.py", line 1550, in do_draw
    return f(draw, *args, **kwargs)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/extra/django/models.py", line 161, in _models_impl
    return draw(strat)[0]
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/strategies.py", line 1548, in draw
    return data.draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 125, in draw
    return self.__draw(strategy)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/internal/conjecture/data.py", line 132, in __draw
    return strategy.do_draw(self)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/searchstrategy/strategies.py", line 507, in do_draw
    return self.pack(data.draw(self.mapped_strategy))
  File "/home/ubuntu/.local/lib/python3.5/site-packages/hypothesis/strategies.py", line 970, in <lambda>
    lambda value: target(*value[0], **value[1])
  File "/home/ubuntu/.local/lib/python3.5/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/django/db/models/query.py", line 487, in get_or_create
    return self.get(**lookup), False
  File "/home/ubuntu/.local/lib/python3.5/site-packages/django/db/models/query.py", line 397, in get
    num = len(clone)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/django/db/models/query.py", line 254, in __len__
    self._fetch_all()
  File "/home/ubuntu/.local/lib/python3.5/site-packages/django/db/models/query.py", line 1179, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/home/ubuntu/.local/lib/python3.5/site-packages/django/db/models/query.py", line 54, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/django/db/models/sql/compiler.py", line 1063, in execute_sql
    cursor.execute(sql, params)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/raven/contrib/django/client.py", line 123, in execute
    return real_execute(self, sql, params)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/django/db/backends/utils.py", line 68, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/django/db/backends/utils.py", line 77, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/ubuntu/.local/lib/python3.5/site-packages/django/db/backends/utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
ValueError: A string literal cannot contain NUL (0x00) characters.
```


Discussion:
I think a possible solution would be to either a) further restrict the set of allowed characters based upon the db backend for the model as this isn't something we can change or b) make it possible to configure this via settings
Would it be enough to name that compound strategy and export it for use in your tests instead? They're first class values, so that'd look something like this:

```python
# helper.py

postgres_text = text(alphabet=characters(blacklisted_categories=('Cc', 'Cs')))

# test.py

from helper import postgres_text

@given(postgres_text, ...)
def test_something(pg_txt, ...):
  ...
```
Unfortunately not, 99% of the code I'm implementing testing on, operate on
the Django model instances directly. This is part of a put tests in first
and then refactor away the cruft exercise so the refactor to make this
possible is defeating the objective.

I'm currently using a variant of this where I pass a named instance like
this into every text field, but with thousands and thousands of cases where
this will be needed it seems like boilerplate that should be taken care of
by the Django module directly as passing a null string to a model in
postgres is always an error.
On Fri., 22 Dec. 2017, 21:50 Jack Firth, <notifications@github.com> wrote:

> Would it be enough to name that compound strategy and export it for use in
> your tests instead? They're first class values, so that'd look something
> like this:
>
> # helper.py
>
> postgres_text = text(alphabet=characters(blacklisted_categories=('Cc', 'Cs')))
> # test.py
> from helper import postgres_text
> @given(postgres_text, ...)def test_something(pg_txt, ...):
>   ...
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/HypothesisWorks/hypothesis-python/issues/1045#issuecomment-353680099>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AARy8tXiQPvgyFqnyiENdpf8Qe9QYZh6ks5tDCQ0gaJpZM4RLE_P>
> .
>

Ah, [we're only a few months late to the party](https://code.djangoproject.com/ticket/28201)!  This is a validation error in Django 2, so I'll blacklist null characters in our default TextField / CharField strategies.

Resolving patch:
diff --git a/src/hypothesis/extra/django/models.py b/src/hypothesis/extra/django/models.py
--- a/src/hypothesis/extra/django/models.py
+++ b/src/hypothesis/extra/django/models.py
@@ -123,8 +123,12 @@ def _get_strategy_for_field(f):
                   'ipv4': ip4_addr_strings(), 'ipv6': ip6_addr_strings()}
         strategy = lookup[f.protocol.lower()]
     elif type(f) in (dm.TextField, dm.CharField):
-        strategy = st.text(min_size=(None if f.blank else 1),
-                           max_size=f.max_length)
+        strategy = st.text(
+            alphabet=st.characters(blacklist_characters=u'\x00',
+                                   blacklist_categories=('Cs',)),
+            min_size=(None if f.blank else 1),
+            max_size=f.max_length,
+        )
     elif type(f) == dm.DecimalField:
         bound = Decimal(10 ** f.max_digits - 1) / (10 ** f.decimal_places)
         strategy = st.decimals(min_value=-bound, max_value=bound,
diff --git a/src/hypothesis/strategies.py b/src/hypothesis/strategies.py
--- a/src/hypothesis/strategies.py
+++ b/src/hypothesis/strategies.py
@@ -790,8 +790,9 @@ def text(
     """Generates values of a unicode text type (unicode on python 2, str on
     python 3) with values drawn from alphabet, which should be an iterable of
     length one strings or a strategy generating such. If it is None it will
-    default to generating the full unicode range. If it is an empty collection
-    this will only generate empty strings.
+    default to generating the full unicode range (excluding surrogate
+    characters). If it is an empty collection this will only generate empty
+    strings.
 
     min_size, max_size and average_size have the usual interpretations.
 


Test patch:
diff --git a/tests/django/toystore/test_given_models.py b/tests/django/toystore/test_given_models.py
--- a/tests/django/toystore/test_given_models.py
+++ b/tests/django/toystore/test_given_models.py
@@ -129,6 +129,11 @@ def test_time_fields(self, x):
         assert isinstance(x.date, dt.date)
         assert isinstance(x.duration, dt.timedelta)
 
+    @given(models(Company))
+    def test_no_null_in_charfield(self, x):
+        # regression test for #1045.  Company just has a convenient CharField.
+        assert u'\x00' not in x.name
+
 
 class TestsNeedingRollback(TransactionTestCase):