From cc57067355db081f66de04664f1f88a76cd0f16d Mon Sep 17 00:00:00 2001
From: Doug Hellmann <doug@doughellmann.com>
Date: Thu, 11 Jun 2015 20:08:29 +0000
Subject: [PATCH] Add CreateFileWithContent fixture

Add a fixture for creating real files on the filesystem with contents,
to replace the implementation inside the test base class.

Blueprint oslotest-refactor-test-base-class

Change-Id: Ibca716d1054da973583e5bb4694ef1d64750d50a
---
 doc/source/api.rst            |  7 ++++
 oslotest/base.py              | 24 ++++--------
 oslotest/createfile.py        | 70 +++++++++++++++++++++++++++++++++++
 tests/unit/test_createfile.py | 62 +++++++++++++++++++++++++++++++
 4 files changed, 147 insertions(+), 16 deletions(-)
 create mode 100644 oslotest/createfile.py
 create mode 100644 tests/unit/test_createfile.py

diff --git a/doc/source/api.rst b/doc/source/api.rst
index bf6ac0c..fbd3965 100644
--- a/doc/source/api.rst
+++ b/doc/source/api.rst
@@ -10,6 +10,13 @@ oslotest.base
 .. autoclass:: oslotest.base.BaseTestCase
    :members:
 
+oslotest.createfile
+===================
+
+.. automodule:: oslotest.createfile
+   :members:
+   :special-members:
+
 oslotest.log
 ============
 
diff --git a/oslotest/base.py b/oslotest/base.py
index aa162f8..b77f846 100644
--- a/oslotest/base.py
+++ b/oslotest/base.py
@@ -15,15 +15,12 @@
 
 """Common utilities used in testing"""
 
-import os
-import tempfile
-
 import fixtures
+from oslotest import createfile
 from oslotest import log
 from oslotest import output
 from oslotest import timeout
 
-import six
 from six.moves import mock
 import testtools
 
@@ -128,16 +125,11 @@ class BaseTestCase(testtools.TestCase):
             else:
                 basename, contents = f
                 encoding = default_encoding
-            if isinstance(contents, six.text_type):
-                contents = contents.encode(encoding)
-            if not os.path.isabs(basename):
-                (fd, path) = tempfile.mkstemp(prefix=basename, suffix=ext)
-            else:
-                path = basename + ext
-                fd = os.open(path, os.O_CREAT | os.O_WRONLY)
-            tempfiles.append(path)
-            try:
-                os.write(fd, contents)
-            finally:
-                os.close(fd)
+            fix = self.useFixture(createfile.CreateFileWithContent(
+                filename=basename,
+                contents=contents,
+                ext=ext,
+                encoding=encoding,
+            ))
+            tempfiles.append(fix.path)
         return tempfiles
diff --git a/oslotest/createfile.py b/oslotest/createfile.py
new file mode 100644
index 0000000..86fa8b2
--- /dev/null
+++ b/oslotest/createfile.py
@@ -0,0 +1,70 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+import tempfile
+
+import fixtures
+import six
+
+
+class CreateFileWithContent(fixtures.Fixture):
+    """Create a temporary file with the given content.
+
+    Creates a file using a predictable name, to be used by tests for
+    code that need a filename to load data or otherwise interact with
+    the real filesystem.
+
+    .. warning::
+
+       It is the responsibility of the caller to ensure that the file
+       is removed.
+
+    Users of this fixture may also want to use
+    :class:`fixtures.NestedTempfile` to set the temporary directory
+    somewhere safe and to ensure the files are cleaned up.
+
+    .. py:attribute:: path
+
+       The canonical name of the file created.
+
+    :param filename: Base file name or full literal path to the file
+        to be created.
+    :param contents: The data to write to the file. Unicode data will
+        be encoded before being written.
+    :param ext: An extension to add to filename.
+    :param encoding: An encoding to use for unicode data (ignored for
+        byte strings).
+
+    """
+
+    def __init__(self, filename, contents, ext='.conf', encoding='utf-8'):
+        self._filename = filename
+        self._contents = contents
+        self._ext = ext
+        self._encoding = encoding
+
+    def setUp(self):
+        super(CreateFileWithContent, self).setUp()
+        contents = self._contents
+        if isinstance(contents, six.text_type):
+            contents = contents.encode(self._encoding)
+        if not os.path.isabs(self._filename):
+            (fd, self.path) = tempfile.mkstemp(prefix=self._filename,
+                                               suffix=self._ext)
+        else:
+            self.path = self._filename + self._ext
+            fd = os.open(self.path, os.O_CREAT | os.O_WRONLY)
+        try:
+            os.write(fd, contents)
+        finally:
+            os.close(fd)
diff --git a/tests/unit/test_createfile.py b/tests/unit/test_createfile.py
new file mode 100644
index 0000000..bed5a99
--- /dev/null
+++ b/tests/unit/test_createfile.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2014 Deutsche Telekom AG
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import os
+
+import six
+
+from oslotest import base
+from oslotest import createfile
+
+
+class CreateFileWithContentTest(base.BaseTestCase):
+
+    def test_create_unicode_files(self):
+        f = createfile.CreateFileWithContent(
+            "no_approve",
+            u'ಠ_ಠ',
+        )
+        f.setUp()
+        with open(f.path, 'rb') as f:
+            contents = f.read()
+        self.assertEqual(u'ಠ_ಠ', six.text_type(contents, encoding='utf-8'))
+
+    def test_create_unicode_files_encoding(self):
+        f = createfile.CreateFileWithContent(
+            "embarrassed", u'⊙﹏⊙', encoding='utf-8',
+        )
+        f.setUp()
+        with open(f.path, 'rb') as f:
+            contents = f.read()
+        self.assertEqual(u'⊙﹏⊙', six.text_type(contents, encoding='utf-8'))
+
+    def test_create_bad_encoding(self):
+        f = createfile.CreateFileWithContent(
+            "hrm", u'ಠ~ಠ', encoding='ascii',
+        )
+        self.assertRaises(UnicodeError, f.setUp)
+
+    def test_prefix(self):
+        f = createfile.CreateFileWithContent('testing', '')
+        f.setUp()
+        basename = os.path.basename(f.path)
+        self.assertTrue(basename.startswith('testing'))
+
+    def test_ext(self):
+        f = createfile.CreateFileWithContent('testing', '', ext='.ending')
+        f.setUp()
+        basename = os.path.basename(f.path)
+        self.assertTrue(basename.endswith('.ending'))