summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Collins <robertc@robertcollins.net>2012-12-17 01:18:12 +1300
committerRobert Collins <robertc@robertcollins.net>2012-12-17 01:18:12 +1300
commit523984ba06fe988e36e7acd700043bbcc4cf8da6 (patch)
tree0f2139e3cf160b32bdb86606a1ec4651316f62d6
parentb9dac139cb0d92f3f72d64aff4486defbb59510f (diff)
downloadfixtures-523984ba06fe988e36e7acd700043bbcc4cf8da6.tar.gz
* ``DetailStream`` was ambiguous about whether it handled bytes or characters,
which matters a lot for Python3. It now is deprecated with ByteStream and StringStream replacing it. (Robert Collins) * Fixtures is now Python3 compatible. (Robert Collins)
-rw-r--r--NEWS6
-rw-r--r--lib/fixtures/__init__.py4
-rw-r--r--lib/fixtures/_fixtures/__init__.py8
-rw-r--r--lib/fixtures/_fixtures/detailstream.py43
-rw-r--r--lib/fixtures/_fixtures/logger.py11
-rw-r--r--lib/fixtures/_fixtures/streams.py95
-rw-r--r--lib/fixtures/fixture.py2
-rw-r--r--lib/fixtures/tests/_fixtures/__init__.py2
-rw-r--r--lib/fixtures/tests/_fixtures/test_logger.py19
-rw-r--r--lib/fixtures/tests/_fixtures/test_streams.py (renamed from lib/fixtures/tests/_fixtures/test_detailstream.py)29
10 files changed, 158 insertions, 61 deletions
diff --git a/NEWS b/NEWS
index 4f82b24..a888fe1 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,12 @@ NEXT
CHANGES
-------
+* ``DetailStream`` was ambiguous about whether it handled bytes or characters,
+ which matters a lot for Python3. It now is deprecated with ByteStream and
+ StringStream replacing it. (Robert Collins)
+
+* Fixtures is now Python3 compatible. (Robert Collins)
+
* ``FakeLogger`` has been split out into a ``LogHandler`` fixture that can
inject arbitrary handlers, giving more flexability. (Jonathan Lange)
diff --git a/lib/fixtures/__init__.py b/lib/fixtures/__init__.py
index 1390ac8..1c03bf8 100644
--- a/lib/fixtures/__init__.py
+++ b/lib/fixtures/__init__.py
@@ -39,6 +39,7 @@ Most users will want to look at TestWithFixtures and Fixture, to start with.
__version__ = (0, 3, 10, 'final', 0)
__all__ = [
+ 'ByteStream',
'DetailStream',
'EnvironmentVariable',
'EnvironmentVariableFixture',
@@ -55,6 +56,7 @@ __all__ = [
'PopenFixture',
'PythonPackage',
'PythonPathEntry',
+ 'StringStream',
'TempDir',
'TempHomeDir',
'TestWithFixtures',
@@ -69,6 +71,7 @@ from fixtures.fixture import (
MethodFixture,
)
from fixtures._fixtures import (
+ ByteStream,
DetailStream,
EnvironmentVariable,
EnvironmentVariableFixture,
@@ -82,6 +85,7 @@ from fixtures._fixtures import (
PopenFixture,
PythonPackage,
PythonPathEntry,
+ StringStream,
TempDir,
TempHomeDir,
Timeout,
diff --git a/lib/fixtures/_fixtures/__init__.py b/lib/fixtures/_fixtures/__init__.py
index e113e1e..1d54858 100644
--- a/lib/fixtures/_fixtures/__init__.py
+++ b/lib/fixtures/_fixtures/__init__.py
@@ -17,6 +17,7 @@
"""Included fixtures."""
__all__ = [
+ 'ByteStream',
'DetailStream',
'EnvironmentVariable',
'EnvironmentVariableFixture',
@@ -30,6 +31,7 @@ __all__ = [
'PopenFixture',
'PythonPackage',
'PythonPathEntry',
+ 'StringStream',
'TempDir',
'TempHomeDir',
'Timeout',
@@ -37,7 +39,6 @@ __all__ = [
]
-from fixtures._fixtures.detailstream import DetailStream
from fixtures._fixtures.environ import (
EnvironmentVariable,
EnvironmentVariableFixture,
@@ -55,6 +56,11 @@ from fixtures._fixtures.popen import (
from fixtures._fixtures.packagepath import PackagePathEntry
from fixtures._fixtures.pythonpackage import PythonPackage
from fixtures._fixtures.pythonpath import PythonPathEntry
+from fixtures._fixtures.streams import (
+ ByteStream,
+ DetailStream,
+ StringStream,
+ )
from fixtures._fixtures.tempdir import (
NestedTempfile,
TempDir,
diff --git a/lib/fixtures/_fixtures/detailstream.py b/lib/fixtures/_fixtures/detailstream.py
deleted file mode 100644
index 779ef18..0000000
--- a/lib/fixtures/_fixtures/detailstream.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# fixtures: Fixtures with cleanups for testing and convenience.
-#
-# Copyright (c) 2012, Robert Collins <robertc@robertcollins.net>
-#
-# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
-# license at the users choice. A copy of both licenses are available in the
-# project source as Apache-2.0 and BSD. You may not use this file except in
-# compliance with one of these two licences.
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# license you chose for the specific language governing permissions and
-# limitations under that license.
-
-__all__ = [
- 'DetailStream'
- ]
-
-from cStringIO import StringIO
-
-from fixtures import Fixture
-import testtools
-
-
-class DetailStream(Fixture):
- """Provide a file-like object and expose it as a detail.
-
- :attr stream: The file-like object.
- """
-
- def __init__(self, detail_name):
- """Create a DetailStream.
-
- :param detail_name: Use this as the name of the stream.
- """
- self._detail_name = detail_name
-
- def setUp(self):
- super(DetailStream, self).setUp()
- self.stream = StringIO()
- self.addDetail(self._detail_name,
- testtools.content.content_from_stream(self.stream, seek_offset=0))
diff --git a/lib/fixtures/_fixtures/logger.py b/lib/fixtures/_fixtures/logger.py
index 0beb170..e46de3a 100644
--- a/lib/fixtures/_fixtures/logger.py
+++ b/lib/fixtures/_fixtures/logger.py
@@ -15,8 +15,10 @@
from logging import StreamHandler, getLogger, INFO, Formatter
+from testtools.compat import _u
+
from fixtures import Fixture
-from fixtures._fixtures.detailstream import DetailStream
+from fixtures._fixtures.streams import StringStream
__all__ = [
'FakeLogger',
@@ -88,8 +90,8 @@ class FakeLogger(Fixture):
def setUp(self):
super(FakeLogger, self).setUp()
- name = u"pythonlogging:'%s'" % self._name
- output = self.useFixture(DetailStream(name)).stream
+ name = _u("pythonlogging:'%s'") % self._name
+ output = self.useFixture(StringStream(name)).stream
self._output = output
handler = StreamHandler(output)
if self._format:
@@ -100,7 +102,8 @@ class FakeLogger(Fixture):
@property
def output(self):
- return self._output.getvalue()
+ self._output.seek(0)
+ return self._output.read()
LoggerFixture = FakeLogger
diff --git a/lib/fixtures/_fixtures/streams.py b/lib/fixtures/_fixtures/streams.py
new file mode 100644
index 0000000..654ee67
--- /dev/null
+++ b/lib/fixtures/_fixtures/streams.py
@@ -0,0 +1,95 @@
+# fixtures: Fixtures with cleanups for testing and convenience.
+#
+# Copyright (c) 2012, Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+__all__ = [
+ 'ByteStream',
+ 'DetailStream',
+ 'StringStream',
+ ]
+
+import io
+import sys
+
+from fixtures import Fixture
+import testtools
+
+
+class Stream(Fixture):
+ """Expose a file-like object as a detail.
+
+ :attr stream: The file-like object.
+ """
+
+ def __init__(self, detail_name, stream_factory):
+ """Create a ByteStream.
+
+ :param detail_name: Use this as the name of the stream.
+ :param stream_factory: Called to construct a pair of streams:
+ (write_stream, content_stream).
+ """
+ self._detail_name = detail_name
+ self._stream_factory = stream_factory
+
+ def setUp(self):
+ super(Stream, self).setUp()
+ write_stream, read_stream = self._stream_factory()
+ self.stream = write_stream
+ self.addDetail(self._detail_name,
+ testtools.content.content_from_stream(read_stream, seek_offset=0))
+
+
+def _byte_stream_factory():
+ result = io.BytesIO()
+ return (result, result)
+
+
+def ByteStream(detail_name):
+ """Provide a file-like object that accepts bytes and expose as a detail.
+
+ :param detail_name: The name of the detail.
+ :return: A fixture which has an attribute `stream` containing the file-like
+ object.
+ """
+ return Stream(detail_name, _byte_stream_factory)
+
+
+def _string_stream_factory():
+ lower = io.BytesIO()
+ upper = io.TextIOWrapper(lower, encoding="utf8")
+ # In theory, this is sufficient and correct, but on Python2,
+ # upper.write(_b('foo")) will whinge louadly.
+ if sys.version_info[0] < 3:
+ upper_write = upper.write
+ def safe_write(str_or_bytes):
+ if type(str_or_bytes) is str:
+ str_or_bytes = str_or_bytes.decode('utf8')
+ return upper_write(str_or_bytes)
+ upper.write = safe_write
+ return upper, lower
+
+
+def StringStream(detail_name):
+ """Provide a file-like object that accepts strings and expose as a detail.
+
+ :param detail_name: The name of the detail.
+ :return: A fixture which has an attribute `stream` containing the file-like
+ object.
+ """
+ return Stream(detail_name, _string_stream_factory)
+
+
+def DetailStream(detail_name):
+ """Deprecated alias for ByteStream."""
+ return ByteStream(detail_name)
diff --git a/lib/fixtures/fixture.py b/lib/fixtures/fixture.py
index 6c3422f..2cf966d 100644
--- a/lib/fixtures/fixture.py
+++ b/lib/fixtures/fixture.py
@@ -29,7 +29,7 @@ from testtools.compat import (
)
from testtools.helpers import try_import
-from callmany import (
+from fixtures.callmany import (
CallMany,
# Deprecated, imported for compatibility.
MultipleExceptions,
diff --git a/lib/fixtures/tests/_fixtures/__init__.py b/lib/fixtures/tests/_fixtures/__init__.py
index 38987b9..e4d9403 100644
--- a/lib/fixtures/tests/_fixtures/__init__.py
+++ b/lib/fixtures/tests/_fixtures/__init__.py
@@ -15,7 +15,6 @@
def load_tests(loader, standard_tests, pattern):
test_modules = [
- 'detailstream',
'environ',
'logger',
'monkeypatch',
@@ -23,6 +22,7 @@ def load_tests(loader, standard_tests, pattern):
'popen',
'pythonpackage',
'pythonpath',
+ 'streams',
'tempdir',
'temphomedir',
'timeout',
diff --git a/lib/fixtures/tests/_fixtures/test_logger.py b/lib/fixtures/tests/_fixtures/test_logger.py
index c05e862..1f69459 100644
--- a/lib/fixtures/tests/_fixtures/test_logger.py
+++ b/lib/fixtures/tests/_fixtures/test_logger.py
@@ -16,7 +16,7 @@
import logging
from testtools import TestCase
-from cStringIO import StringIO
+from testtools.compat import StringIO
from fixtures import (
FakeLogger,
@@ -89,11 +89,22 @@ class FakeLoggerTest(TestCase, TestWithFixtures):
# Output after getDetails is called is included.
logging.info('some message')
self.assertEqual("some message\n", content.as_text())
+ # The old content object returns the old usage after cleanUp (not
+ # strictly needed but convenient). Note that no guarantee is made that
+ # it will work after setUp is called again. [It does on Python 2.x, not
+ # on 3.x]
+ self.assertEqual("some message\n", content.as_text())
with fixture:
- # The old content object returns the old usage
- self.assertEqual("some message\n", content.as_text())
- # A new one returns the new output:
+ # A new one returns new output:
self.assertEqual("", fixture.getDetails()[detail_name].as_text())
+ # The original content object may either fail, or return the old
+ # content (it must not have been reset..).
+ try:
+ self.assertEqual("some message\n", content.as_text())
+ except AssertionError:
+ raise
+ except:
+ pass
class LogHandlerTest(TestCase, TestWithFixtures):
diff --git a/lib/fixtures/tests/_fixtures/test_detailstream.py b/lib/fixtures/tests/_fixtures/test_streams.py
index 9df351d..b4f4838 100644
--- a/lib/fixtures/tests/_fixtures/test_detailstream.py
+++ b/lib/fixtures/tests/_fixtures/test_streams.py
@@ -14,18 +14,33 @@
# limitations under that license.
from testtools import TestCase
+from testtools.compat import (
+ _b,
+ _u,
+ )
+from testtools.matchers import Contains
-from fixtures import DetailStream
+from fixtures import (
+ ByteStream,
+ DetailStream,
+ StringStream,
+ )
class DetailStreamTest(TestCase):
+ def test_doc_mentions_deprecated(self):
+ self.assertThat(DetailStream.__doc__, Contains('Deprecated'))
+
+
+class TestByteStreams(TestCase):
+
def test_empty_detail_stream(self):
detail_name = 'test'
fixture = DetailStream(detail_name)
with fixture:
content = fixture.getDetails()[detail_name]
- self.assertEqual("", content.as_text())
+ self.assertEqual(_u(""), content.as_text())
def test_stream_content_in_details(self):
detail_name = 'test'
@@ -34,7 +49,7 @@ class DetailStreamTest(TestCase):
stream = fixture.stream
content = fixture.getDetails()[detail_name]
# Output after getDetails is called is included.
- stream.write("testing 1 2 3")
+ stream.write(_b("testing 1 2 3"))
self.assertEqual("testing 1 2 3", content.as_text())
def test_stream_content_reset(self):
@@ -43,12 +58,12 @@ class DetailStreamTest(TestCase):
with fixture:
stream = fixture.stream
content = fixture.getDetails()[detail_name]
- stream.write("testing 1 2 3")
+ stream.write(_b("testing 1 2 3"))
with fixture:
# The old content object returns the old usage
- self.assertEqual("testing 1 2 3", content.as_text())
+ self.assertEqual(_u("testing 1 2 3"), content.as_text())
content = fixture.getDetails()[detail_name]
# A new fixture returns the new output:
stream = fixture.stream
- stream.write("1 2 3 testing")
- self.assertEqual("1 2 3 testing", content.as_text())
+ stream.write(_b("1 2 3 testing"))
+ self.assertEqual(_u("1 2 3 testing"), content.as_text())