diff --git a/integration/tests/file_buffered_read.py b/integration/tests/file_buffered_read.py new file mode 100644 index 00000000..60395634 --- /dev/null +++ b/integration/tests/file_buffered_read.py @@ -0,0 +1,44 @@ +# Regression test: BufferedReader.read1(n)/read(n) must return at most n +# bytes and never None. Previously the buffered fast path returned every +# buffered byte regardless of n, or None when fewer than n bytes were +# buffered. + +# Run with cwd == integration/ (as the integration runner does). +DATA = "tests/file_readline_data.txt" # b"a\nbb\nccc" + +# 1. read1 returns at most n bytes; "" at EOF. +f = open(DATA, "rb") +assert f.read1(3) == b"a\nb" +assert f.read1(0) == b"" +assert f.read1(100) == b"b\nccc" +assert f.read1(4) == b"" +assert f.read1() == b"" +f.close() + +# 2. read(n) returns exactly n bytes until the stream runs out. +g = open(DATA, "rb") +assert g.read(2) == b"a\n" +assert g.read(100) == b"bb\nccc" +assert g.read(1) == b"" +g.close() + +# 3. read() with no argument reads everything. +h = open(DATA, "rb") +assert h.read() == b"a\nbb\nccc" +assert h.read() == b"" +h.close() + +# 4. readinto fills a writable buffer and returns the byte count; read-only +# buffers (bytes) must be rejected instead of silently written to. +i = open(DATA, "rb") +ba = bytearray(b"xyz") +assert i.readinto(ba) == 3 +assert ba == bytearray(b"a\nb") +try: + i.readinto(b"xxxx") + assert False, "readinto(bytes) must raise TypeError" +except TypeError: + pass +i.close() + +print("file_buffered_read: ok") diff --git a/integration/tests/file_readline.py b/integration/tests/file_readline.py new file mode 100644 index 00000000..d14ed123 --- /dev/null +++ b/integration/tests/file_readline.py @@ -0,0 +1,85 @@ +# Regression test: TextIOWrapper.readline must return one line at a time +# (including the line ending), honour the size limit, keep lines that span +# the 8192-byte read chunks intact, and return "" at EOF. Previously a single +# call concatenated lines, dropped data, or crashed on uneven line lengths. + +# Run with cwd == integration/ (as the integration runner does). +DATA = "tests/file_readline_data.txt" # b"a\nbb\nccc" (no trailing newline) +DATA_CRLF = "tests/file_readline_data_crlf.txt" # b"one\r\ntwo\r\n" +DATA_LONG = "tests/file_readline_data_long.txt" # b"x" * 9000 + b"\ntail\n" +DATA_STRADDLE = "tests/file_readline_data_straddle.txt" # b"x" * 8191 + b"\r\ntail\r\n" +DATA_CR_TAIL = "tests/file_readline_data_cr_tail.txt" # b"a\nb\r" +DATA_CHUNK_END = "tests/file_readline_data_chunk_end.txt" # b"x" * 8191 + b"\ntail\n" + +# 1. one line per call, trailing newline included, "" at EOF (and stays ""). +f = open(DATA, "r") +assert f.readline() == "a\n" +assert f.readline() == "bb\n" +assert f.readline() == "ccc" +assert f.readline() == "" +assert f.readline() == "" + +# 2. size limit: at most `limit` characters, the rest stays buffered. +g = open(DATA, "r") +assert g.readline(1) == "a" +assert g.readline(0) == "" +assert g.readline(100) == "\n" +assert g.readline(2) == "bb" +assert g.readline() == "\n" +assert g.readline() == "ccc" + +# 3. \r\n is consumed as a single line ending (no spurious empty line). +# TODO: with newline=None CPython translates the terminator to "\n"; the +# decoder is not implemented yet, so the raw ending is returned for now. +h = open(DATA_CRLF, "r") +assert h.readline() == "one\r\n" +assert h.readline() == "two\r\n" +assert h.readline() == "" + +# 4. a line longer than the 8192-byte read chunk is returned whole. +i = open(DATA_LONG, "r") +line = i.readline() +assert len(line) == 9001, len(line) +assert i.readline() == "tail\n" +assert i.readline() == "" + +# 5. readlines returns the same split. +j = open(DATA, "r") +assert j.readlines() == ["a\n", "bb\n", "ccc"] + +# 6. a \r\n split across the 8192-byte chunk boundary stays one line ending +# (the first chunk ends with the \r, the \n arrives with the next chunk). +k = open(DATA_STRADDLE, "r") +line = k.readline() +assert len(line) == 8193, len(line) +assert line == "x" * 8191 + "\r\n" +assert k.readline() == "tail\r\n" +assert k.readline() == "" + +# 7. a \r\n straddling the size limit: the \r fits within the limit, the \n +# stays buffered and becomes its own line on the next call (CPython clamps +# the found line to size and pushes the remainder back). +m = open(DATA_CRLF, "r") +assert m.readline(4) == "one\r" +assert m.readline() == "\n" +assert m.readline() == "two\r\n" +assert m.readline() == "" + +# 8. an unresolved trailing \r must not stop an earlier complete line from +# being returned (on a blocking stream the read-ahead would stall), and a +# lone \r at EOF terminates the final line. +n = open(DATA_CR_TAIL, "r") +assert n.readline() == "a\n" +assert n.readline() == "b\r" +assert n.readline() == "" + +# 9. readlines keeps reading past a delimiter that lands exactly on the chunk +# boundary and drains the buffer mid-stream (the first line fills the whole +# 8192-byte chunk). +o = open(DATA_CHUNK_END, "r") +assert o.readlines() == ["x" * 8191 + "\n", "tail\n"] +p = open(DATA_CHUNK_END, "r") +assert p.readline() == "x" * 8191 + "\n" +assert p.readlines() == ["tail\n"] + +print("file_readline: ok") diff --git a/integration/tests/file_readline_data.txt b/integration/tests/file_readline_data.txt new file mode 100644 index 00000000..9decbd29 --- /dev/null +++ b/integration/tests/file_readline_data.txt @@ -0,0 +1,3 @@ +a +bb +ccc \ No newline at end of file diff --git a/integration/tests/file_readline_data_chunk_end.txt b/integration/tests/file_readline_data_chunk_end.txt new file mode 100644 index 00000000..d2e82b1a --- /dev/null +++ b/integration/tests/file_readline_data_chunk_end.txt @@ -0,0 +1,2 @@ +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +tail diff --git a/integration/tests/file_readline_data_cr_tail.txt b/integration/tests/file_readline_data_cr_tail.txt new file mode 100644 index 00000000..8fca5113 --- /dev/null +++ b/integration/tests/file_readline_data_cr_tail.txt @@ -0,0 +1,2 @@ +a +b \ No newline at end of file diff --git a/integration/tests/file_readline_data_crlf.txt b/integration/tests/file_readline_data_crlf.txt new file mode 100644 index 00000000..4e349b59 --- /dev/null +++ b/integration/tests/file_readline_data_crlf.txt @@ -0,0 +1,2 @@ +one +two diff --git a/integration/tests/file_readline_data_long.txt b/integration/tests/file_readline_data_long.txt new file mode 100644 index 00000000..d3d564fd --- /dev/null +++ b/integration/tests/file_readline_data_long.txt @@ -0,0 +1,2 @@ +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +tail diff --git a/integration/tests/file_readline_data_straddle.txt b/integration/tests/file_readline_data_straddle.txt new file mode 100644 index 00000000..3ec9b5ff --- /dev/null +++ b/integration/tests/file_readline_data_straddle.txt @@ -0,0 +1,2 @@ +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +tail diff --git a/integration/tests/print_file.py b/integration/tests/print_file.py new file mode 100644 index 00000000..866d49f2 --- /dev/null +++ b/integration/tests/print_file.py @@ -0,0 +1,37 @@ +# Regression test: print's file argument must be honoured, including when +# sys.stdout is None (previously the sys.stdout None-guard ran before the +# file= keyword was parsed, so the call silently did nothing). Also covers +# calling a Python-level flush() from print, which used to crash in +# PyBoundMethod::__call__ on the null args tuple. + +import sys + + +class Sink: + def __init__(self): + self.parts = [] + + def write(self, s): + self.parts.append(s) + + def flush(self): + pass + + +s = Sink() +print("hello", "world", file=s) +assert "".join(s.parts) == "hello world\n", s.parts + +# an explicit file= destination must win even when sys.stdout is None, +# while a plain print() must silently do nothing +stdout = sys.stdout +sys.stdout = None +s2 = Sink() +print("x", 1, file=s2, sep="-") +r = print("swallowed") +sys.stdout = stdout +assert "".join(s2.parts) == "x-1\n", s2.parts +assert r is None + +# file=None falls back to sys.stdout +print("print_file: ok", file=None) diff --git a/src/interpreter/Interpreter.cpp b/src/interpreter/Interpreter.cpp index fea3bbdc..54b7d8f4 100644 --- a/src/interpreter/Interpreter.cpp +++ b/src/interpreter/Interpreter.cpp @@ -24,7 +24,9 @@ #include "executable/bytecode/BytecodeProgram.hpp" #include "utilities.hpp" +#include #include +#include namespace fs = std::filesystem; using namespace py; @@ -186,10 +188,23 @@ void Interpreter::internal_setup(const std::string &name, std::get(io_module->symbol_table()->map().at(String{ "FileIO" })); auto *buffered_writer = std::get(io_module->symbol_table()->map().at(String{ "BufferedWriter" })); + auto *buffered_reader = + std::get(io_module->symbol_table()->map().at(String{ "BufferedReader" })); auto *text_io_wrapper = std::get(io_module->symbol_table()->map().at(String{ "TextIOWrapper" })); + auto py_stdin = + file_io->call(PyTuple::create(Number{ STDIN_FILENO }, String{ "rb" }).unwrap(), nullptr) + .and_then([buffered_reader](PyObject *stdin) { + return buffered_reader->call(PyTuple::create(stdin).unwrap(), nullptr); + }) + .and_then([text_io_wrapper](PyObject *stdin_buffer_writer) { + return text_io_wrapper->call( + PyTuple::create(stdin_buffer_writer).unwrap(), nullptr); + }); + ASSERT(py_stdin.is_ok()); auto py_stdout = - file_io->call(PyTuple::create(Number{ 1 }, String{ "wb" }).unwrap(), nullptr) + file_io->call( + PyTuple::create(Number{ STDOUT_FILENO }, String{ "wb" }).unwrap(), nullptr) .and_then([buffered_writer](PyObject *stdout) { return buffered_writer->call(PyTuple::create(stdout).unwrap(), nullptr); }) @@ -199,7 +214,8 @@ void Interpreter::internal_setup(const std::string &name, }); ASSERT(py_stdout.is_ok()); auto py_stderr = - file_io->call(PyTuple::create(Number{ 2 }, String{ "wb" }).unwrap(), nullptr) + file_io->call( + PyTuple::create(Number{ STDERR_FILENO }, String{ "wb" }).unwrap(), nullptr) .and_then([buffered_writer](PyObject *stderr) { return buffered_writer->call(PyTuple::create(stderr).unwrap(), nullptr); }) @@ -208,6 +224,7 @@ void Interpreter::internal_setup(const std::string &name, PyTuple::create(stderr_buffer_writer).unwrap(), nullptr); }); ASSERT(py_stderr.is_ok()); + sys->add_symbol(PyString::create("stdin").unwrap(), py_stdin.unwrap()); sys->add_symbol(PyString::create("stdout").unwrap(), py_stdout.unwrap()); sys->add_symbol(PyString::create("stderr").unwrap(), py_stderr.unwrap()); } diff --git a/src/runtime/PyBoundMethod.cpp b/src/runtime/PyBoundMethod.cpp index a550b92d..5a1bc862 100644 --- a/src/runtime/PyBoundMethod.cpp +++ b/src/runtime/PyBoundMethod.cpp @@ -43,9 +43,11 @@ PyResult PyBoundMethod::__call__(PyTuple *args, PyDict *kwargs) { // first create new args tuple -> (self, *args) std::vector new_args_vector; - new_args_vector.reserve(args->size() + 1); + new_args_vector.reserve((args ? args->size() : 0) + 1); new_args_vector.push_back(m_self); - for (const auto &arg : args->elements()) { new_args_vector.push_back(arg); } + if (args) { + for (const auto &arg : args->elements()) { new_args_vector.push_back(arg); } + } auto args_ = PyTuple::create(new_args_vector); if (args_.is_err()) { return args_; } return m_method->call(args_.unwrap(), kwargs); diff --git a/src/runtime/PyBytes.cpp b/src/runtime/PyBytes.cpp index edb14aa3..eabdb5ae 100644 --- a/src/runtime/PyBytes.cpp +++ b/src/runtime/PyBytes.cpp @@ -230,8 +230,10 @@ PyResult PyBytes::__getitem__(PyObject *index) } } -PyResult PyBytes::__getbuffer__(PyBuffer &view, int /*flags*/) +PyResult PyBytes::__getbuffer__(PyBuffer &view, int flags) { + // PyBUF_WRITABLE == 1; bytes objects are immutable + if (flags & 1) { return Err(type_error("cannot use bytes as a read-write buffer")); } view.obj = this; view.buf = std::make_unique>(m_value.b.data()); view.len = m_value.b.size(); diff --git a/src/runtime/PyMemoryView.cpp b/src/runtime/PyMemoryView.cpp index 5c939d9a..07133190 100644 --- a/src/runtime/PyMemoryView.cpp +++ b/src/runtime/PyMemoryView.cpp @@ -246,6 +246,13 @@ PyResult PyMemoryView::create(PyObject *object) type_error("memoryview: a bytes-like object is required, not {}", object->type()->name())); } +PyResult PyMemoryView::create(PyBuffer buffer) +{ + auto obj = VirtualMachine::the().heap().allocate(std::move(buffer)); + if (!obj) { return Err(memory_error(sizeof(PyMemoryView))); } + return Ok(obj); +} + PyResult PyMemoryView::__new__(const PyType *, PyTuple *args, PyDict *kwargs) { auto result = PyArgsParser::unpack_tuple(args, @@ -336,9 +343,13 @@ PyResult PyMemoryView::tolist() PyResult PyMemoryView::__repr__() const { return PyString::create(to_string()); } -PyResult PyMemoryView::__getbuffer__(PyBuffer &view, int /*flags*/) +PyResult PyMemoryView::__getbuffer__(PyBuffer &view, int flags) { - // TODO: validate flags + // PyBUF_WRITABLE == 1 + if ((flags & 1) && m_view.readonly) { + return Err(type_error("cannot use a read-only memoryview as a read-write buffer")); + } + // TODO: validate the remaining flags view = PyBuffer{ .buf = m_view.buf->view(), .obj = this, diff --git a/src/runtime/PyMemoryView.hpp b/src/runtime/PyMemoryView.hpp index b5d21871..1d5c6972 100644 --- a/src/runtime/PyMemoryView.hpp +++ b/src/runtime/PyMemoryView.hpp @@ -24,6 +24,7 @@ class PyMemoryView : public PyBaseObject public: static PyResult create(PyObject *object); + static PyResult create(PyBuffer buffer); static PyResult __new__(const PyType *type, PyTuple *args, PyDict *kwargs); PyResult __repr__() const; diff --git a/src/runtime/modules/BuiltinsModule.cpp b/src/runtime/modules/BuiltinsModule.cpp index 05232d6e..32f9066c 100644 --- a/src/runtime/modules/BuiltinsModule.cpp +++ b/src/runtime/modules/BuiltinsModule.cpp @@ -71,19 +71,34 @@ PyResult print(const PyTuple *args, const PyDict *kwargs, Interprete { auto *separator = PyString::create(" ").unwrap(); auto *end = PyString::create("\n").unwrap(); - // TODO: handle error case? - PyObject *file = - PyObject::from(interpreter.get_imported_module(PyString::create("sys").unwrap()) - ->symbol_table() - ->map() - .at(String{ "stdout" })) - .unwrap(); - // sys.stdout may be None when FILE* stdout isn't connected - if (!file || file == py_none()) { return Ok(py_none()); } + // an explicit file argument wins; file=None (or no file argument) falls + // back to sys.stdout + PyObject *file = nullptr; + static const Value file_keyword = String{ "file" }; + if (kwargs) { + if (auto it = kwargs->map().find(file_keyword); it != kwargs->map().end()) { + auto file_ = PyObject::from(it->second); + if (file_.is_err()) { return file_; } + if (file_.unwrap() != py_none()) { file = file_.unwrap(); } + } + } + if (!file) { + // TODO: handle error case? + file = PyObject::from(interpreter.get_imported_module(PyString::create("sys").unwrap()) + ->symbol_table() + ->map() + .at(String{ "stdout" })) + .unwrap(); + // sys.stdout may be None when FILE* stdout isn't connected + if (!file || file == py_none()) { return Ok(py_none()); } + } + // TODO: flush should be false, but for now we keep it as true, since there is no flush at + // interpreter shutdown bool flush = true; if (kwargs) { static const Value separator_keyword = String{ "sep" }; static const Value end_keyword = String{ "end" }; + static const Value flush_keyword = String{ "flush" }; if (auto it = kwargs->map().find(separator_keyword); it != kwargs->map().end()) { auto maybe_str = it->second; @@ -107,6 +122,13 @@ PyResult print(const PyTuple *args, const PyDict *kwargs, Interprete } end = PyString::create(std::get(maybe_str).s).unwrap(); } + if (auto it = kwargs->map().find(flush_keyword); it != kwargs->map().end()) { + auto flush_ = PyObject::from(it->second); + if (flush_.is_err()) { return flush_; } + const auto truthy = flush_.unwrap()->true_(); + if (truthy.is_err()) { return Err(truthy.unwrap_err()); } + flush = truthy.unwrap(); + } } auto file_write_ = file->get_method(PyString::create("write").unwrap()); if (file_write_.is_err()) { return file_write_; } diff --git a/src/runtime/modules/IOModule.cpp b/src/runtime/modules/IOModule.cpp index cdc01d17..80dae86a 100644 --- a/src/runtime/modules/IOModule.cpp +++ b/src/runtime/modules/IOModule.cpp @@ -22,8 +22,10 @@ #include "runtime/types/builtin.hpp" #include "utilities.hpp" #include "vm/VM.hpp" +#include #include #include +#include #include #include @@ -557,7 +559,7 @@ class RawIOBase : public IOBase }); } - PyResult readinto() const + PyResult readinto(PyObject *) const { return Err(not_implemented_error("_RawIOBase.readinto")); } @@ -684,7 +686,7 @@ class BufferedIOBase : public IOBase data_bytes.size(), static_cast(buffer.buf->get_buffer())); - return data; + return PyInteger::create(static_cast(len)); } PyResult readinto(PyBuffer &buffer) const @@ -984,6 +986,29 @@ struct Buffered } } + PyResult read1(int64_t n) + { + if (auto err = check_initialized(); err.is_err()) return Err(err.unwrap_err()); + + if (n < 0) { + // TODO: determine actual buffer size + n = 4096; + } + + if (is_closed()) { return Err(value_error("read of closed file")); } + + if (n == 0) { return PyBytes::create(); } + + const auto have = readahead(); + if (have > 0) { return static_cast(this)->readfast(std::min(n, have)); } + Bytes b; + b.b.resize(n); + auto result = static_cast(this)->raw_read(std::span{ b.b.begin(), b.b.end() }); + if (result.is_err()) { return Err(result.unwrap_err()); } + b.b.resize(result.unwrap()); + return PyBytes::create(std::move(b)); + } + void visit_graph_buffered(Cell::Visitor &visitor) { if (raw) { visitor.visit(*raw); } @@ -1130,16 +1155,69 @@ class BufferedReader if (static_cast(n) <= current_size) { ASSERT(buffer); std::vector data; - data.resize(current_size); - buffer->sgetn(::bit_cast(data.begin().base()), current_size); + data.resize(n); + buffer->sgetn(::bit_cast(data.begin().base()), n); return PyBytes::create(Bytes{ std::move(data) }); } return Ok(py_none()); } - PyResult readgeneric(size_t) + PyResult raw_read(std::span data) { - return Err(not_implemented_error("BufferedReader.readgeneric")); + PyBuffer buffer{ + .buf = std::make_unique>(data.data()), + .obj = nullptr, + .len = static_cast(data.size()), + .itemsize = 1, + .readonly = false, + .ndim = 1, + .format = "B", + }; + auto memobj = PyMemoryView::create(std::move(buffer)).unwrap(); + return raw->get_method(PyString::create("readinto").unwrap()) + .and_then([memobj](PyObject *read_into) { + return read_into->call(PyTuple::create(memobj).unwrap(), nullptr); + }) + .and_then([](PyObject *n) -> PyResult { + if (!as(n)) { + return Err(value_error("expected 'int' got '{}'", n->type()->name())); + } + return Ok(as(n)->as_size_t()); + }); + } + + PyResult readgeneric(size_t n) + { + if (auto result = readfast(n); result.is_ok() && result.unwrap() != py_none()) { + return result; + } + Bytes bytes; + bytes.b.resize(n); + const auto current_size = readahead(); + auto remaining = n; + size_t written = 0; + if (current_size > 0) { + buffer->sgetn(bit_cast(bytes.b.data()), current_size); + remaining -= current_size; + written += current_size; + } + + // TODO: read and writable buffer? + // if (auto result = flush_and_rewind(); result.is_err()) { return result; } + + while (remaining > 0) { + std::span view{ bytes.b.data() + written, remaining }; + if (auto result = raw_read(view); result.is_err()) { + return Err(result.unwrap_err()); + } else { + if (result.unwrap() == 0) { break; } + ASSERT(result.unwrap() <= remaining); + remaining -= result.unwrap(); + written += result.unwrap(); + } + } + bytes.b.resize(written); + return PyBytes::create(std::move(bytes)); } PyResult __repr__() const @@ -1194,9 +1272,9 @@ class BufferedReader ASSERT(!kwargs || kwargs->map().empty()); int64_t n = -1; if (args && args->elements().size() > 1) { - return Err(value_error( - "BaseIO.readlines expected at most one argument (got {})", - args->elements().size())); + return Err( + value_error("read expected at most one argument (got {})", + args->elements().size())); } else if (args && args->elements().size() == 1) { auto arg0 = PyObject::from(args->elements()[0]); if (arg0.is_err()) return arg0; @@ -1211,9 +1289,31 @@ class BufferedReader } return static_cast *>(self)->read(n); }) + .def("read1", + [](BufferedReader *self, + PyTuple *args, + PyDict *kwargs) -> PyResult { + ASSERT(!kwargs || kwargs->map().empty()); + int64_t n = -1; + if (args && args->elements().size() > 1) { + return Err(value_error("read1 at most one argument (got {})", + args->elements().size())); + } else if (args && args->elements().size() == 1) { + auto arg0 = PyObject::from(args->elements()[0]); + if (arg0.is_err()) return arg0; + if (!as(arg0.unwrap()) && arg0.unwrap() != py_none()) { + return Err( + type_error("argument should be integer or None, not '{}'", + arg0.unwrap()->type()->name())); + } + if (arg0.unwrap() != py_none()) { + n = as(arg0.unwrap())->as_i64(); + } + } + return static_cast *>(self)->read1(n); + }) .def("readall", &BufferedReader::readall) // .def("peek", &BufferedReader::peek) - // .def("read1", &BufferedReader::read1) // .def("readinto", &BufferedReader::readinto) // .def("readinto1", &BufferedReader::readinto1) // .def("readline", &BufferedReader::readline) @@ -2112,8 +2212,6 @@ class FileIO : public RawIOBase { if (!m_filebuffer.is_open()) { return Err(value_error("I/O operation on closed file")); } - m_filebuffer.pubseekpos(0, std::ios::in); - // TODO: if (m_filestream.fail()) { TODO(); } const auto initial_position = m_filebuffer.pubseekoff(0, std::ios::cur, std::ios::in); if (initial_position == -1) { TODO(); } @@ -2121,7 +2219,7 @@ class FileIO : public RawIOBase const auto end_position = m_filebuffer.pubseekoff(0, std::ios::end, std::ios::in); if (end_position == -1) { TODO(); } - m_filebuffer.pubseekpos(0, std::ios::in); + m_filebuffer.pubseekpos(initial_position, std::ios::in); // TODO: if (m_filestream.fail()) { TODO(); } const auto file_size = end_position - initial_position; @@ -2172,6 +2270,29 @@ class FileIO : public RawIOBase return PyInteger::create(written); } + PyResult readinto(PyObject *bytes) + { + PyBuffer buffer; + if (auto result = bytes->get_buffer(buffer, 1); result.is_err()) { + return Err( + type_error("readinto() argument must be read-write bytes-like object, not {}", + bytes->type()->name())); + } + if (!buffer.is_ccontiguous()) { + return Err(type_error( + "readinto() argument must be contiguous buffer, not {}", bytes->type()->name())); + } + auto *data = static_cast(buffer.buf->get_buffer()); + // prime the buffer + m_filebuffer.sgetc(); + const auto to_read = std::min(buffer.len, m_filebuffer.in_avail()); + if (to_read > 0) { + const auto read = m_filebuffer.sgetn(data, to_read); + return PyInteger::create(read); + } + return PyInteger::create(0); + } + static PyType *register_type(PyModule *module) { if (!s_io_fileio) { @@ -2180,6 +2301,7 @@ class FileIO : public RawIOBase .def("write", &FileIO::write) .def("close", &FileIO::close) .def("flush", &FileIO::flush) + .def("readinto", &FileIO::readinto) .finalize(); } module->add_symbol(PyString::create("FileIO").unwrap(), s_io_fileio); @@ -2828,9 +2950,15 @@ class TextIOWrapper : public TextIOBase bool m_write_through; std::optional m_buffer_bytes; - size_t m_position; + size_t m_position{ 0 }; std::optional m_pending_bytes;// bytes to be written - size_t m_pending_bytes_count; + size_t m_pending_bytes_count{ 0 }; + + size_t m_chunk_size{ 8192 }; + + // line terminators recognised by readline; universal newlines for now + // TODO: derive the set from the newline constructor argument + std::vector m_line_delimiters{ "\r\n", "\r", "\n" }; TextIOWrapper(PyType *type) : TextIOBase(type) {} @@ -2963,51 +3091,129 @@ class TextIOWrapper : public TextIOBase write_through == py_true()); } + PyResult read_chunk(size_t size_hint) + { + // TODO: handle decoding + const auto chunk_size = std::max(size_hint, m_chunk_size); + auto input_chunk = + this->m_buffer->get_method(PyString::create("read1").unwrap()) + .and_then([chunk_size](auto *read) { + return read->call(PyTuple::create(Number{ chunk_size }).unwrap(), nullptr); + }); + if (input_chunk.is_err()) { return Err(input_chunk.unwrap_err()); } + PyBuffer out; + if (auto result = input_chunk.unwrap()->get_buffer(out, 0); result.is_err()) { + return Err(result.unwrap_err()); + } + + if (out.len == 0) { return Ok(false); } + + auto *chunk_bytes = static_cast(out.buf->get_buffer()); + if (m_buffer_bytes.has_value()) { + m_buffer_bytes->b.insert(m_buffer_bytes->b.end(), chunk_bytes, chunk_bytes + out.len); + } else { + m_buffer_bytes = Bytes{ .b = { chunk_bytes, chunk_bytes + out.len } }; + } + return Ok(true); + } + PyResult readline(PyTuple *args, PyDict *kwargs) { - ASSERT(!args || args->size() == 0); - ASSERT(!kwargs || kwargs->map().empty()); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "readline", + std::integral_constant{}, + std::integral_constant{}, + std::numeric_limits::max()); + if (result.is_err()) { return Err(result.unwrap_err()); } + auto [limit] = result.unwrap(); + return readline_impl(limit); + } - if (auto err = setup_buffer(); err.is_err()) { return Err(err.unwrap_err()); } + PyResult readline_impl(size_t limit) + { + if (auto result = writeflush(); result.is_err()) { return Err(result.unwrap_err()); } - // Lines in the input can end in \'\\n\', \'\\r\', or \'\\r\\n\' - auto is_line_end = [](std::string_view remaining) { - if (remaining.starts_with("\n") || remaining.starts_with("\r")) { - return 1; - } else if (remaining.starts_with("\r\n")) { - return 2; + // length of the longest line delimiter at the start of text, 0 if none + auto match_delimiter = [this](std::string_view text) -> size_t { + size_t longest = 0; + for (const auto &delimiter : m_line_delimiters) { + if (delimiter.size() > longest && text.starts_with(delimiter)) { + longest = delimiter.size(); + } } - return 0; + return longest; + }; + // text runs to the end of the available input; a proper prefix of a + // delimiter cannot be classified until more input arrives + auto is_delimiter_prefix = [this](std::string_view text) { + for (const auto &delimiter : m_line_delimiters) { + if (delimiter.size() > text.size() + && std::string_view{ delimiter }.starts_with(text)) { + return true; + } + } + return false; }; - - std::string_view remaining{ bit_cast(m_buffer_bytes->b.data()) + m_position, - m_buffer_bytes->b.size() - m_position }; - - // TODO: use utf8 codepoints std::string line; - while (!remaining.empty()) { - if (auto chars = is_line_end(remaining)) { - m_position += chars; - line.insert(line.end(), remaining.begin(), remaining.begin() + chars); - return PyString::create(line); + bool found_nl = false; + bool eof = false; + bool need_more = false; + while (!found_nl && line.size() < limit) { + if (need_more || !m_buffer_bytes.has_value() || m_buffer_bytes->b.empty()) { + need_more = false; + auto chunk = read_chunk(0); + if (chunk.is_err()) { return Err(chunk.unwrap_err()); } + eof = !chunk.unwrap(); + if (eof && (!m_buffer_bytes.has_value() || m_buffer_bytes->b.empty())) { break; } } - line.push_back(remaining[0]); - remaining = remaining.substr(1); - m_position++; - } + std::string_view remaining{ bit_cast(m_buffer_bytes->b.data()) + + m_position, + m_buffer_bytes->b.size() - m_position }; + + // TODO: use utf8 codepoints + const char *start = remaining.data(); + size_t len = 0; + const size_t budget = limit - line.size(); + const size_t scan_limit = std::min(budget, remaining.size()); + for (; len < scan_limit; ++len) { + // the rest of the buffer may hold an incomplete delimiter (e.g. the + // '\r' of a "\r\n" straddling the chunk boundary); fetch more input + // before classifying it, unless the size limit would clamp the + // consumed length to the same result either way + if (!eof && len + 1 < budget && is_delimiter_prefix(remaining)) { + need_more = true; + break; + } + if (auto chars = match_delimiter(remaining)) { + // the delimiter may extend past the caller's limit (e.g. a + // "\r\n" straddling it); consume only up to the limit and + // leave the rest buffered for the next call + len = std::min(len + chars, budget); + found_nl = true; + break; + } + remaining = remaining.substr(1); + } + line.insert(line.end(), start, start + len); + m_buffer_bytes->b.erase( + m_buffer_bytes->b.begin(), m_buffer_bytes->b.begin() + m_position + len); + m_position = 0; + } return PyString::create(std::move(line)); } PyResult readlines() { - if (auto err = setup_buffer(); err.is_err()) { return Err(err.unwrap_err()); } auto result_ = PyList::create(); if (result_.is_err()) { return result_; } auto *result = result_.unwrap(); - while (m_position < m_buffer_bytes->b.size()) { - auto line = readline(nullptr, nullptr); + while (true) { + auto line = readline_impl(std::numeric_limits::max()); if (line.is_err()) { return line; } + // readline only returns an empty string at EOF + if (as(line.unwrap())->value().empty()) { break; } if (auto appended = result->append(line.unwrap()); appended.is_err()) { return Err(appended.unwrap_err()); } @@ -3030,6 +3236,7 @@ class TextIOWrapper : public TextIOBase return Ok(0); } + PyResult write(PyTuple *args, PyDict *kwargs) { ASSERT(!kwargs || kwargs->map().empty()); @@ -3103,22 +3310,6 @@ class TextIOWrapper : public TextIOBase return Ok(1); } - - PyResult setup_buffer() - { - // TODO: read in chunks! - if (!m_buffer_bytes.has_value()) { - auto buffer_ = - m_buffer->get_method(PyString::create("readall").unwrap()) - .and_then([](auto *readall) { return readall->call(nullptr, nullptr); }); - if (buffer_.is_err()) { return Err(buffer_.unwrap_err()); } - auto *buffer = buffer_.unwrap(); - - ASSERT(buffer->type()->issubclass(types::bytes())); - m_buffer_bytes = Bytes{ static_cast(*buffer).value() }; - } - return Ok(std::monostate{}); - } }; PyResult open(PyObject *file, const std::string &mode)