Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/context_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,17 @@ impl BufferedLine {
/// provide a more optimized `ContextBuffer` when `mmap()` is available.
pub struct ContextBuffer {
slots: Vec<BufferedLine>,
/// Number of lines to retain — the requested context size. The slot count is
/// rounded up to a power of two for cheap `& mask` indexing, so it must not be
/// used as the retention limit.
capacity: usize,
head: usize,
len: usize,
}

impl ContextBuffer {
pub fn new(capacity: usize) -> Self {
let len = if capacity == 0 {
let slots = if capacity == 0 {
0
} else {
capacity.next_power_of_two()
Expand All @@ -60,8 +64,9 @@ impl ContextBuffer {
line_number: 0,
byte_offset: 0,
};
len
slots
],
capacity,
head: 0,
len: 0,
}
Expand Down Expand Up @@ -90,7 +95,7 @@ impl ContextBuffer {
slot.byte_offset = byte_offset;

self.head = self.head.wrapping_add(1);
self.len = (self.len + 1).min(self.slots.len());
self.len = (self.len + 1).min(self.capacity);
}

pub fn drain_iter(&mut self) -> impl Iterator<Item = &BufferedLine> {
Expand Down
39 changes: 39 additions & 0 deletions tests/test_grep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,45 @@ fn after_before_combined_context() {
.stdout_only("b\nMATCH\nc\n");
}

#[test]
fn before_context_count_is_exact_not_rounded() {
// `-B N` must retain exactly N preceding lines. The ring buffer rounds its
// slot count up to a power of two, so a non-power-of-two N (3, 5, 6, 7) must
// not leak extra context lines.
let input = "01\n02\n03\n04\n05\n06\n07\n08\n09\n10\nMM\n";

let expected = [
("0", "MM\n"),
("1", "10\nMM\n"),
("2", "09\n10\nMM\n"),
("3", "08\n09\n10\nMM\n"),
("4", "07\n08\n09\n10\nMM\n"),
("5", "06\n07\n08\n09\n10\nMM\n"),
("6", "05\n06\n07\n08\n09\n10\nMM\n"),
("7", "04\n05\n06\n07\n08\n09\n10\nMM\n"),
];
for (n, want) in expected {
let (_s, mut c) = ucmd();
c.args(&["-B", n, "MM"])
.pipe_in(input)
.succeeds()
.stdout_only(want);
}

// A count larger than the available lines just caps at what is there.
let (_s, mut c) = ucmd();
c.args(&["-B", "1000000", "MM"])
.pipe_in(input)
.succeeds()
.stdout_only(input);

// Non-numeric, negative, and fractional counts are rejected with exit code 2.
for bad in ["abc", "-1", "2.5"] {
let (_s, mut c) = ucmd();
c.args(&["-B", bad, "MM"]).pipe_in(input).fails_with_code(2);
}
}

#[test]
fn num_shorthand_is_context() {
// `-2` is shorthand for `-C 2`.
Expand Down
Loading