Blob Blame History Raw
diff --git a/examples/grinimports.py b/examples/grinimports.py
index b6d48ff..fa4917f 100755
--- a/examples/grinimports.py
+++ b/examples/grinimports.py
@@ -2,10 +2,9 @@
 # -*- coding: UTF-8 -*-
 """ Transform Python files into normalized import statements for grepping.
 """
-
 import compiler
 from compiler.visitor import ASTVisitor, walk
-from cStringIO import StringIO
+from io import StringIO
 import os
 import shlex
 import sys
@@ -70,7 +69,7 @@ def normalize_file(filename, *args):
     """
     try:
         ast = compiler.parseFile(filename)
-    except Exception, e:
+    except Exception as e:
         return StringIO('')
     ip = ImportPuller()
     walk(ast, ip)
diff --git a/examples/grinpython.py b/examples/grinpython.py
index 31e23ee..a9e1395 100755
--- a/examples/grinpython.py
+++ b/examples/grinpython.py
@@ -3,7 +3,7 @@
 """ Transform Python code by omitting strings, comments, and/or code.
 """
 
-from cStringIO import StringIO
+from io import BytesIO
 import os
 import shlex
 import string
@@ -55,7 +55,7 @@ class Transformer(object):
         """ Open a file and convert it to a filelike object with transformed
         contents.
         """
-        g = StringIO()
+        g = BytesIO()
         f = open(filename, mode)
         try:
             gen = tokenize.generate_tokens(f.readline)
diff --git a/grin.py b/grin.py
index bf42dc0..9781432 100755
--- a/grin.py
+++ b/grin.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 """ grin searches text files.
 """
+from __future__ import print_function
 
 import bisect
 import fnmatch
@@ -11,9 +12,16 @@ import re
 import shlex
 import stat
 import sys
+from io import UnsupportedOperation
 
 import argparse
 
+if sys.version_info[0] > 2:
+    to_str = lambda s : s.decode('latin1')
+    ints2bytes = bytes
+else:
+    to_str = str
+    ints2bytes = lambda ints : ''.join(map(chr, ints))
 
 #### Constants ####
 __version__ = '1.2.1'
@@ -24,8 +32,8 @@ MATCH = 0
 POST = 1
 
 # Use file(1)'s choices for what's text and what's not.
-TEXTCHARS = ''.join(map(chr, [7,8,9,10,12,13,27] + range(0x20, 0x100)))
-ALLBYTES = ''.join(map(chr, range(256)))
+TEXTCHARS = ints2bytes([7,8,9,10,12,13,27] + list(range(0x20, 0x100)))
+ALLBYTES = ints2bytes(range(256))
 
 COLOR_TABLE = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan',
                'white', 'default']
@@ -35,12 +43,11 @@ COLOR_STYLE = {
         }
 
 # gzip magic header bytes.
-GZIP_MAGIC = '\037\213'
+GZIP_MAGIC = b'\037\213'
 
 # Target amount of data to read into memory at a time.
 READ_BLOCKSIZE = 16 * 1024 * 1024
 
-
 def is_binary_string(bytes):
     """ Determine if a string is classified as binary rather than text.
 
@@ -54,7 +61,8 @@ def is_binary_string(bytes):
     """
     nontext = bytes.translate(ALLBYTES, TEXTCHARS)
     return bool(nontext)
-    
+
+
 def get_line_offsets(block):
     """ Compute the list of offsets in DataBlock 'block' which correspond to
     the beginnings of new lines.
@@ -80,7 +88,8 @@ def get_line_offsets(block):
             # Keep track of the count of lines within the "current block"
             if next_newline >= block.start and next_newline < block.end:
                 line_count += 1
-            
+
+
 def colorize(s, fg=None, bg=None, bold=False, underline=False, reverse=False):
     """ Wraps a string with ANSI color escape sequences corresponding to the
     style parameters given.
@@ -207,7 +216,7 @@ class GrepText(object):
     def read_block_with_context(self, prev, fp, fp_size):
         """ Read a block of data from the file, along with some surrounding
         context.
-        
+
         Parameters
         ----------
         prev : DataBlock, or None
@@ -216,23 +225,23 @@ class GrepText(object):
 
         fp : filelike object
             The source of block data.
-        
+
         fp_size : int or None
             Size of the file in bytes, or None if the size could not be
             determined.
-        
+
         Returns
         -------
         A DataBlock representing the "current" block along with context.
         """
         if fp_size is None:
             target_io_size = READ_BLOCKSIZE
-            block_main = fp.read(target_io_size)
+            block_main = to_str(fp.read(target_io_size))
             is_last_block = len(block_main) < target_io_size
         else:
             remaining = max(fp_size - fp.tell(), 0)
             target_io_size = min(READ_BLOCKSIZE, remaining)
-            block_main = fp.read(target_io_size)
+            block_main = to_str(fp.read(target_io_size))
             is_last_block = target_io_size == remaining
 
         if prev is None:
@@ -271,12 +280,13 @@ class GrepText(object):
         before_lines = prev.data[before_start:prev.end]
         # Using readline() to force this block out to a newline boundary...
         curr_block = (prev.data[prev.end:] + block_main +
-            ('' if is_last_block else fp.readline()))
+            ('' if is_last_block else to_str(fp.readline())))
         # Read in some lines of 'after' context.
         if is_last_block:
             after_lines = ''
         else:
-            after_lines_list = [fp.readline() for i in range(self.options.after_context)]
+            after_lines_list = [to_str(fp.readline())
+                                for i in range(self.options.after_context)]
             after_lines = ''.join(after_lines_list)
 
         result = DataBlock(
@@ -308,13 +318,15 @@ class GrepText(object):
             fp_size = None  # gzipped data is usually longer than the file
         else:
             try:
-                status = os.fstat(fp.fileno())
+                file_no = fp.fileno()
+            except (AttributeError, UnsupportedOperation):  # doesn't support fileno()
+                fp_size = None
+            else:
+                status = os.fstat(file_no)
                 if stat.S_ISREG(status.st_mode):
                     fp_size = status.st_size
                 else:
                     fp_size = None
-            except AttributeError:  # doesn't support fileno()
-                fp_size = None
 
         block = self.read_block_with_context(None, fp, fp_size)
         while block.end > block.start:
@@ -457,7 +469,7 @@ class GrepText(object):
                         color_substring = colorize(old_substring, **style)
                         line = line[:start] + color_substring + line[end:]
                         total_offset += len(color_substring) - len(old_substring)
-                        
+
                 ns = dict(
                     lineno = i+1,
                     sep = {PRE: '-', POST: '+', MATCH: ':'}[kind],
@@ -495,8 +507,8 @@ class GrepText(object):
             f = sys.stdin
             filename = '<STDIN>'
         else:
-            # 'r' does the right thing for both open ('rt') and gzip.open ('rb')
-            f = opener(filename, 'r')
+            # Always open in binary mode
+            f = opener(filename, 'rb')
         try:
             unique_context = self.do_grep(f)
         finally:
@@ -587,7 +599,7 @@ class FileRecognizer(object):
         """
         try:
             bytes = f.read(self.binary_bytes)
-        except Exception, e:
+        except Exception as e:
             # When trying to read from something that looks like a gzipped file,
             # it may be corrupt. If we do get an error, assume that the file is binary.
             return True
@@ -1032,7 +1044,7 @@ def grin_main(argv=None):
             sys.stdout.write(report)
     except KeyboardInterrupt:
         raise SystemExit(0)
-    except IOError, e:
+    except IOError as e:
         if 'Broken pipe' in str(e):
             # The user is probably piping to a pager like less(1) and has exited
             # it. Just exit.
@@ -1040,7 +1052,7 @@ def grin_main(argv=None):
         raise
 
 def print_line(filename):
-    print filename
+    print(filename)
 
 def print_null(filename):
     # Note that the final filename will have a trailing NUL, just like 
@@ -1073,7 +1085,7 @@ def grind_main(argv=None):
                     output(filename)
     except KeyboardInterrupt:
         raise SystemExit(0)
-    except IOError, e:
+    except IOError as e:
         if 'Broken pipe' in str(e):
             # The user is probably piping to a pager like less(1) and has exited
             # it. Just exit.
diff --git a/tests/test_file_recognizer.py b/tests/test_file_recognizer.py
index 70b7f5a..a085ce1 100644
--- a/tests/test_file_recognizer.py
+++ b/tests/test_file_recognizer.py
@@ -1,5 +1,6 @@
 """ Test the file recognizer capabilities.
 """
+from __future__ import print_function
 
 import gzip
 import os
@@ -9,7 +10,7 @@ import sys
 
 import nose
 
-from grin import FileRecognizer
+from grin import FileRecognizer, ints2bytes, GZIP_MAGIC
 
 def empty_file(filename, open=open):
     f = open(filename, 'wb')
@@ -17,13 +18,13 @@ def empty_file(filename, open=open):
 
 def binary_file(filename, open=open):
     f = open(filename, 'wb')
-    f.write(''.join(map(chr, range(256))))
+    f.write(ints2bytes(range(255)))
     f.close()
 
 def text_file(filename, open=open):
-    lines = ['foo\n', 'bar\n'] * 100
-    lines.append('baz\n')
-    lines.extend(['foo\n', 'bar\n'] * 100)
+    lines = [b'foo\n', b'bar\n'] * 100
+    lines.append(b'baz\n')
+    lines.extend([b'foo\n', b'bar\n'] * 100)
     f = open(filename, 'wb')
     f.writelines(lines)
     f.close()
@@ -32,10 +33,9 @@ def fake_gzip_file(filename, open=open):
     """ Write out a binary file that has the gzip magic header bytes, but is not
     a gzip file.
     """
-    GZIP_MAGIC = '\037\213'
     f = open(filename, 'wb')
     f.write(GZIP_MAGIC)
-    f.write(''.join(map(chr, range(256))))
+    f.write(ints2bytes(range(255)))
     f.close()
 
 def binary_middle(filename, open=open):
@@ -43,7 +43,7 @@ def binary_middle(filename, open=open):
     bytes, then 100 text bytes to test that the recognizer only reads some of
     the file.
     """
-    text = 'a'*100 + '\0'*100 + 'b'*100
+    text = b'a'*100 + b'\0'*100 + b'b'*100
     f = open(filename, 'wb')
     f.write(text)
     f.close()
@@ -56,25 +56,25 @@ def unreadable_file(filename):
     """ Write a file that does not have read permissions.
     """
     text_file(filename)
-    os.chmod(filename, 0200)
+    os.chmod(filename, 0o200)
 
 def unreadable_dir(filename):
     """ Make a directory that does not have read permissions.
     """
     os.mkdir(filename)
-    os.chmod(filename, 0300)
+    os.chmod(filename, 0o300)
 
 def unexecutable_dir(filename):
     """ Make a directory that does not have execute permissions.
     """
     os.mkdir(filename)
-    os.chmod(filename, 0600)
+    os.chmod(filename, 0o600)
 
 def totally_unusable_dir(filename):
     """ Make a directory that has neither read nor execute permissions.
     """
     os.mkdir(filename)
-    os.chmod(filename, 0100)
+    os.chmod(filename, 0o100)
 
 def setup():
     # Make files to test individual recognizers.
@@ -135,22 +135,14 @@ def setup():
     text_file('tree/.skip_hidden_file')
     os.mkdir('tree/unreadable_dir')
     text_file('tree/unreadable_dir/text')
-    os.chmod('tree/unreadable_dir', 0300)
+    os.chmod('tree/unreadable_dir', 0o300)
     os.mkdir('tree/unexecutable_dir')
     text_file('tree/unexecutable_dir/text')
-    os.chmod('tree/unexecutable_dir', 0600)
+    os.chmod('tree/unexecutable_dir', 0o600)
     os.mkdir('tree/totally_unusable_dir')
     text_file('tree/totally_unusable_dir/text')
-    os.chmod('tree/totally_unusable_dir', 0100)
+    os.chmod('tree/totally_unusable_dir', 0o100)
 
-def ensure_deletability(arg, dirname, fnames):
-    """ os.path.walk() callback function which will make sure every directory is
-    readable and executable so that it may be easily deleted.
-    """
-    for fn in fnames:
-        fn = os.path.join(dirname, fn)
-        if os.path.isdir(fn):
-            os.chmod(fn, 0700)
 
 def teardown():
     files_to_delete = ['empty', 'binary', 'binary_middle', 'text', 'text~',
@@ -168,10 +160,13 @@ def teardown():
                 os.unlink(filename)
             else:
                 os.rmdir(filename)
-        except Exception, e:
-            print >>sys.stderr, 'Could not delete %s: %s' % (filename, e)
+        except Exception as e:
+            print('Could not delete %s: %s' % (filename, e), file=sys.stderr)
     os.unlink('socket_test')
-    os.path.walk('tree', ensure_deletability, None)
+    for dirpath, dirnames, filenames in os.walk('tree'):
+        # Make sure every directory can be deleted
+        for dirname in dirnames:
+            os.chmod(os.path.join(dirpath, dirname), 0o700)
     shutil.rmtree('tree')
 
 
diff --git a/tests/test_grep.py b/tests/test_grep.py
index aa367f2..f5ee62f 100644
--- a/tests/test_grep.py
+++ b/tests/test_grep.py
@@ -4,52 +4,52 @@ r'''
 Set up
 
     >>> import grin
-    >>> from cStringIO import StringIO
+    >>> from io import BytesIO
     >>> import re
-    >>> 
-    >>> all_foo = """\
+    >>>
+    >>> all_foo = b"""\
     ... foo
     ... foo
     ... foo
     ... foo
     ... foo
     ... """
-    >>> first_foo = """\
+    >>> first_foo = b"""\
     ... foo
     ... bar
     ... bar
     ... bar
     ... bar
     ... """
-    >>> last_foo = """\
+    >>> last_foo = b"""\
     ... bar
     ... bar
     ... bar
     ... bar
     ... foo
     ... """
-    >>> second_foo = """\
+    >>> second_foo = b"""\
     ... bar
     ... foo
     ... bar
     ... bar
     ... bar
     ... """
-    >>> second_last_foo = """\
+    >>> second_last_foo = b"""\
     ... bar
     ... bar
     ... bar
     ... foo
     ... bar
     ... """
-    >>> middle_foo = """\
+    >>> middle_foo = b"""\
     ... bar
     ... bar
     ... foo
     ... bar
     ... bar
     ... """
-    >>> small_gap = """\
+    >>> small_gap = b"""\
     ... bar
     ... bar
     ... foo
@@ -58,8 +58,8 @@ Set up
     ... bar
     ... bar
     ... """
-    >>> no_eol = "foo"
-    >>> middle_of_line = """\
+    >>> no_eol = b"foo"
+    >>> middle_of_line = b"""\
     ... bar
     ... bar
     ... barfoobar
@@ -70,111 +70,111 @@ Set up
 Test the basic defaults, no context.
 
     >>> gt_default = grin.GrepText(re.compile('foo'))
-    >>> gt_default.do_grep(StringIO(all_foo))
+    >>> gt_default.do_grep(BytesIO(all_foo))
     [(0, 0, 'foo\n', [(0, 3)]), (1, 0, 'foo\n', [(0, 3)]), (2, 0, 'foo\n', [(0, 3)]), (3, 0, 'foo\n', [(0, 3)]), (4, 0, 'foo\n', [(0, 3)])]
-    >>> gt_default.do_grep(StringIO(first_foo))
+    >>> gt_default.do_grep(BytesIO(first_foo))
     [(0, 0, 'foo\n', [(0, 3)])]
-    >>> gt_default.do_grep(StringIO(last_foo))
+    >>> gt_default.do_grep(BytesIO(last_foo))
     [(4, 0, 'foo\n', [(0, 3)])]
-    >>> gt_default.do_grep(StringIO(second_foo))
+    >>> gt_default.do_grep(BytesIO(second_foo))
     [(1, 0, 'foo\n', [(0, 3)])]
-    >>> gt_default.do_grep(StringIO(second_last_foo))
+    >>> gt_default.do_grep(BytesIO(second_last_foo))
     [(3, 0, 'foo\n', [(0, 3)])]
-    >>> gt_default.do_grep(StringIO(middle_foo))
+    >>> gt_default.do_grep(BytesIO(middle_foo))
     [(2, 0, 'foo\n', [(0, 3)])]
-    >>> gt_default.do_grep(StringIO(small_gap))
+    >>> gt_default.do_grep(BytesIO(small_gap))
     [(2, 0, 'foo\n', [(0, 3)]), (4, 0, 'foo\n', [(0, 3)])]
-    >>> gt_default.do_grep(StringIO(no_eol))
+    >>> gt_default.do_grep(BytesIO(no_eol))
     [(0, 0, 'foo', [(0, 3)])]
-    >>> gt_default.do_grep(StringIO(middle_of_line))
+    >>> gt_default.do_grep(BytesIO(middle_of_line))
     [(2, 0, 'barfoobar\n', [(3, 6)])]
 
 Symmetric 1-line context.
 
     >>> gt_context_1 = grin.GrepText(re.compile('foo'), options=grin.Options(before_context=1, after_context=1))
-    >>> gt_context_1.do_grep(StringIO(all_foo))
+    >>> gt_context_1.do_grep(BytesIO(all_foo))
     [(0, 0, 'foo\n', [(0, 3)]), (1, 0, 'foo\n', [(0, 3)]), (2, 0, 'foo\n', [(0, 3)]), (3, 0, 'foo\n', [(0, 3)]), (4, 0, 'foo\n', [(0, 3)])]
-    >>> gt_context_1.do_grep(StringIO(first_foo))
+    >>> gt_context_1.do_grep(BytesIO(first_foo))
     [(0, 0, 'foo\n', [(0, 3)]), (1, 1, 'bar\n', None)]
-    >>> gt_context_1.do_grep(StringIO(last_foo))
+    >>> gt_context_1.do_grep(BytesIO(last_foo))
     [(3, -1, 'bar\n', None), (4, 0, 'foo\n', [(0, 3)])]
-    >>> gt_context_1.do_grep(StringIO(second_foo))
+    >>> gt_context_1.do_grep(BytesIO(second_foo))
     [(0, -1, 'bar\n', None), (1, 0, 'foo\n', [(0, 3)]), (2, 1, 'bar\n', None)]
-    >>> gt_context_1.do_grep(StringIO(second_last_foo))
+    >>> gt_context_1.do_grep(BytesIO(second_last_foo))
     [(2, -1, 'bar\n', None), (3, 0, 'foo\n', [(0, 3)]), (4, 1, 'bar\n', None)]
-    >>> gt_context_1.do_grep(StringIO(middle_foo))
+    >>> gt_context_1.do_grep(BytesIO(middle_foo))
     [(1, -1, 'bar\n', None), (2, 0, 'foo\n', [(0, 3)]), (3, 1, 'bar\n', None)]
-    >>> gt_context_1.do_grep(StringIO(small_gap))
+    >>> gt_context_1.do_grep(BytesIO(small_gap))
     [(1, -1, 'bar\n', None), (2, 0, 'foo\n', [(0, 3)]), (3, 1, 'bar\n', None), (4, 0, 'foo\n', [(0, 3)]), (5, 1, 'bar\n', None)]
-    >>> gt_context_1.do_grep(StringIO(no_eol))
+    >>> gt_context_1.do_grep(BytesIO(no_eol))
     [(0, 0, 'foo', [(0, 3)])]
-    >>> gt_context_1.do_grep(StringIO(middle_of_line))
+    >>> gt_context_1.do_grep(BytesIO(middle_of_line))
     [(1, -1, 'bar\n', None), (2, 0, 'barfoobar\n', [(3, 6)]), (3, 1, 'bar\n', None)]
 
 Symmetric 2-line context.
 
     >>> gt_context_2 = grin.GrepText(re.compile('foo'), options=grin.Options(before_context=2, after_context=2))
-    >>> gt_context_2.do_grep(StringIO(all_foo))
+    >>> gt_context_2.do_grep(BytesIO(all_foo))
     [(0, 0, 'foo\n', [(0, 3)]), (1, 0, 'foo\n', [(0, 3)]), (2, 0, 'foo\n', [(0, 3)]), (3, 0, 'foo\n', [(0, 3)]), (4, 0, 'foo\n', [(0, 3)])]
-    >>> gt_context_2.do_grep(StringIO(first_foo))
+    >>> gt_context_2.do_grep(BytesIO(first_foo))
     [(0, 0, 'foo\n', [(0, 3)]), (1, 1, 'bar\n', None), (2, 1, 'bar\n', None)]
-    >>> gt_context_2.do_grep(StringIO(last_foo))
+    >>> gt_context_2.do_grep(BytesIO(last_foo))
     [(2, -1, 'bar\n', None), (3, -1, 'bar\n', None), (4, 0, 'foo\n', [(0, 3)])]
-    >>> gt_context_2.do_grep(StringIO(second_foo))
+    >>> gt_context_2.do_grep(BytesIO(second_foo))
     [(0, -1, 'bar\n', None), (1, 0, 'foo\n', [(0, 3)]), (2, 1, 'bar\n', None), (3, 1, 'bar\n', None)]
-    >>> gt_context_2.do_grep(StringIO(second_last_foo))
+    >>> gt_context_2.do_grep(BytesIO(second_last_foo))
     [(1, -1, 'bar\n', None), (2, -1, 'bar\n', None), (3, 0, 'foo\n', [(0, 3)]), (4, 1, 'bar\n', None)]
-    >>> gt_context_2.do_grep(StringIO(middle_foo))
+    >>> gt_context_2.do_grep(BytesIO(middle_foo))
     [(0, -1, 'bar\n', None), (1, -1, 'bar\n', None), (2, 0, 'foo\n', [(0, 3)]), (3, 1, 'bar\n', None), (4, 1, 'bar\n', None)]
-    >>> gt_context_2.do_grep(StringIO(small_gap))
+    >>> gt_context_2.do_grep(BytesIO(small_gap))
     [(0, -1, 'bar\n', None), (1, -1, 'bar\n', None), (2, 0, 'foo\n', [(0, 3)]), (3, 1, 'bar\n', None), (4, 0, 'foo\n', [(0, 3)]), (5, 1, 'bar\n', None), (6, 1, 'bar\n', None)]
-    >>> gt_context_2.do_grep(StringIO(no_eol))
+    >>> gt_context_2.do_grep(BytesIO(no_eol))
     [(0, 0, 'foo', [(0, 3)])]
-    >>> gt_context_2.do_grep(StringIO(middle_of_line))
+    >>> gt_context_2.do_grep(BytesIO(middle_of_line))
     [(0, -1, 'bar\n', None), (1, -1, 'bar\n', None), (2, 0, 'barfoobar\n', [(3, 6)]), (3, 1, 'bar\n', None), (4, 1, 'bar\n', None)]
 
 1 line of before-context, no lines after.
 
     >>> gt_before_context_1 = grin.GrepText(re.compile('foo'), options=grin.Options(before_context=1, after_context=0))
-    >>> gt_before_context_1.do_grep(StringIO(all_foo))
+    >>> gt_before_context_1.do_grep(BytesIO(all_foo))
     [(0, 0, 'foo\n', [(0, 3)]), (1, 0, 'foo\n', [(0, 3)]), (2, 0, 'foo\n', [(0, 3)]), (3, 0, 'foo\n', [(0, 3)]), (4, 0, 'foo\n', [(0, 3)])]
-    >>> gt_before_context_1.do_grep(StringIO(first_foo))
+    >>> gt_before_context_1.do_grep(BytesIO(first_foo))
     [(0, 0, 'foo\n', [(0, 3)])]
-    >>> gt_before_context_1.do_grep(StringIO(last_foo))
+    >>> gt_before_context_1.do_grep(BytesIO(last_foo))
     [(3, -1, 'bar\n', None), (4, 0, 'foo\n', [(0, 3)])]
-    >>> gt_before_context_1.do_grep(StringIO(second_foo))
+    >>> gt_before_context_1.do_grep(BytesIO(second_foo))
     [(0, -1, 'bar\n', None), (1, 0, 'foo\n', [(0, 3)])]
-    >>> gt_before_context_1.do_grep(StringIO(second_last_foo))
+    >>> gt_before_context_1.do_grep(BytesIO(second_last_foo))
     [(2, -1, 'bar\n', None), (3, 0, 'foo\n', [(0, 3)])]
-    >>> gt_before_context_1.do_grep(StringIO(middle_foo))
+    >>> gt_before_context_1.do_grep(BytesIO(middle_foo))
     [(1, -1, 'bar\n', None), (2, 0, 'foo\n', [(0, 3)])]
-    >>> gt_before_context_1.do_grep(StringIO(small_gap))
+    >>> gt_before_context_1.do_grep(BytesIO(small_gap))
     [(1, -1, 'bar\n', None), (2, 0, 'foo\n', [(0, 3)]), (3, -1, 'bar\n', None), (4, 0, 'foo\n', [(0, 3)])]
-    >>> gt_before_context_1.do_grep(StringIO(no_eol))
+    >>> gt_before_context_1.do_grep(BytesIO(no_eol))
     [(0, 0, 'foo', [(0, 3)])]
-    >>> gt_before_context_1.do_grep(StringIO(middle_of_line))
+    >>> gt_before_context_1.do_grep(BytesIO(middle_of_line))
     [(1, -1, 'bar\n', None), (2, 0, 'barfoobar\n', [(3, 6)])]
 
 1 line of after-context, no lines before.
 
     >>> gt_after_context_1 = grin.GrepText(re.compile('foo'), options=grin.Options(before_context=0, after_context=1))
-    >>> gt_after_context_1.do_grep(StringIO(all_foo))
+    >>> gt_after_context_1.do_grep(BytesIO(all_foo))
     [(0, 0, 'foo\n', [(0, 3)]), (1, 0, 'foo\n', [(0, 3)]), (2, 0, 'foo\n', [(0, 3)]), (3, 0, 'foo\n', [(0, 3)]), (4, 0, 'foo\n', [(0, 3)])]
-    >>> gt_after_context_1.do_grep(StringIO(first_foo))
+    >>> gt_after_context_1.do_grep(BytesIO(first_foo))
     [(0, 0, 'foo\n', [(0, 3)]), (1, 1, 'bar\n', None)]
-    >>> gt_after_context_1.do_grep(StringIO(last_foo))
+    >>> gt_after_context_1.do_grep(BytesIO(last_foo))
     [(4, 0, 'foo\n', [(0, 3)])]
-    >>> gt_after_context_1.do_grep(StringIO(second_foo))
+    >>> gt_after_context_1.do_grep(BytesIO(second_foo))
     [(1, 0, 'foo\n', [(0, 3)]), (2, 1, 'bar\n', None)]
-    >>> gt_after_context_1.do_grep(StringIO(second_last_foo))
+    >>> gt_after_context_1.do_grep(BytesIO(second_last_foo))
     [(3, 0, 'foo\n', [(0, 3)]), (4, 1, 'bar\n', None)]
-    >>> gt_after_context_1.do_grep(StringIO(middle_foo))
+    >>> gt_after_context_1.do_grep(BytesIO(middle_foo))
     [(2, 0, 'foo\n', [(0, 3)]), (3, 1, 'bar\n', None)]
-    >>> gt_after_context_1.do_grep(StringIO(small_gap))
+    >>> gt_after_context_1.do_grep(BytesIO(small_gap))
     [(2, 0, 'foo\n', [(0, 3)]), (3, 1, 'bar\n', None), (4, 0, 'foo\n', [(0, 3)]), (5, 1, 'bar\n', None)]
-    >>> gt_after_context_1.do_grep(StringIO(no_eol))
+    >>> gt_after_context_1.do_grep(BytesIO(no_eol))
     [(0, 0, 'foo', [(0, 3)])]
-    >>> gt_after_context_1.do_grep(StringIO(middle_of_line))
+    >>> gt_after_context_1.do_grep(BytesIO(middle_of_line))
     [(2, 0, 'barfoobar\n', [(3, 6)]), (3, 1, 'bar\n', None)]
 
 '''