Skip to content

Commit 81fd4d3

Browse files
committed
test: cover GNU dangling symlink cases
Add seven ls tests mirroring GNU tests/ls/ls-misc.pl sl-dangle3..9 to validate combinations of `ln=`, `or=` and `mi=` settings. The tests verify both file-level output and directory listings, including the `\x1b[m` reset requirement when `or=:` is set.
1 parent a764ba0 commit 81fd4d3

File tree

1 file changed

+201
-21
lines changed

1 file changed

+201
-21
lines changed

tests/by-util/test_ls.rs

Lines changed: 201 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55
// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired tmpfs mdir COLORTERM mexe bcdef mfoo timefile
6-
// spell-checker:ignore (words) fakeroot setcap drwxr bcdlps
6+
// spell-checker:ignore (words) fakeroot setcap drwxr bcdlps mdangling mentry
77
#![allow(
88
clippy::similar_names,
99
clippy::too_many_lines,
@@ -1446,31 +1446,213 @@ fn test_ls_long_dangling_symlink_color() {
14461446

14471447
at.mkdir("dir1");
14481448
at.symlink_dir("foo", "dir1/dangling_symlink");
1449+
let ls_colors = "ln=target:or=40:mi=34";
14491450
let result = ts
14501451
.ucmd()
1452+
.env("LS_COLORS", ls_colors)
14511453
.arg("-l")
14521454
.arg("--color=always")
14531455
.arg("dir1/dangling_symlink")
14541456
.succeeds();
14551457

14561458
let stdout = result.stdout_str();
1457-
// stdout contains output like in the below sequence. We match for the color i.e. 01;36
1458-
// \x1b[0m\x1b[01;36mdir1/dangling_symlink\x1b[0m -> \x1b[01;36mfoo\x1b[0m
1459-
let color_regex = Regex::new(r"(\d\d;)\d\dm").unwrap();
1460-
// colors_vec[0] contains the symlink color and style and colors_vec[1] contains the color and style of the file the
1461-
// symlink points to.
1462-
let colors_vec: Vec<_> = color_regex
1463-
.find_iter(stdout)
1464-
.map(|color| color.as_str())
1465-
.collect();
1459+
// Ensure dangling link name uses `or=` and target uses `mi=`.
1460+
let name_regex =
1461+
Regex::new(r"(?:\x1b\[[0-9;]*m)*\x1b\[([0-9;]*)mdir1/dangling_symlink\x1b\[0m").unwrap();
1462+
let target_path = regex::escape(&at.plus_as_string("foo"));
1463+
let target_pattern = format!(r"(?:\x1b\[[0-9;]*m)*\x1b\[([0-9;]*)m{target_path}\x1b\[0m");
1464+
let target_regex = Regex::new(&target_pattern).unwrap();
1465+
1466+
let name_caps = name_regex
1467+
.captures(stdout)
1468+
.expect("failed to capture dangling symlink name color");
1469+
let target_caps = target_regex
1470+
.captures(stdout)
1471+
.expect("failed to capture dangling target color");
1472+
1473+
let name_color = name_caps.get(1).unwrap().as_str();
1474+
let target_color = target_caps.get(1).unwrap().as_str();
1475+
1476+
assert_eq!(name_color, "40");
1477+
assert_eq!(target_color, "34");
1478+
}
1479+
1480+
#[test]
1481+
/// Mirrors GNU `tests/ls/ls-misc.pl::sl-dangle3`.
1482+
fn test_ls_dangling_symlink_or_and_missing_colors() {
1483+
let ts = TestScenario::new(util_name!());
1484+
let at = &ts.fixtures;
1485+
at.symlink_file("nowhere", "dangling");
1486+
1487+
let stdout = ts
1488+
.ucmd()
1489+
.env("LS_COLORS", "ln=target:or=40:mi=34")
1490+
.arg("-o")
1491+
.arg("--time-style=+:TIME:")
1492+
.arg("--color=always")
1493+
.arg("dangling")
1494+
.succeeds()
1495+
.stdout_str()
1496+
.to_string();
14661497

1467-
assert_eq!(colors_vec[0], colors_vec[1]);
1468-
// constructs the string of file path with the color code
1469-
let symlink_color_name = colors_vec[0].to_owned() + "dir1/dangling_symlink\x1b";
1470-
let target_color_name = colors_vec[1].to_owned() + at.plus_as_string("foo\x1b").as_str();
1498+
let color_regex = Regex::new(
1499+
r"\x1b\[0m\x1b\[(?P<link>[0-9;]*)mdangling\x1b\[0m -> \x1b\[(?P<target>[0-9;]*)m",
1500+
)
1501+
.unwrap();
1502+
let captures = color_regex
1503+
.captures(&stdout)
1504+
.expect("failed to capture dangling colors");
14711505

1472-
assert!(stdout.contains(&symlink_color_name));
1473-
assert!(stdout.contains(&target_color_name));
1506+
assert_eq!(captures.name("link").unwrap().as_str(), "40");
1507+
assert_eq!(captures.name("target").unwrap().as_str(), "34");
1508+
}
1509+
1510+
#[test]
1511+
/// Mirrors GNU `tests/ls/ls-misc.pl::sl-dangle4`.
1512+
fn test_ls_dangling_symlink_ln_or_priority() {
1513+
let ts = TestScenario::new(util_name!());
1514+
let at = &ts.fixtures;
1515+
at.symlink_file("nowhere", "dangling");
1516+
1517+
let stdout = ts
1518+
.ucmd()
1519+
.env("LS_COLORS", "ln=34:mi=35:or=36")
1520+
.arg("-o")
1521+
.arg("--time-style=+:TIME:")
1522+
.arg("--color=always")
1523+
.arg("dangling")
1524+
.succeeds()
1525+
.stdout_str()
1526+
.to_string();
1527+
1528+
let color_regex = Regex::new(
1529+
r"\x1b\[0m\x1b\[(?P<link>[0-9;]*)mdangling\x1b\[0m -> \x1b\[(?P<target>[0-9;]*)m",
1530+
)
1531+
.unwrap();
1532+
let captures = color_regex
1533+
.captures(&stdout)
1534+
.expect("failed to capture dangling colors");
1535+
assert_eq!(captures.name("link").unwrap().as_str(), "36");
1536+
assert_eq!(captures.name("target").unwrap().as_str(), "35");
1537+
}
1538+
1539+
#[test]
1540+
/// Mirrors GNU `tests/ls/ls-misc.pl::sl-dangle5`.
1541+
fn test_ls_dangling_symlink_ln_and_missing_colors() {
1542+
let ts = TestScenario::new(util_name!());
1543+
let at = &ts.fixtures;
1544+
at.symlink_file("nowhere", "dangling");
1545+
1546+
let stdout = ts
1547+
.ucmd()
1548+
.env("LS_COLORS", "ln=34:mi=35")
1549+
.arg("-o")
1550+
.arg("--time-style=+:TIME:")
1551+
.arg("--color=always")
1552+
.arg("dangling")
1553+
.succeeds()
1554+
.stdout_str()
1555+
.to_string();
1556+
1557+
let color_regex = Regex::new(
1558+
r"\x1b\[0m\x1b\[(?P<link>[0-9;]*)mdangling\x1b\[0m -> \x1b\[(?P<target>[0-9;]*)m",
1559+
)
1560+
.unwrap();
1561+
let captures = color_regex
1562+
.captures(&stdout)
1563+
.expect("failed to capture dangling colors");
1564+
assert_eq!(captures.name("link").unwrap().as_str(), "34");
1565+
assert_eq!(captures.name("target").unwrap().as_str(), "35");
1566+
}
1567+
1568+
#[test]
1569+
/// Mirrors GNU `tests/ls/ls-misc.pl::sl-dangle7`.
1570+
fn test_ls_dangling_symlink_blank_or_still_emits_reset() {
1571+
let ts = TestScenario::new(util_name!());
1572+
let at = &ts.fixtures;
1573+
at.symlink_file("nowhere", "dangling");
1574+
1575+
let stdout = ts
1576+
.ucmd()
1577+
.env("LS_COLORS", "ln=target:or=:ex=:")
1578+
.arg("--color=always")
1579+
.arg("dangling")
1580+
.succeeds()
1581+
.stdout_str()
1582+
.to_string();
1583+
1584+
assert!(
1585+
stdout.contains("\u{1b}[0m\u{1b}[mdangling\u{1b}[0m"),
1586+
"unexpected output: {stdout:?}"
1587+
);
1588+
}
1589+
1590+
#[test]
1591+
/// Mirrors GNU `tests/ls/ls-misc.pl::sl-dangle9`.
1592+
fn test_ls_dangling_symlink_blank_or_in_directory_listing() {
1593+
let ts = TestScenario::new(util_name!());
1594+
let at = &ts.fixtures;
1595+
at.mkdir("dir");
1596+
at.symlink_file("nowhere", "dir/entry");
1597+
1598+
let stdout = ts
1599+
.ucmd()
1600+
.env("LS_COLORS", "ln=target:or=:ex=:")
1601+
.arg("--color=always")
1602+
.arg("dir")
1603+
.succeeds()
1604+
.stdout_str()
1605+
.to_string();
1606+
1607+
assert!(
1608+
stdout.contains("\u{1b}[0m\u{1b}[mentry\u{1b}[0m"),
1609+
"unexpected output: {stdout:?}"
1610+
);
1611+
}
1612+
1613+
#[test]
1614+
/// Mirrors GNU `tests/ls/ls-misc.pl::sl-dangle8`.
1615+
fn test_ls_dangling_symlink_uses_ln_when_or_blank() {
1616+
let ts = TestScenario::new(util_name!());
1617+
let at = &ts.fixtures;
1618+
at.symlink_file("nowhere", "dangling");
1619+
1620+
let stdout = ts
1621+
.ucmd()
1622+
.env("LS_COLORS", "ln=1;36:or=:")
1623+
.arg("--color=always")
1624+
.arg("dangling")
1625+
.succeeds()
1626+
.stdout_str()
1627+
.to_string();
1628+
1629+
assert!(
1630+
stdout.contains("\u{1b}[0m\u{1b}[1;36mdangling\u{1b}[0m"),
1631+
"unexpected output: {stdout:?}"
1632+
);
1633+
}
1634+
1635+
#[test]
1636+
/// Mirrors GNU `tests/ls/ls-misc.pl::sl-dangle6`.
1637+
fn test_ls_directory_dangling_symlink_uses_ln_when_or_blank() {
1638+
let ts = TestScenario::new(util_name!());
1639+
let at = &ts.fixtures;
1640+
at.mkdir("dir");
1641+
at.symlink_file("nowhere", "dir/entry");
1642+
1643+
let stdout = ts
1644+
.ucmd()
1645+
.env("LS_COLORS", "ln=1;36:or=:")
1646+
.arg("--color=always")
1647+
.arg("dir")
1648+
.succeeds()
1649+
.stdout_str()
1650+
.to_string();
1651+
1652+
assert!(
1653+
stdout.contains("\u{1b}[0m\u{1b}[1;36mentry\u{1b}[0m"),
1654+
"unexpected output: {stdout:?}"
1655+
);
14741656
}
14751657

14761658
#[test]
@@ -5862,8 +6044,7 @@ fn test_ls_color_norm() {
58626044
.stdout_contains(expected);
58636045

58646046
// uncolored ordinary files that do _not_ inherit from NORMAL.
5865-
let expected =
5866-
"\x1b[0m\x1b[07mnorm \x1b[0mno_color\x1b[0m\n\x1b[07mnorm \x1b[0m\x1b[01;32mexe\x1b[0m\n"; // spell-checker:disable-line
6047+
let expected = "\x1b[0m\x1b[07mnorm \x1b[0m\x1b[mno_color\x1b[0m\n\x1b[07mnorm \x1b[0m\x1b[01;32mexe\x1b[0m\n"; // spell-checker:disable-line
58676048
scene
58686049
.ucmd()
58696050
.env("LS_COLORS", format!("{colors}:fi="))
@@ -5876,8 +6057,7 @@ fn test_ls_color_norm() {
58766057
.stdout_str_apply(strip)
58776058
.stdout_contains(expected);
58786059

5879-
let expected =
5880-
"\x1b[0m\x1b[07mnorm \x1b[0mno_color\x1b[0m\n\x1b[07mnorm \x1b[0m\x1b[01;32mexe\x1b[0m\n"; // spell-checker:disable-line
6060+
let expected = "\x1b[0m\x1b[07mnorm \x1b[0m\x1b[00mno_color\x1b[0m\n\x1b[07mnorm \x1b[0m\x1b[01;32mexe\x1b[0m\n"; // spell-checker:disable-line
58816061
scene
58826062
.ucmd()
58836063
.env("LS_COLORS", format!("{colors}:fi=0"))
@@ -6498,7 +6678,7 @@ fn test_f_overrides_sort_flags() {
64986678

64996679
// Create files with different sizes for predictable sort order
65006680
at.write("small.txt", "a"); // 1 byte
6501-
at.write("medium.txt", "bb"); // 2 bytes
6681+
at.write("medium.txt", "bb"); // 2 bytes
65026682
at.write("large.txt", "ccc"); // 3 bytes
65036683

65046684
// Get baseline outputs (include -a to match -f behavior which shows all files)

0 commit comments

Comments
 (0)