-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
os: add File.set_buffer and related helpers + tests (#20661)
- Loading branch information
Showing
2 changed files
with
129 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
module os | ||
|
||
fn C.setvbuf(stream &C.FILE, buffer &char, mode int, size usize) int | ||
|
||
// FileBufferMode describes the available buffering modes for an os.File: unbuffered, line buffered and block/fully buffered. | ||
// Normally all files are block buffered. If a stream refers to a terminal (as stdout normally does), it is line buffered. | ||
// The standard error stream stderr is always unbuffered by default. | ||
pub enum FileBufferMode { | ||
fully_buffered = C._IOFBF // many characters are saved up and written as a block | ||
line_buffered = C._IOLBF // characters are saved up until a newline is output or input is read from any stream attached to a terminal device (typically stdin) | ||
not_buffered = C._IONBF // information appears on the destination file or terminal as soon as it is written | ||
} | ||
|
||
// setvbuf sets the buffer and buffering mode for the given file `f`. See also os.FileBufferMode. | ||
// It returns 0 on success. It returns nonzero on failure (the mode is invalid, or the request cannot be honored). | ||
// Except for unbuffered files, the buffer argument should point to a buffer at least size bytes long; this buffer will be used instead of the current buffer. | ||
// If the argument buffer is nil, only the mode is affected; a new buffer will be allocated on the next read or write operation. | ||
// Note: make sure, that the space, that buffer points to (when != 0), still exists by the time the file stream is closed, which also happens at program termination. | ||
// Note: f.setvbuf() may be used only after opening a file stream, and before any other operations have been performed on it. | ||
@[unsafe] | ||
pub fn (mut f File) setvbuf(buffer &char, mode FileBufferMode, size usize) int { | ||
return C.setvbuf(f.cfile, buffer, int(mode), size) | ||
} | ||
|
||
// set_buffer sets the buffer for the file, and the file buffering mode (see also os.FileBufferMode). | ||
// Unlike File.setvbuf, it allows you to pass an existing V []u8 array directly. | ||
// Note: f.set_buffer() may be used only after opening a file stream, and before any other operations have been performed on it. | ||
pub fn (mut f File) set_buffer(mut buffer []u8, mode FileBufferMode) int { | ||
return unsafe { f.setvbuf(&char(buffer.data), mode, usize(buffer.len)) } | ||
} | ||
|
||
// set_line_buffered sets the file buffering mode to FileBufferMode.line_buffered. | ||
// Note: f.set_line_buffered() may be used only after opening a file stream, and before any other operations have been performed on it. | ||
pub fn (mut f File) set_line_buffered() { | ||
unsafe { f.setvbuf(&char(nil), .line_buffered, usize(0)) } | ||
} | ||
|
||
// set_unbuffered sets the file buffering mode to FileBufferMode.not_buffered. | ||
// Note: f.set_unbuffered() may be used only after opening a file stream, and before any other operations have been performed on it. | ||
pub fn (mut f File) set_unbuffered() { | ||
unsafe { f.setvbuf(&char(nil), .not_buffered, usize(0)) } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import os | ||
|
||
const tfolder = os.join_path(os.vtmp_dir(), 'tests', 'os_file_buffering_test') | ||
|
||
fn testsuite_begin() { | ||
os.rmdir_all(tfolder) or {} | ||
assert !os.is_dir(tfolder) | ||
os.mkdir_all(tfolder)! | ||
os.chdir(tfolder)! | ||
assert os.is_dir(tfolder) | ||
} | ||
|
||
fn testsuite_end() { | ||
os.rmdir_all(tfolder) or {} | ||
} | ||
|
||
fn test_set_buffer_line_buffered() { | ||
dump(@LOCATION) | ||
mut buf := []u8{len: 25} | ||
dump(buf) | ||
mut wfile := os.open_file('text.txt', 'wb', 0o666)! | ||
wfile.set_buffer(mut buf, .line_buffered) | ||
wfile.write_string('----------------------------------\n')! | ||
for line in ['hello\n', 'world\n', 'hi\n'] { | ||
wfile.write_string(line)! | ||
wfile.flush() | ||
dump(buf) | ||
print(buf.bytestr()) | ||
// assert buf.bytestr().contains(line) // this works on GLIBC, but fails on MUSL. | ||
unsafe { buf.reset() } | ||
} | ||
wfile.close() | ||
// | ||
content := os.read_lines('text.txt')! | ||
dump(content) | ||
assert content == ['----------------------------------', 'hello', 'world', 'hi'] | ||
} | ||
|
||
fn test_set_buffer_fully_buffered() { | ||
dump(@LOCATION) | ||
mut buf := []u8{len: 30} | ||
dump(buf) | ||
mut wfile := os.open_file('text.txt', 'wb', 0o666)! | ||
wfile.set_buffer(mut buf, .fully_buffered) | ||
// Ubuntu GLIBC 2.31 seems to not use the buffer for the first write call, but it does write to the buffer first for the subsequent ones. | ||
// MUSL (detecting the MUSL version is deliberately made hard by its authors, because of course it is :-( ...), will skip the first 8 bytes | ||
// of the buffer, and write everything after those. | ||
wfile.write_string('S')! | ||
wfile.write_string('---\n')! | ||
dump(buf) | ||
for line in ['hello\n', 'world\n', 'hi\n'] { | ||
wfile.write_string(line)! | ||
dump(buf) | ||
// print(buf.bytestr()) | ||
} | ||
wfile.close() | ||
dump(buf) | ||
// assert buf.bytestr().starts_with('---\nhello\nworld\nhi\n') // works on GLIBC, fails on MUSL | ||
assert buf.bytestr().contains('---\nhello\nworld\n') | ||
// | ||
content := os.read_lines('text.txt')! | ||
dump(content) | ||
assert content == ['S---', 'hello', 'world', 'hi'] | ||
} | ||
|
||
fn test_set_unbuffered() { | ||
dump(@LOCATION) | ||
mut buf := []u8{len: 30} | ||
dump(buf) | ||
mut wfile := os.open_file('text.txt', 'wb', 0o666)! | ||
wfile.set_buffer(mut buf, .not_buffered) | ||
wfile.write_string('S')! | ||
wfile.write_string('---\n')! | ||
dump(buf) | ||
for line in ['hello\n', 'world\n', 'hi\n'] { | ||
wfile.write_string(line)! | ||
dump(buf) | ||
// print(buf.bytestr()) | ||
} | ||
wfile.close() | ||
// dump(buf.bytestr()) | ||
assert buf.all(it == 0) | ||
// | ||
content := os.read_lines('text.txt')! | ||
dump(content) | ||
assert content == ['S---', 'hello', 'world', 'hi'] | ||
} |