Files
Zecheng Zhang 8ad5ec2a4a fix: printf byte escapes and Google Sheets cell values (#714)
* fix(shell): write a hex or octal escape as the byte it names

printf '\xc3\xa9' wrote four bytes where bash writes two: \xHH read as a
code point and was then UTF-8 encoded, so \xff came out as c3 bf rather
than the single byte it names. Octal escapes and echo -e had it too.

A byte above ASCII now rides as its surrogate escape and every place the
shell turns its own text into bytes decodes it back, which covers printf,
echo, %q, heredocs and here strings. \u and \U still name code points.

* fix(gsheets): ask for the cell values the mount documents

.gsheet.json promised cell values at .sheets[0].data[0].rowData[] and
carried none: spreadsheets.get returns no grid data unless the request
asks for it, so the file was tab metadata against real Google too, not
only against the fake server.

The fake server implements the endpoint now, with the shapes taken from
the live API: no startRow or startColumn at zero, {} for an empty cell,
numbers parsed under USER_ENTERED, row and column metadata, tableRange on
append, and the whole SheetProperties in an addSheet reply.

* fix(shell): mask a byte escape and keep a non-BMP character whole

Three octal digits reach past one byte, and bash writes the low byte of
those. The sentinel mapped the whole value instead, so 256..511 landed on
U+DD00..U+DDFF, outside the range surrogateescape can encode: printf
'\400' raised UnicodeEncodeError rather than printing a NUL. Mask to one
byte before building the sentinel.

In TypeScript the sentinel range also swallowed the low half of an
ordinary surrogate pair, so a non-BMP character with a low surrogate in
U+DC80..U+DCFF (U+10080 is D800 DC80) came out as U+FFFD plus a raw 0x80
even with no escape in sight. Only a lone low surrogate is a byte now.

The non-BMP integ case named U+1F600, whose surrogates are D83D DE00 and
never were in the sentinel range, so it passed against the broken
encoder. It names U+10080 instead, with an echo case and a case that
mixes the character with a byte escape.

Pinned against bash 5.2 in a UTF-8 locale.

* test(gws): match live Sheets on grid size and cell typing

The mock reported a fixed 1000x26 grid, so a tab holding more rows than
that claimed to hold 1000 while carrying more, and handed out one
rowMetadata entry for only the first 1000. Live grows the grid to what
was written, so gridProperties, rowMetadata and columnMetadata all read
from one extent now: 1313 written rows report 1313.

Cell typing went through Number(), which is looser than USER_ENTERED: a
whitespace-only cell became numeric zero and 0x10 became 16, where live
keeps both as strings. A plain decimal test replaces it. TRUE is a
boolean rather than a string, and formattedValue is the rendered value
rather than the raw input, so 007 reports "7" and 4.50 reports "4.5". A
number typed with an exponent keeps a scientific format, so 1e3 reports
"1.00E+03".

Verified against the live API: 18 typing cases and 8 exponent cases
match exactly. Currency, percent, thousands-separated and date-shaped
cells stay strings and say so in a comment, since they need locale-aware
number formats.

The fixture seeds a boolean, a hex-looking string and an exponent so the
typing is asserted through the mount.
2026-08-05 13:37:31 -07:00

33 lines
1.0 KiB
Python

from mirage.shell.bytes import byte_char, encode_text
def test_ascii_bytes_stand_for_themselves():
assert byte_char(0x41) == "A"
assert byte_char(0x00) == "\0"
assert encode_text(byte_char(0x41)) == b"A"
def test_a_byte_above_ascii_round_trips():
assert encode_text(byte_char(0xFF)) == b"\xff"
assert encode_text(byte_char(0xC3) + byte_char(0xA9)) == b"\xc3\xa9"
def test_ordinary_text_still_encodes_as_utf8():
assert encode_text("café\n") == "café\n".encode()
def test_bytes_and_text_mix():
assert encode_text("a" + byte_char(0xFF) + "b") == b"a\xffb"
def test_three_octal_digits_past_a_byte_keep_the_low_byte():
# bash writes \400 as 0x00 and \777 as 0xff.
assert encode_text(byte_char(0o400)) == b"\x00"
assert encode_text(byte_char(0o777)) == b"\xff"
def test_a_non_bmp_character_is_not_a_byte():
assert encode_text("\U00010080") == "\U00010080".encode()
assert encode_text("a\U00010080" +
byte_char(0xFF)) == "a\U00010080".encode() + b"\xff"