@@ -1412,6 +1412,7 @@ async fn glob_basic() {
14121412 . run ( )
14131413 . await ;
14141414
1415+ // ** matches recursively (globstar on by default)
14151416 TestBuilder :: new ( )
14161417 . directory ( "sub_dir/sub" )
14171418 . file ( "sub_dir/sub/1.txt" , "1\n " )
@@ -1742,10 +1743,10 @@ async fn sigpipe_from_pipeline() {
17421743
17431744#[ tokio:: test]
17441745async fn shopt ( ) {
1745- // query all options (default: failglob on, nullglob off)
1746+ // query all options (default: failglob on, globstar on, nullglob off)
17461747 TestBuilder :: new ( )
17471748 . command ( "shopt" )
1748- . assert_stdout ( "failglob\t on\n nullglob\t off\n " )
1749+ . assert_stdout ( "failglob\t on\n globstar \t on \ n nullglob\t off\n " )
17491750 . run ( )
17501751 . await ;
17511752
@@ -1807,7 +1808,23 @@ async fn shopt() {
18071808 // multiple options
18081809 TestBuilder :: new ( )
18091810 . command ( "shopt -s nullglob && shopt -u failglob && shopt" )
1810- . assert_stdout ( "failglob\t off\n nullglob\t on\n " )
1811+ . assert_stdout ( "failglob\t off\n globstar\t on\n nullglob\t on\n " )
1812+ . run ( )
1813+ . await ;
1814+
1815+ // query globstar option (on by default)
1816+ TestBuilder :: new ( )
1817+ . command ( "shopt globstar" )
1818+ . assert_stdout ( "globstar\t on\n " )
1819+ . assert_exit_code ( 0 ) // returns 0 when option is on
1820+ . run ( )
1821+ . await ;
1822+
1823+ // disable globstar
1824+ TestBuilder :: new ( )
1825+ . command ( "shopt -u globstar && shopt globstar" )
1826+ . assert_stdout ( "globstar\t off\n " )
1827+ . assert_exit_code ( 1 )
18111828 . run ( )
18121829 . await ;
18131830}
@@ -1890,25 +1907,76 @@ async fn shopt_failglob() {
18901907 . await ;
18911908}
18921909
1910+ #[ tokio:: test]
1911+ async fn shopt_globstar ( ) {
1912+ // globstar on by default: ** matches zero or more directories
1913+ // use cat to verify all files are matched (content order verifies glob worked)
1914+ TestBuilder :: new ( )
1915+ . directory ( "sub/deep" )
1916+ . file ( "a.txt" , "a\n " )
1917+ . file ( "sub/b.txt" , "b\n " )
1918+ . file ( "sub/deep/c.txt" , "c\n " )
1919+ . command ( "cat **/*.txt" )
1920+ . assert_stdout ( "a\n b\n c\n " )
1921+ . assert_exit_code ( 0 )
1922+ . run ( )
1923+ . await ;
1924+
1925+ // single * still behaves normally even with globstar enabled
1926+ TestBuilder :: new ( )
1927+ . directory ( "sub" )
1928+ . file ( "a.txt" , "a\n " )
1929+ . file ( "sub/b.txt" , "b\n " )
1930+ . command ( "echo *.txt" )
1931+ . assert_stdout ( "a.txt\n " )
1932+ . assert_exit_code ( 0 )
1933+ . run ( )
1934+ . await ;
1935+
1936+ // disabling globstar: ** behaves like * (single-segment match)
1937+ TestBuilder :: new ( )
1938+ . directory ( "sub/deep" )
1939+ . file ( "a.txt" , "a\n " )
1940+ . file ( "sub/b.txt" , "b\n " )
1941+ . file ( "sub/deep/c.txt" , "c\n " )
1942+ . command ( "shopt -u globstar && cat **/*.txt" )
1943+ . assert_stdout ( "b\n " ) // only matches one level deep (sub/b.txt)
1944+ . assert_exit_code ( 0 )
1945+ . run ( )
1946+ . await ;
1947+
1948+ // re-enabling globstar restores recursive behavior
1949+ TestBuilder :: new ( )
1950+ . directory ( "sub/deep" )
1951+ . file ( "a.txt" , "a\n " )
1952+ . file ( "sub/b.txt" , "b\n " )
1953+ . file ( "sub/deep/c.txt" , "c\n " )
1954+ . command ( "shopt -u globstar && shopt -s globstar && cat **/*.txt" )
1955+ . assert_stdout ( "a\n b\n c\n " )
1956+ . assert_exit_code ( 0 )
1957+ . run ( )
1958+ . await ;
1959+ }
1960+
18931961#[ tokio:: test]
18941962async fn pipefail_option ( ) {
18951963 // Without pipefail: exit code is from last command (0)
18961964 TestBuilder :: new ( )
1897- . command ( "sh -c ' exit 1' | true" )
1965+ . command ( "( exit 1) | true" )
18981966 . assert_exit_code ( 0 )
18991967 . run ( )
19001968 . await ;
19011969
19021970 // With pipefail: exit code is rightmost non-zero (1)
19031971 TestBuilder :: new ( )
1904- . command ( "set -o pipefail && sh -c ' exit 1' | true" )
1972+ . command ( "set -o pipefail && ( exit 1) | true" )
19051973 . assert_exit_code ( 1 )
19061974 . run ( )
19071975 . await ;
19081976
19091977 // Multiple failures - should return rightmost non-zero
19101978 TestBuilder :: new ( )
1911- . command ( "set -o pipefail && sh -c ' exit 2' | sh -c ' exit 3' | true" )
1979+ . command ( "set -o pipefail && ( exit 2) | ( exit 3) | true" )
19121980 . assert_exit_code ( 3 )
19131981 . run ( )
19141982 . await ;
@@ -1922,7 +1990,7 @@ async fn pipefail_option() {
19221990
19231991 // Disable pipefail with +o
19241992 TestBuilder :: new ( )
1925- . command ( "set -o pipefail && set +o pipefail && sh -c ' exit 1' | true" )
1993+ . command ( "set -o pipefail && set +o pipefail && ( exit 1) | true" )
19261994 . assert_exit_code ( 0 )
19271995 . run ( )
19281996 . await ;
0 commit comments