import pytest from mirage.commands.builtin.generic.cat import cat async def _drain(gen): return b"".join([c async for c in gen]) @pytest.mark.asyncio async def test_cat_passthrough_from_bytes(): out = await _drain(cat(b"hello\nworld\n")) assert out == b"hello\nworld\n" @pytest.mark.asyncio async def test_cat_passthrough_from_stream_preserves_chunks(): async def src(): yield b"hel" yield b"lo\nwo" yield b"rld\n" chunks = [c async for c in cat(src())] assert chunks == [b"hel", b"lo\nwo", b"rld\n"] @pytest.mark.asyncio async def test_cat_number_lines(): out = await _drain(cat(b"a\nb\nc\n", number_lines=True)) assert out == b" 1\ta\n 2\tb\n 3\tc\n" @pytest.mark.asyncio async def test_cat_number_lines_across_chunk_boundaries(): async def src(): yield b"a\nb" yield b"\nc\n" out = await _drain(cat(src(), number_lines=True)) assert out == b" 1\ta\n 2\tb\n 3\tc\n" @pytest.mark.asyncio async def test_cat_show_ends(): out = await _drain(cat(b"a\nb\n", show_ends=True)) assert out == b"a$\nb$\n" @pytest.mark.asyncio async def test_cat_squeeze_blank(): out = await _drain(cat(b"a\n\n\n\nb\n", squeeze_blank=True)) assert out == b"a\n\nb\n" @pytest.mark.asyncio async def test_cat_no_trailing_newline_preserved(): """POSIX: `printf "x" | cat -n` emits no trailing newline.""" out = await _drain(cat(b"hello", number_lines=True)) assert out == b" 1\thello" @pytest.mark.asyncio async def test_cat_passthrough_no_trailing_newline(): """No-flag cat must not add a trailing newline.""" out = await _drain(cat(b"hello")) assert out == b"hello" @pytest.mark.asyncio async def test_cat_number_lines_multidigit_alignment(): """POSIX format is `%6d\\t` — width 6, right-justified. Old MIRAGE used a 5-space prefix which broke alignment for line numbers >= 10.""" body = b"".join(f"line{i}\n".encode() for i in range(1, 13)) out = await _drain(cat(body, number_lines=True)) lines = out.split(b"\n") assert lines[0] == b" 1\tline1" assert lines[8] == b" 9\tline9" assert lines[9] == b" 10\tline10" assert lines[11] == b" 12\tline12" @pytest.mark.asyncio async def test_cat_show_ends_no_trailing_newline(): """cat -E on input without trailing newline must not emit final `$\\n`.""" out = await _drain(cat(b"hello", show_ends=True)) assert out == b"hello" @pytest.mark.asyncio async def test_cat_combined_n_E_s(): """Combined flags: number all kept lines, show ends, squeeze blanks.""" out = await _drain( cat(b"a\n\n\n\nb\n", number_lines=True, show_ends=True, squeeze_blank=True)) # Kept lines: "a", "" (first blank, kept), "b". Numbered 1, 2, 3. assert out == b" 1\ta$\n 2\t$\n 3\tb$\n" @pytest.mark.asyncio async def test_cat_empty_input_emits_nothing_with_flags(): out = await _drain(cat(b"", number_lines=True)) assert out == b"" @pytest.mark.asyncio async def test_cat_only_newlines(): """Three blank lines numbered 1, 2, 3.""" out = await _drain(cat(b"\n\n\n", number_lines=True)) assert out == b" 1\t\n 2\t\n 3\t\n" @pytest.mark.asyncio async def test_cat_squeeze_blank_preserves_first_blank(): out = await _drain(cat(b"\n\n\nx\n", squeeze_blank=True)) assert out == b"\nx\n" @pytest.mark.asyncio async def test_cat_empty_input_passthrough(): out = await _drain(cat(b"")) assert out == b"" @pytest.mark.asyncio async def test_cat_binary_passthrough_full_byte_range(): """Full 0..255 byte range must pass through unchanged (no flags).""" data = bytes(range(256)) out = await _drain(cat(data)) assert out == data @pytest.mark.asyncio async def test_cat_binary_with_show_ends_marks_only_newlines(): """show_ends should only mark 0x0A bytes, not other binary bytes.""" data = b"\x00\x01\n\x02\x03\n" out = await _drain(cat(data, show_ends=True)) assert out == b"\x00\x01$\n\x02\x03$\n" @pytest.mark.asyncio async def test_cat_number_lines_chunked_one_byte_at_a_time(): """Worst-case chunking: every byte its own chunk. Result must match unbuffered input exactly.""" async def src(): for byte in b"a\nbb\nccc\n": yield bytes([byte]) out = await _drain(cat(src(), number_lines=True)) assert out == b" 1\ta\n 2\tbb\n 3\tccc\n" @pytest.mark.asyncio async def test_cat_show_tabs_renders_caret_i(): out = b"".join([c async for c in cat(b"a\tb\nx\n", show_tabs=True)]) assert out == b"a^Ib\nx\n" @pytest.mark.asyncio async def test_cat_show_all_combines_tabs_and_ends(): out = b"".join([ c async for c in cat(b"a\tb\nx\n", show_tabs=True, show_ends=True, show_nonprinting=True) ]) assert out == b"a^Ib$\nx$\n" @pytest.mark.asyncio async def test_cat_show_nonprinting_caret_and_meta_notation(): out = b"".join( [c async for c in cat(b"\x01\x7f\xff\n", show_nonprinting=True)]) assert out == b"^A^?M-^?\n"