|
| 1 | +# BufferedWriter must respect buffer_size like CPython (Modules/_io/bufferedio.c): |
| 2 | +# - writes that fit in the buffer's free space are only buffered (no raw write) |
| 3 | +# - writes that don't fit first drain the buffer to the raw stream |
| 4 | +# - payloads larger than buffer_size bypass the buffer and go straight to raw |
| 5 | +# - the remaining tail (<= buffer_size) is buffered again |
| 6 | +import _io |
| 7 | + |
| 8 | +PATH = "/tmp/pycpp_buffered_writer_test.bin" |
| 9 | + |
| 10 | + |
| 11 | +def file_contents(): |
| 12 | + f = _io.FileIO(PATH, "rb") |
| 13 | + data = f.readall() |
| 14 | + f.close() |
| 15 | + return data |
| 16 | + |
| 17 | + |
| 18 | +# 1. a small write stays in the buffer until flush |
| 19 | +w = _io.BufferedWriter(_io.FileIO(PATH, "wb")) |
| 20 | +assert w.write(b"abcd") == 4 |
| 21 | +assert file_contents() == b"", file_contents() |
| 22 | +w.flush() |
| 23 | +assert file_contents() == b"abcd", file_contents() |
| 24 | + |
| 25 | +# 2. overflowing the buffer drains it, and an oversized payload bypasses the buffer |
| 26 | +w = _io.BufferedWriter(_io.FileIO(PATH, "wb"), 8) |
| 27 | +assert w.write(b"abcd") == 4 |
| 28 | +assert file_contents() == b"", file_contents() |
| 29 | +assert w.write(b"efghijklm") == 9 |
| 30 | +assert file_contents() == b"abcdefghijklm", file_contents() |
| 31 | + |
| 32 | +# 3. a tail smaller than buffer_size stays buffered after the drain |
| 33 | +w = _io.BufferedWriter(_io.FileIO(PATH, "wb"), 8) |
| 34 | +assert w.write(b"abcdef") == 6 |
| 35 | +assert w.write(b"ghi") == 3 |
| 36 | +assert file_contents() == b"abcdef", file_contents() |
| 37 | +w.flush() |
| 38 | +assert file_contents() == b"abcdefghi", file_contents() |
| 39 | + |
| 40 | +# 4. an exact fit is fully buffered |
| 41 | +w = _io.BufferedWriter(_io.FileIO(PATH, "wb"), 8) |
| 42 | +assert w.write(b"12345678") == 8 |
| 43 | +assert file_contents() == b"", file_contents() |
| 44 | +w.flush() |
| 45 | +assert file_contents() == b"12345678", file_contents() |
| 46 | + |
| 47 | +# 5. buffer_size must be strictly positive |
| 48 | +try: |
| 49 | + _io.BufferedWriter(_io.FileIO(PATH, "wb"), 0) |
| 50 | + assert False, "expected ValueError" |
| 51 | +except ValueError: |
| 52 | + pass |
| 53 | + |
| 54 | + |
| 55 | +# 6. raw can be any duck-typed object; a write() that over-reports the byte |
| 56 | +# count must raise OSError instead of wrapping the unsigned byte counters |
| 57 | +class OverReportingWriter: |
| 58 | + def write(self, b): |
| 59 | + return 1000 |
| 60 | + |
| 61 | + |
| 62 | +w = _io.BufferedWriter(OverReportingWriter(), 8) |
| 63 | +try: |
| 64 | + w.write(b"0123456789abcdef") |
| 65 | + assert False, "expected OSError" |
| 66 | +except OSError: |
| 67 | + pass |
| 68 | + |
| 69 | + |
| 70 | +# 7. same for a negative count |
| 71 | +class NegativeWriter: |
| 72 | + def write(self, b): |
| 73 | + return -1 |
| 74 | + |
| 75 | + |
| 76 | +w = _io.BufferedWriter(NegativeWriter(), 8) |
| 77 | +try: |
| 78 | + w.write(b"0123456789abcdef") |
| 79 | + assert False, "expected OSError" |
| 80 | +except OSError: |
| 81 | + pass |
| 82 | + |
| 83 | +# 8. a raw write() returning 0 makes no progress; retrying forever would hang, |
| 84 | +# so it must raise OSError |
| 85 | +class ZeroWriter: |
| 86 | + def write(self, b): |
| 87 | + return 0 |
| 88 | + |
| 89 | + |
| 90 | +w = _io.BufferedWriter(ZeroWriter(), 8) |
| 91 | +try: |
| 92 | + w.write(b"0123456789abcdef") |
| 93 | + assert False, "expected OSError" |
| 94 | +except OSError: |
| 95 | + pass |
| 96 | + |
| 97 | + |
| 98 | +# 9. a raw write() returning None means the stream accepted no data without |
| 99 | +# blocking; that is an OSError (BlockingIOError in CPython), not a crash |
| 100 | +class NoneWriter: |
| 101 | + def write(self, b): |
| 102 | + return None |
| 103 | + |
| 104 | + |
| 105 | +w = _io.BufferedWriter(NoneWriter(), 8) |
| 106 | +try: |
| 107 | + w.write(b"0123456789abcdef") |
| 108 | + assert False, "expected OSError" |
| 109 | +except OSError: |
| 110 | + pass |
| 111 | + |
| 112 | + |
| 113 | +# 10. any other non-int return from raw write() is a TypeError, not a crash |
| 114 | +class StringWriter: |
| 115 | + def write(self, b): |
| 116 | + return "16" |
| 117 | + |
| 118 | + |
| 119 | +w = _io.BufferedWriter(StringWriter(), 8) |
| 120 | +try: |
| 121 | + w.write(b"0123456789abcdef") |
| 122 | + assert False, "expected TypeError" |
| 123 | +except TypeError: |
| 124 | + pass |
| 125 | + |
| 126 | +# 11. an object created via __new__ without __init__ has no raw stream; every |
| 127 | +# I/O method must raise ValueError instead of dereferencing it |
| 128 | +uninitialized = _io.BufferedWriter.__new__(_io.BufferedWriter) |
| 129 | +for method in ( |
| 130 | + uninitialized.isatty, |
| 131 | + uninitialized.flush, |
| 132 | + lambda: uninitialized.write(b"x"), |
| 133 | +): |
| 134 | + try: |
| 135 | + method() |
| 136 | + assert False, "expected ValueError" |
| 137 | + except ValueError: |
| 138 | + pass |
| 139 | + |
| 140 | +print("buffered_writer: ok") |
0 commit comments