| 1160 |
1160 |
|
assert!(!r.0.iter().any(|e| e.starts_with("apc(")), "got {:?}", r.0);
|
| 1161 |
1161 |
|
assert!(p.buffered_bytes() < 8 * 1024);
|
| 1162 |
1162 |
|
}
|
|
1163 |
+ |
|
|
1164 |
+ |
// ---- The bounds, as numbers ---------------------------------------
|
|
1165 |
+ |
|
|
1166 |
+ |
/// Every one of these is written as an arithmetic expression, and an
|
|
1167 |
+ |
/// expression nothing asserts is a number nothing pins: mutation found that
|
|
1168 |
+ |
/// `8 * 1024 * 1024` could become `8 + 1024 + 1024` and no test noticed.
|
|
1169 |
+ |
/// The parser stays self-consistent under that change, which is exactly why
|
|
1170 |
+ |
/// its own behavioural tests cannot catch it.
|
|
1171 |
+ |
#[test]
|
|
1172 |
+ |
fn the_bounds_are_the_numbers_the_module_documents() {
|
|
1173 |
+ |
assert_eq!(MAX_PARAMS, 32, "vte's number, so we are a drop-in for it");
|
|
1174 |
+ |
assert_eq!(MAX_STRING_BYTES, 8_388_608, "8 MiB");
|
|
1175 |
+ |
assert_eq!(MAX_OSC_PARAMS, 1024);
|
|
1176 |
+ |
assert_eq!(INITIAL_BODY_CAPACITY, 2048);
|
|
1177 |
+ |
}
|
|
1178 |
+ |
|
|
1179 |
+ |
// ---- Params, directly ----------------------------------------------
|
|
1180 |
+ |
|
|
1181 |
+ |
/// `len` and `is_empty` are public and nothing called them, so the whole
|
|
1182 |
+ |
/// accessor pair could return a constant unnoticed. `clear` is the other
|
|
1183 |
+ |
/// half: a parameter list that does not empty carries one sequence's
|
|
1184 |
+ |
/// parameters into the next.
|
|
1185 |
+ |
#[test]
|
|
1186 |
+ |
fn params_len_and_is_empty_track_the_open_groups() {
|
|
1187 |
+ |
let mut p = Params::default();
|
|
1188 |
+ |
assert!(p.is_empty());
|
|
1189 |
+ |
assert_eq!(p.len(), 0);
|
|
1190 |
+ |
|
|
1191 |
+ |
assert!(p.new_param());
|
|
1192 |
+ |
assert!(!p.is_empty());
|
|
1193 |
+ |
assert_eq!(p.len(), 1);
|
|
1194 |
+ |
|
|
1195 |
+ |
assert!(p.new_param());
|
|
1196 |
+ |
assert_eq!(p.len(), 2);
|
|
1197 |
+ |
|
|
1198 |
+ |
p.clear();
|
|
1199 |
+ |
assert!(p.is_empty());
|
|
1200 |
+ |
assert_eq!(p.len(), 0);
|
|
1201 |
+ |
}
|
|
1202 |
+ |
|
|
1203 |
+ |
/// A subparameter costs a slot exactly as a parameter does, or `38:2::R:G:B`
|
|
1204 |
+ |
/// repeated is a cap that does not bind. The expression under test is
|
|
1205 |
+ |
/// `slots += 1`; `slots *= 1` leaves it at its opening value forever, which
|
|
1206 |
+ |
/// nothing observes without pushing the list to the cap.
|
|
1207 |
+ |
#[test]
|
|
1208 |
+ |
fn subparameters_consume_slots_so_the_cap_still_binds() {
|
|
1209 |
+ |
let mut p = Params::default();
|
|
1210 |
+ |
assert!(p.new_param(), "the first slot");
|
|
1211 |
+ |
for i in 1..MAX_PARAMS {
|
|
1212 |
+ |
assert!(p.new_subparam(), "slot {} of {MAX_PARAMS}", i + 1);
|
|
1213 |
+ |
}
|
|
1214 |
+ |
assert!(
|
|
1215 |
+ |
!p.new_subparam(),
|
|
1216 |
+ |
"slot {} is past the cap and must be refused",
|
|
1217 |
+ |
MAX_PARAMS + 1
|
|
1218 |
+ |
);
|
|
1219 |
+ |
}
|
|
1220 |
+ |
|
|
1221 |
+ |
/// `footprint` feeds [`Parser::buffered_bytes`], which is what the soak
|
|
1222 |
+ |
/// oracle asserts an amplification ceiling against. A footprint that
|
|
1223 |
+ |
/// under-reports is an oracle that cannot fire.
|
|
1224 |
+ |
///
|
|
1225 |
+ |
/// The expected value is written out here rather than read from the
|
|
1226 |
+ |
/// function, so the arithmetic is stated twice and a change to either side
|
|
1227 |
+ |
/// disagrees.
|
|
1228 |
+ |
#[test]
|
|
1229 |
+ |
fn footprint_counts_the_outer_vec_and_every_group() {
|
|
1230 |
+ |
let p = Params::default();
|
|
1231 |
+ |
assert_eq!(p.footprint(), 0, "an unused list holds nothing");
|
|
1232 |
+ |
|
|
1233 |
+ |
let mut p = Params::default();
|
|
1234 |
+ |
assert!(p.new_param());
|
|
1235 |
+ |
assert!(p.new_subparam());
|
|
1236 |
+ |
assert!(p.new_param());
|
|
1237 |
+ |
|
|
1238 |
+ |
let outer = p.inner.capacity();
|
|
1239 |
+ |
let groups: usize = p.inner.iter().map(Vec::capacity).sum();
|
|
1240 |
+ |
assert!(outer > 0 && groups > 0, "the case has to be non-degenerate");
|
|
1241 |
+ |
assert_eq!(
|
|
1242 |
+ |
p.footprint(),
|
|
1243 |
+ |
outer * std::mem::size_of::<Vec<u16>>() + groups * std::mem::size_of::<u16>()
|
|
1244 |
+ |
);
|
|
1245 |
+ |
}
|
|
1246 |
+ |
|
|
1247 |
+ |
// ---- Parser accounting ---------------------------------------------
|
|
1248 |
+ |
|
|
1249 |
+ |
/// `in_ground` is how a caller knows a chunk boundary is safe to cut on,
|
|
1250 |
+ |
/// and it is what the fuzz oracle checks after a terminated sequence. A
|
|
1251 |
+ |
/// constant `true` makes both of those say yes mid-sequence.
|
|
1252 |
+ |
#[test]
|
|
1253 |
+ |
fn in_ground_is_false_while_a_sequence_is_open() {
|
|
1254 |
+ |
let mut p = Parser::new();
|
|
1255 |
+ |
let mut r = Rec::default();
|
|
1256 |
+ |
assert!(p.in_ground(), "a fresh parser holds nothing");
|
|
1257 |
+ |
|
|
1258 |
+ |
p.advance(&mut r, b"\x1b[1");
|
|
1259 |
+ |
assert!(!p.in_ground(), "mid-CSI");
|
|
1260 |
+ |
|
|
1261 |
+ |
p.advance(&mut r, b"m");
|
|
1262 |
+ |
assert!(p.in_ground(), "the sequence terminated");
|
|
1263 |
+ |
}
|
|
1264 |
+ |
|
|
1265 |
+ |
/// The same twice-stated arithmetic as `footprint`, for the total the soak
|
|
1266 |
+ |
/// oracle actually reads.
|
|
1267 |
+ |
#[test]
|
|
1268 |
+ |
fn buffered_bytes_sums_every_buffer_the_parser_holds() {
|
|
1269 |
+ |
let mut p = Parser::new();
|
|
1270 |
+ |
let mut r = Rec::default();
|
|
1271 |
+ |
// An open OSC with fields, so all four terms are non-zero.
|
|
1272 |
+ |
p.advance(&mut r, b"\x1b]52;c;aGVsbG8=");
|
|
1273 |
+ |
|
|
1274 |
+ |
let expected = p.osc_buf.capacity()
|
|
1275 |
+ |
+ p.apc_buf.capacity()
|
|
1276 |
+ |
+ p.osc_params.capacity() * std::mem::size_of::<(usize, usize)>()
|
|
1277 |
+ |
+ p.params.footprint();
|
|
1278 |
+ |
assert!(expected > 1, "the case has to distinguish 0 and 1");
|
|
1279 |
+ |
assert_eq!(p.buffered_bytes(), expected);
|
|
1280 |
+ |
}
|
|
1281 |
+ |
|
|
1282 |
+ |
/// `clear` empties the intermediates, the ignore flag and the parameters
|
|
1283 |
+ |
/// when a fresh sequence starts. Without it one sequence's parameters are
|
|
1284 |
+ |
/// dispatched as the next one's.
|
|
1285 |
+ |
#[test]
|
|
1286 |
+ |
fn a_fresh_sequence_does_not_inherit_the_last_ones_parameters() {
|
|
1287 |
+ |
assert_eq!(
|
|
1288 |
+ |
run(b"\x1b[1;2m\x1b[m"),
|
|
1289 |
+ |
vec![
|
|
1290 |
+ |
"csi(params=[[1], [2]], intermediates=[], ignore=false, action='m')",
|
|
1291 |
+ |
"csi(params=[], intermediates=[], ignore=false, action='m')",
|
|
1292 |
+ |
]
|
|
1293 |
+ |
);
|
|
1294 |
+ |
}
|
|
1295 |
+ |
|
|
1296 |
+ |
// ---- Ground --------------------------------------------------------
|
|
1297 |
+ |
|
|
1298 |
+ |
/// CAN and SUB abort a sequence and are executed in Ground like any other
|
|
1299 |
+ |
/// C0. They are their own match arm, so deleting it drops them silently.
|
|
1300 |
+ |
#[test]
|
|
1301 |
+ |
fn can_and_sub_execute_in_ground() {
|
|
1302 |
+ |
assert_eq!(run(b"\x18"), vec!["exec(0x18)"]);
|
|
1303 |
+ |
assert_eq!(run(b"\x1a"), vec!["exec(0x1a)"]);
|
|
1304 |
+ |
}
|
|
1305 |
+ |
|
|
1306 |
+ |
// ---- Escape --------------------------------------------------------
|
|
1307 |
+ |
|
|
1308 |
+ |
#[test]
|
|
1309 |
+ |
fn a_c0_inside_escape_executes_without_ending_the_sequence() {
|
|
1310 |
+ |
assert_eq!(
|
|
1311 |
+ |
run(b"\x1b\rB"),
|
|
1312 |
+ |
vec![
|
|
1313 |
+ |
"exec(0x0d)",
|
|
1314 |
+ |
"esc(intermediates=[], ignore=false, byte=0x42)"
|
|
1315 |
+ |
]
|
|
1316 |
+ |
);
|
|
1317 |
+ |
}
|
|
1318 |
+ |
|
|
1319 |
+ |
#[test]
|
|
1320 |
+ |
fn an_escape_intermediate_reaches_the_dispatch() {
|
|
1321 |
+ |
assert_eq!(
|
|
1322 |
+ |
run(b"\x1b(B"),
|
|
1323 |
+ |
vec!["esc(intermediates=[40], ignore=false, byte=0x42)"]
|
|
1324 |
+ |
);
|
|
1325 |
+ |
}
|
|
1326 |
+ |
|
|
1327 |
+ |
/// A second intermediate accumulates rather than replacing the first.
|
|
1328 |
+ |
#[test]
|
|
1329 |
+ |
fn escape_intermediates_accumulate() {
|
|
1330 |
+ |
assert_eq!(
|
|
1331 |
+ |
run(b"\x1b($B"),
|
|
1332 |
+ |
vec!["esc(intermediates=[40, 36], ignore=false, byte=0x42)"]
|
|
1333 |
+ |
);
|
|
1334 |
+ |
}
|
|
1335 |
+ |
|
|
1336 |
+ |
#[test]
|
|
1337 |
+ |
fn a_c0_inside_an_escape_intermediate_executes() {
|
|
1338 |
+ |
assert_eq!(
|
|
1339 |
+ |
run(b"\x1b(\rB"),
|
|
1340 |
+ |
vec![
|
|
1341 |
+ |
"exec(0x0d)",
|
|
1342 |
+ |
"esc(intermediates=[40], ignore=false, byte=0x42)"
|
|
1343 |
+ |
]
|
|
1344 |
+ |
);
|
|
1345 |
+ |
}
|
|
1346 |
+ |
|
|
1347 |
+ |
/// SOS (0x58) and PM (0x5E) open a string the parser treats exactly as APC
|
|
1348 |
+ |
/// does: consumed, and delivered through the same callback. They share an
|
|
1349 |
+ |
/// arm with nothing, so deleting it leaves the parser sitting in Escape
|
|
1350 |
+ |
/// eating the body as escape finals.
|
|
1351 |
+ |
#[test]
|
|
1352 |
+ |
fn sos_and_pm_open_a_string_and_dispatch_it() {
|
|
1353 |
+ |
assert_eq!(
|
|
1354 |
+ |
run(b"\x1bXhi\x1b\\"),
|
|
1355 |
+ |
vec![
|
|
1356 |
+ |
"apc([104, 105])",
|
|
1357 |
+ |
"esc(intermediates=[], ignore=false, byte=0x5c)"
|
|
1358 |
+ |
]
|
|
1359 |
+ |
);
|
|
1360 |
+ |
assert_eq!(
|
|
1361 |
+ |
run(b"\x1b^hi\x1b\\"),
|
|
1362 |
+ |
vec![
|
|
1363 |
+ |
"apc([104, 105])",
|
|
1364 |
+ |
"esc(intermediates=[], ignore=false, byte=0x5c)"
|
|
1365 |
+ |
]
|
|
1366 |
+ |
);
|
|
1367 |
+ |
}
|
|
1368 |
+ |
|
|
1369 |
+ |
// ---- CSI -----------------------------------------------------------
|
|
1370 |
+ |
|
|
1371 |
+ |
#[test]
|
|
1372 |
+ |
fn a_c0_inside_csi_entry_executes() {
|
|
1373 |
+ |
assert_eq!(
|
|
1374 |
+ |
run(b"\x1b[\rm"),
|
|
1375 |
+ |
vec![
|
|
1376 |
+ |
"exec(0x0d)",
|
|
1377 |
+ |
"csi(params=[], intermediates=[], ignore=false, action='m')"
|
|
1378 |
+ |
]
|
|
1379 |
+ |
);
|
|
1380 |
+ |
}
|
|
1381 |
+ |
|
|
1382 |
+ |
/// A colon opening a CSI opens a subparameter group, so the sequence
|
|
1383 |
+ |
/// carries one empty slot rather than none. Deleting the arm makes the
|
|
1384 |
+ |
/// colon vanish and the dispatch carry no parameters at all.
|
|
1385 |
+ |
#[test]
|
|
1386 |
+ |
fn a_leading_colon_opens_a_subparameter_slot() {
|
|
1387 |
+ |
assert_eq!(
|
|
1388 |
+ |
run(b"\x1b[:m"),
|
|
1389 |
+ |
vec!["csi(params=[[0]], intermediates=[], ignore=false, action='m')"]
|
|
1390 |
+ |
);
|
|
1391 |
+ |
}
|
|
1392 |
+ |
|
|
1393 |
+ |
/// The overflow flag is set when a slot is REFUSED, not when one is taken.
|
|
1394 |
+ |
/// Dropping the `!` inverts that, and every ordinary sequence starts
|
|
1395 |
+ |
/// reporting itself as ignored.
|
|
1396 |
+ |
#[test]
|
|
1397 |
+ |
fn opening_a_slot_that_succeeds_does_not_mark_the_sequence_ignored() {
|
|
1398 |
+ |
assert_eq!(
|
|
1399 |
+ |
run(b"\x1b[:m"),
|
|
1400 |
+ |
vec!["csi(params=[[0]], intermediates=[], ignore=false, action='m')"]
|
|
1401 |
+ |
);
|
|
1402 |
+ |
assert_eq!(
|
|
1403 |
+ |
run(b"\x1b[;m"),
|
|
1404 |
+ |
vec!["csi(params=[[]], intermediates=[], ignore=false, action='m')"]
|
|
1405 |
+ |
);
|
|
1406 |
+ |
}
|
|
1407 |
+ |
|
|
1408 |
+ |
#[test]
|
|
1409 |
+ |
fn a_c0_inside_csi_param_executes() {
|
|
1410 |
+ |
assert_eq!(
|
|
1411 |
+ |
run(b"\x1b[1\rm"),
|
|
1412 |
+ |
vec![
|
|
1413 |
+ |
"exec(0x0d)",
|
|
1414 |
+ |
"csi(params=[[1]], intermediates=[], ignore=false, action='m')"
|
|
1415 |
+ |
]
|
|
1416 |
+ |
);
|
|
1417 |
+ |
}
|
|
1418 |
+ |
|
|
1419 |
+ |
/// A private marker arriving after a parameter is malformed: the DEC
|
|
1420 |
+ |
/// parser sends the sequence to CsiIgnore, so nothing dispatches.
|
|
1421 |
+ |
#[test]
|
|
1422 |
+ |
fn a_private_marker_after_a_parameter_abandons_the_sequence() {
|
|
1423 |
+ |
assert_eq!(run(b"\x1b[1<m"), Vec::<String>::new());
|
|
1424 |
+ |
}
|
|
1425 |
+ |
|
|
1426 |
+ |
#[test]
|
|
1427 |
+ |
fn a_c0_inside_a_csi_intermediate_executes() {
|
|
1428 |
+ |
assert_eq!(
|
|
1429 |
+ |
run(b"\x1b[ \rm"),
|
|
1430 |
+ |
vec![
|
|
1431 |
+ |
"exec(0x0d)",
|
|
1432 |
+ |
"csi(params=[], intermediates=[32], ignore=false, action='m')"
|
|
1433 |
+ |
]
|
|
1434 |
+ |
);
|
|
1435 |
+ |
}
|
|
1436 |
+ |
|
|
1437 |
+ |
/// A parameter byte after an intermediate is out of order, so the sequence
|
|
1438 |
+ |
/// is abandoned rather than dispatched with the bytes rearranged.
|
|
1439 |
+ |
#[test]
|
|
1440 |
+ |
fn a_parameter_after_an_intermediate_abandons_the_sequence() {
|
|
1441 |
+ |
assert_eq!(run(b"\x1b[ 1m"), Vec::<String>::new());
|
|
1442 |
+ |
}
|
|
1443 |
+ |
|
|
1444 |
+ |
/// CsiIgnore still executes C0s, and still ends on a final byte -- ending
|
|
1445 |
+ |
/// is the part that matters, because a parser stuck in CsiIgnore swallows
|
|
1446 |
+ |
/// the rest of the stream.
|
|
1447 |
+ |
#[test]
|
|
1448 |
+ |
fn an_abandoned_csi_still_executes_c0s_and_still_ends() {
|
|
1449 |
+ |
assert_eq!(run(b"\x1b[1<\rma"), vec!["exec(0x0d)", "print('a')"]);
|
|
1450 |
+ |
}
|
|
1451 |
+ |
|
|
1452 |
+ |
// ---- DCS -----------------------------------------------------------
|
|
1453 |
+ |
|
|
1454 |
+ |
#[test]
|
|
1455 |
+ |
fn a_bare_dcs_hooks_and_unhooks() {
|
|
1456 |
+ |
assert_eq!(
|
|
1457 |
+ |
run(b"\x1bPq\x1b\\"),
|
|
1458 |
+ |
vec![
|
|
1459 |
+ |
"hook(params=[], intermediates=[], ignore=false, action='q')",
|
|
1460 |
+ |
"unhook",
|
|
1461 |
+ |
"esc(intermediates=[], ignore=false, byte=0x5c)",
|
|
1462 |
+ |
]
|
|
1463 |
+ |
);
|
|
1464 |
+ |
}
|
|
1465 |
+ |
|
|
1466 |
+ |
#[test]
|
|
1467 |
+ |
fn dcs_passthrough_delivers_the_body_and_a_bell_terminates_it() {
|
|
1468 |
+ |
assert_eq!(
|
|
1469 |
+ |
run(b"\x1bPqAB\x07"),
|
|
1470 |
+ |
vec![
|
|
1471 |
+ |
"hook(params=[], intermediates=[], ignore=false, action='q')",
|
|
1472 |
+ |
"put(0x41)",
|
|
1473 |
+ |
"put(0x42)",
|
|
1474 |
+ |
"unhook",
|
|
1475 |
+ |
]
|
|
1476 |
+ |
);
|
|
1477 |
+ |
}
|
|
1478 |
+ |
|
|
1479 |
+ |
/// The digit arithmetic in both DCS parameter states, which is the same
|
|
1480 |
+ |
/// `b - b'0'` the CSI states use and was covered in neither.
|
|
1481 |
+ |
#[test]
|
|
1482 |
+ |
fn dcs_parameters_parse_as_numbers() {
|
|
1483 |
+ |
assert_eq!(
|
|
1484 |
+ |
run(b"\x1bP1q\x1b\\")[0],
|
|
1485 |
+ |
"hook(params=[[1]], intermediates=[], ignore=false, action='q')"
|
|
1486 |
+ |
);
|
|
1487 |
+ |
assert_eq!(
|
|
1488 |
+ |
run(b"\x1bP12q\x1b\\")[0],
|
|
1489 |
+ |
"hook(params=[[12]], intermediates=[], ignore=false, action='q')"
|
|
1490 |
+ |
);
|
|
1491 |
+ |
assert_eq!(
|
|
1492 |
+ |
run(b"\x1bP1;2q\x1b\\")[0],
|
|
1493 |
+ |
"hook(params=[[1], [2]], intermediates=[], ignore=false, action='q')"
|
|
1494 |
+ |
);
|
|
1495 |
+ |
}
|
|
1496 |
+ |
|
|
1497 |
+ |
#[test]
|
|
1498 |
+ |
fn a_dcs_intermediate_reaches_the_hook() {
|
|
1499 |
+ |
assert_eq!(
|
|
1500 |
+ |
run(b"\x1bP$q\x1b\\")[0],
|
|
1501 |
+ |
"hook(params=[], intermediates=[36], ignore=false, action='q')"
|
|
1502 |
+ |
);
|
|
1503 |
+ |
assert_eq!(
|
|
1504 |
+ |
run(b"\x1bP1$q\x1b\\")[0],
|
|
1505 |
+ |
"hook(params=[[1]], intermediates=[36], ignore=false, action='q')"
|
|
1506 |
+ |
);
|
|
1507 |
+ |
assert_eq!(
|
|
1508 |
+ |
run(b"\x1bP$$q\x1b\\")[0],
|
|
1509 |
+ |
"hook(params=[], intermediates=[36, 36], ignore=false, action='q')"
|
|
1510 |
+ |
);
|
|
1511 |
+ |
}
|
|
1512 |
+ |
|
|
1513 |
+ |
#[test]
|
|
1514 |
+ |
fn a_dcs_private_marker_reaches_the_hook() {
|
|
1515 |
+ |
assert_eq!(
|
|
1516 |
+ |
run(b"\x1bP?q\x1b\\")[0],
|
|
1517 |
+ |
"hook(params=[], intermediates=[63], ignore=false, action='q')"
|
|
1518 |
+ |
);
|
|
1519 |
+ |
}
|
|
1520 |
+ |
|
|
1521 |
+ |
/// A separator opening a DCS opens an empty parameter, and does not mark
|
|
1522 |
+ |
/// the sequence ignored -- the same inverted-`!` shape as CSI.
|
|
1523 |
+ |
#[test]
|
|
1524 |
+ |
fn a_leading_dcs_separator_opens_an_empty_parameter() {
|
|
1525 |
+ |
assert_eq!(
|
|
1526 |
+ |
run(b"\x1bP;q\x1b\\")[0],
|
|
1527 |
+ |
"hook(params=[[]], intermediates=[], ignore=false, action='q')"
|
|
1528 |
+ |
);
|
|
1529 |
+ |
}
|
|
1530 |
+ |
|
|
1531 |
+ |
/// Every route into DcsIgnore. A colon is illegal in a DCS parameter list
|
|
1532 |
+ |
/// and a private marker is illegal after one, so both abandon the sequence:
|
|
1533 |
+ |
/// no hook, and therefore no passthrough of whatever followed.
|
|
1534 |
+ |
#[test]
|
|
1535 |
+ |
fn an_illegal_dcs_prelude_abandons_the_sequence() {
|
|
1536 |
+ |
for seq in [
|
|
1537 |
+ |
&b"\x1bP:q\x1b\\"[..], // colon in DcsEntry
|
|
1538 |
+ |
&b"\x1bP1:q\x1b\\"[..], // colon in DcsParam
|
|
1539 |
+ |
&b"\x1bP1<q\x1b\\"[..], // private marker after a parameter
|
|
1540 |
+ |
&b"\x1bP$1q\x1b\\"[..], // parameter after an intermediate
|
|
1541 |
+ |
] {
|
|
1542 |
+ |
let log = run(seq);
|
|
1543 |
+ |
assert!(
|
|
1544 |
+ |
!log.iter().any(|e| e.starts_with("hook(")),
|
|
1545 |
+ |
"{seq:?} must not hook, got {log:?}"
|
|
1546 |
+ |
);
|
|
1547 |
+ |
}
|
|
1548 |
+ |
}
|
|
1549 |
+ |
|
|
1550 |
+ |
/// DcsIgnore ends on ESC and nothing else, and the parser comes back out of
|
|
1551 |
+ |
/// it. A DcsIgnore that never ends swallows the rest of the stream; one
|
|
1552 |
+ |
/// that ends on every byte ends in the middle of the discarded prelude.
|
|
1553 |
+ |
#[test]
|
|
1554 |
+ |
fn an_abandoned_dcs_ends_on_the_terminator_and_prints_again() {
|
|
1555 |
+ |
assert_eq!(
|
|
1556 |
+ |
run(b"\x1bP:q\x1b\\a"),
|
|
1557 |
+ |
vec![
|
|
1558 |
+ |
"esc(intermediates=[], ignore=false, byte=0x5c)",
|
|
1559 |
+ |
"print('a')"
|
|
1560 |
+ |
]
|
|
1561 |
+ |
);
|
|
1562 |
+ |
}
|
| 1163 |
1563 |
|
}
|