Repository: HypothesisWorks/hypothesis

Issue:
Add Python 3.8 release candidate to our CI
[We're in the final beta period and moving to release candidate soon](https://discuss.python.org/t/action-required-python-3-8-0b4-available-for-testing/2231), and CPython devs have asked the community to test against the beta while there's still time to make code changes.  So it would be nice to add it to our CI!

Out of scope for this issue, [there are a bunch of `typing` changes](https://docs.python.org/3.8/whatsnew/3.8.html#typing) which I'll need to handle in `st.from_type()`.  More seriously, positional-only parameters will demand some overhaul of our decorator logic for `@given` and argument analysis for `st.builds()`... but both of those should be considered blocked on getting a 3.8 CI job anyway, so let's do the simple thing first :slightly_smiling_face: 


Discussion:

Resolving patch:
diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py
--- a/tooling/src/hypothesistooling/__main__.py
+++ b/tooling/src/hypothesistooling/__main__.py
@@ -372,12 +372,15 @@ def run_tox(task, version):
     pip_tool("tox", "-e", task, env=env, cwd=hp.HYPOTHESIS_PYTHON)
 
 
-PY27 = "2.7.14"
-PY35 = "3.5.5"
-PY36 = "3.6.8"
-PY37 = "3.7.0"
+# Via https://github.com/pyenv/pyenv/tree/master/plugins/python-build/share/python-build
+PY27 = "2.7.16"
+PY35 = "3.5.7"
+PY36 = "3.6.9"
+PY37 = "3.7.4"
+PY38 = "3.8.0rc1"
 PYPY2 = "pypy2.7-5.10.0"
-PYPY3 = "pypy3.5-5.10.1"
+PYPY35 = "pypy3.5-7.0.0"
+PYPY36 = "pypy3.6-7.1.1"
 
 
 @task()
@@ -386,9 +389,9 @@ def install_core():
     install.python_executable(PY36)
 
 
-ALIASES = {PYPY2: "pypy", PYPY3: "pypy3"}
+ALIASES = {PYPY2: "pypy", PYPY35: "pypy3", PYPY36: "pypy3"}
 
-for n in [PY27, PY35, PY36, PY37]:
+for n in [PY27, PY35, PY36, PY37, PY38]:
     major, minor, patch = n.split(".")
     ALIASES[n] = "python%s.%s" % (major, minor)
 
@@ -422,14 +425,24 @@ def check_py37():
     run_tox("py37-full", PY37)
 
 
+@python_tests
+def check_py38():
+    run_tox("py38-full", PY38)
+
+
 @python_tests
 def check_pypy():
     run_tox("pypy-full", PYPY2)
 
 
 @python_tests
-def check_pypy3():
-    run_tox("pypy3-full", PYPY3)
+def check_pypy35():
+    run_tox("pypy3-full", PYPY35)
+
+
+@python_tests
+def check_pypy36():
+    run_tox("pypy3-full", PYPY36)
 
 
 @python_tests


Test patch:
diff --git a/hypothesis-python/tests/cover/test_regex.py b/hypothesis-python/tests/cover/test_regex.py
--- a/hypothesis-python/tests/cover/test_regex.py
+++ b/hypothesis-python/tests/cover/test_regex.py
@@ -26,7 +26,7 @@
 import hypothesis.strategies as st
 from hypothesis import assume, given, settings
 from hypothesis.errors import InvalidArgument
-from hypothesis.internal.compat import PY3, hrange, hunichr
+from hypothesis.internal.compat import PY3, PYPY, hrange, hunichr
 from hypothesis.searchstrategy.regex import (
     SPACE_CHARS,
     UNICODE_DIGIT_CATEGORIES,
@@ -287,6 +287,10 @@ def test_groupref_not_shared_between_regex():
     st.tuples(st.from_regex("(a)\\1"), st.from_regex("(b)\\1")).example()
 
 
+@pytest.mark.skipif(
+    PYPY and sys.version_info[:2] == (3, 6),  # Skip for now so we can test the rest
+    reason=r"Under PyPy3.6, the pattern generates but does not match \x80\x80",
+)
 @given(st.data())
 def test_group_ref_is_not_shared_between_identical_regex(data):
     pattern = re.compile(u"^(.+)\\1\\Z", re.UNICODE)
diff --git a/hypothesis-python/tests/py3/test_asyncio.py b/hypothesis-python/tests/py3/test_asyncio.py
--- a/hypothesis-python/tests/py3/test_asyncio.py
+++ b/hypothesis-python/tests/py3/test_asyncio.py
@@ -18,6 +18,7 @@
 from __future__ import absolute_import, division, print_function
 
 import asyncio
+import sys
 import unittest
 from unittest import TestCase
 
@@ -58,6 +59,23 @@ def g():
             raise error
 
     @pytest.mark.skipif(PYPY, reason="Error in asyncio.new_event_loop()")
+    @pytest.mark.skipif(sys.version_info[:2] >= (3, 8), reason="deprecated @coroutine")
+    @given(st.text())
+    @asyncio.coroutine
+    def test_foo(self, x):
+        assume(x)
+        yield from asyncio.sleep(0.001)
+        assert x
+
+
+class TestAsyncioRun(TestCase):
+
+    timeout = 5
+
+    def execute_example(self, f):
+        asyncio.run(f())
+
+    @pytest.mark.skipif(sys.version_info[:2] < (3, 7), reason="asyncio.run() is new")
     @given(st.text())
     @asyncio.coroutine
     def test_foo(self, x):
diff --git a/hypothesis-python/tests/pytest/test_capture.py b/hypothesis-python/tests/pytest/test_capture.py
--- a/hypothesis-python/tests/pytest/test_capture.py
+++ b/hypothesis-python/tests/pytest/test_capture.py
@@ -17,6 +17,8 @@
 
 from __future__ import absolute_import, division, print_function
 
+import sys
+
 import pytest
 
 from hypothesis.internal.compat import PY2, WINDOWS, escape_unicode_characters, hunichr
@@ -112,7 +114,8 @@ def test_healthcheck_traceback_is_hidden(testdir):
     timeout_token = ": FailedHealthCheck"
     def_line = get_line_num(def_token, result)
     timeout_line = get_line_num(timeout_token, result)
-    assert timeout_line - def_line == 6
+    expected = 6 if sys.version_info[:2] < (3, 8) else 7
+    assert timeout_line - def_line == expected
 
 
 COMPOSITE_IS_NOT_A_TEST = """