diff --git a/cvise/passes/lines.py b/cvise/passes/lines.py index f61e449d7..ddeedf093 100644 --- a/cvise/passes/lines.py +++ b/cvise/passes/lines.py @@ -39,11 +39,18 @@ def generate_hints(self, test_case: Path, process_event_notifier: ProcessEventNo def _generate_hints_for_text_lines(self, input_path: Path, path_id: int | None, hints: list[Hint]) -> None: """Generate a hint per each line in the input as written.""" with open(input_path, 'rb') as in_file: + lines = in_file.readlines() file_pos = 0 - for line in in_file: + for i, line in enumerate(lines): end_pos = file_pos + len(line) - hints.append(Hint(patches=(Patch(left=file_pos, right=end_pos, path=path_id),))) - file_pos = end_pos + + # Do not consume the file-terminating newline. + if i == len(lines) - 1 and line.endswith(b'\n'): + end_pos -= 1 + + if end_pos > file_pos: + hints.append(Hint(patches=(Patch(left=file_pos, right=end_pos, path=path_id),))) + file_pos += len(line) def _generate_topformflat_hints( self, diff --git a/cvise/tests/test_lines.py b/cvise/tests/test_lines.py index 929c34f9c..f61c3fb0b 100644 --- a/cvise/tests/test_lines.py +++ b/cvise/tests/test_lines.py @@ -56,6 +56,19 @@ def is_valid_brace_sequence(s: bytes) -> bool: return balance == 0 +def test_trailing_newline_preserved(tmp_path: Path, input_path: Path): + """Test that removing the last line doesn't eat the trailing newline.""" + input_path.write_bytes(b'int x = 2;\nint main() {\n}\n') + p, state = init_pass('None', tmp_path, input_path) + all_transforms = collect_all_transforms(p, state, input_path) + + for transform in all_transforms: + assert transform.endswith(b'\n') + + # Deleting the last line `}\n` now leaves the `\n` behind. + assert b'int x = 2;\nint main() {\n\n' in all_transforms + + def test_func_namespace_level0(tmp_path: Path, input_path: Path): """Test that arg=0 deletes top-level functions and namespaces.""" input_path.write_text( @@ -808,10 +821,10 @@ def test_multi_file_arg_none(tmp_path: Path): all_transforms = collect_all_transforms_dir(p, state, input_dir) assert (('bar.h', b'x = 1;\n'), ('foo.cc', b'char\nbar() {}\n')) in all_transforms - assert (('bar.h', b'int\n'), ('foo.cc', b'char\nbar() {}\n')) in all_transforms + assert (('bar.h', b'int\n\n'), ('foo.cc', b'char\nbar() {}\n')) in all_transforms assert (('bar.h', b'int\nx = 1;\n'), ('foo.cc', b'bar() {}\n')) in all_transforms - assert (('bar.h', b'int\nx = 1;\n'), ('foo.cc', b'char\n')) in all_transforms - assert (('bar.h', b''), ('foo.cc', b'')) in all_transforms + assert (('bar.h', b'int\nx = 1;\n'), ('foo.cc', b'char\n\n')) in all_transforms + assert (('bar.h', b'\n'), ('foo.cc', b'\n')) in all_transforms def test_multi_file_arg_0(tmp_path: Path):