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
9 changes: 6 additions & 3 deletions src/context_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ impl BufferedLine {
/// provide a more optimized `ContextBuffer` when `mmap()` is available.
pub struct ContextBuffer {
slots: Vec<BufferedLine>,
/// Retention limit; `slots` is rounded up to a power of two.
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()
Comment thread
sylvestre marked this conversation as resolved.
Expand All @@ -60,8 +62,9 @@ impl ContextBuffer {
line_number: 0,
byte_offset: 0,
};
len
slots
],
capacity,
head: 0,
len: 0,
}
Expand Down Expand Up @@ -90,7 +93,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
51 changes: 51 additions & 0 deletions tests/test_grep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,57 @@ 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"])
Comment thread
sylvestre marked this conversation as resolved.
.pipe_in(input)
.succeeds()
.stdout_only(want);
}

// `-C` sets the before-context through the same path.
let (_s, mut c) = ucmd();
c.args(&["-C", "3", "MM"])
.pipe_in(input)
.succeeds()
.stdout_only("08\n09\n10\nMM\n");

// 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);
}

#[test]
fn before_context_rejects_invalid_count() {
// 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("MM\n")
.fails_with_code(2);
}
}

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