@renault-digital/bash-base 2.4.1 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +83 -10
- package/README.md +87 -6
- package/bin/bash-base +596 -312
- package/docs/bash-zsh-compatibility.md +202 -0
- package/docs/coverage.md +289 -0
- package/docs/design-principles.md +176 -0
- package/docs/references.md +122 -68
- package/docs/roadmap.md +289 -0
- package/man/bash-base.1 +71 -4
- package/package.json +4 -3
- package/scripts/test-shell-compat.sh +391 -0
- package/scripts/test.sh +82 -14
- package/spec/array.spec.sh +8 -2
- package/spec/coverage_edges.spec.sh +88 -0
- package/spec/coverage_zsh_array_map.spec.sh +24 -0
- package/spec/process.spec.sh +109 -15
- package/spec/reflection.spec.sh +1 -1
- package/spec/spec_helper.sh +17 -0
- package/spec/string.spec.sh +14 -17
- package/src/alias.sh +35 -4
- package/src/argument.sh +222 -124
- package/src/array.sh +96 -98
- package/src/constant.sh +1 -1
- package/src/document.sh +17 -11
- package/src/out.sh +26 -15
- package/src/process.sh +4 -4
- package/src/reflection.sh +76 -17
- package/src/string.sh +92 -27
- package/src/tool.sh +28 -12
package/spec/process.spec.sh
CHANGED
|
@@ -9,38 +9,101 @@ AfterRun preserve
|
|
|
9
9
|
Describe 'prc_filter_by_port'
|
|
10
10
|
It 'with port number'
|
|
11
11
|
nc -lk 127.0.0.1 9999 &
|
|
12
|
+
listener_pid=$!
|
|
12
13
|
sleep 1
|
|
13
14
|
echo "=== Debug: check listener ==="
|
|
14
15
|
lsof -iTCP:9999 -sTCP:LISTEN -n -P
|
|
15
16
|
When call prc_filter_by_port 9999
|
|
16
17
|
The status should be success
|
|
17
18
|
The output should include ":9999"
|
|
18
|
-
kill $
|
|
19
|
+
kill "$listener_pid" 2>/dev/null || true
|
|
20
|
+
wait "$listener_pid" 2>/dev/null || true
|
|
19
21
|
End
|
|
20
22
|
|
|
21
23
|
It 'without port number'
|
|
22
24
|
nc -l 9999 &
|
|
25
|
+
listener_pid=$!
|
|
26
|
+
sleep 1
|
|
23
27
|
When call prc_filter_by_port
|
|
24
28
|
The status should be success
|
|
25
29
|
The output should include ":9999"
|
|
26
|
-
kill $
|
|
30
|
+
kill "$listener_pid" 2>/dev/null || true
|
|
31
|
+
wait "$listener_pid" 2>/dev/null || true
|
|
27
32
|
End
|
|
28
33
|
End
|
|
29
34
|
|
|
30
35
|
|
|
36
|
+
# Use an owned listener and wait until it is actually listening before
|
|
37
|
+
# exercising the public API. Redirect all background descriptors so an
|
|
38
|
+
# unsuccessful kill cannot hold the command-substitution output pipe open.
|
|
39
|
+
prc_test_kill_listener() {
|
|
40
|
+
local port="$1" signal="$2"
|
|
41
|
+
local listener_pid="" supervisor_pid attempt ready=0 rc=1 pid_file
|
|
42
|
+
|
|
43
|
+
pid_file="$(mktemp "${TMPDIR:-/tmp}/bash-base-listener.XXXXXX")" || return 1
|
|
44
|
+
|
|
45
|
+
# A SIGKILL-ed direct background job causes Bash to write "Killed: 9"
|
|
46
|
+
# on ShellSpec's stderr, even if nc itself redirects stderr. Instead,
|
|
47
|
+
# the direct child is a POSIX sh supervisor that exits normally after
|
|
48
|
+
# waiting for nc. Keep nc's PID to verify we are testing our listener.
|
|
49
|
+
sh -c '
|
|
50
|
+
nc -l "$1" </dev/null >/dev/null 2>&1 &
|
|
51
|
+
child=$!
|
|
52
|
+
trap "kill $child 2>/dev/null || :" 0 1 2 15
|
|
53
|
+
printf "%s\n" "$child" > "$2"
|
|
54
|
+
wait "$child" 2>/dev/null || :
|
|
55
|
+
' sh "$port" "$pid_file" </dev/null >/dev/null 2>&1 &
|
|
56
|
+
supervisor_pid=$!
|
|
57
|
+
|
|
58
|
+
for ((attempt = 0; attempt < 50; attempt++)); do
|
|
59
|
+
if [[ -s "$pid_file" ]]; then
|
|
60
|
+
IFS= read -r listener_pid < "$pid_file" || :
|
|
61
|
+
fi
|
|
62
|
+
if [[ -n "$listener_pid" ]] &&
|
|
63
|
+
lsof -nP -iTCP:"$port" -sTCP:LISTEN -t 2>/dev/null | grep -qx "$listener_pid"; then
|
|
64
|
+
ready=1
|
|
65
|
+
break
|
|
66
|
+
fi
|
|
67
|
+
if ! kill -0 "$supervisor_pid" 2>/dev/null; then
|
|
68
|
+
break
|
|
69
|
+
fi
|
|
70
|
+
sleep 0.1
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
if ((ready)); then
|
|
74
|
+
actual="$(prc_kill_by_port "$port" "$signal")"
|
|
75
|
+
rc=$?
|
|
76
|
+
else
|
|
77
|
+
printf 'Test listener failed to bind on port %s\n' "$port" >&2
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Cleanup only the processes owned by this test. Reap the normal-exiting
|
|
81
|
+
# supervisor, not the signal-terminated nc child it manages.
|
|
82
|
+
if [[ -n "$listener_pid" ]]; then
|
|
83
|
+
kill "$listener_pid" 2>/dev/null || :
|
|
84
|
+
fi
|
|
85
|
+
wait "$supervisor_pid" 2>/dev/null || :
|
|
86
|
+
rm -f "$pid_file"
|
|
87
|
+
return "$rc"
|
|
88
|
+
}
|
|
89
|
+
|
|
31
90
|
Describe 'prc_kill_by_port'
|
|
32
91
|
It 'signal term'
|
|
33
|
-
|
|
92
|
+
test_port=$((22000 + RANDOM % 20000))
|
|
93
|
+
func() { prc_test_kill_listener "$test_port" 15; }
|
|
34
94
|
When run func
|
|
35
95
|
The status should be success
|
|
36
|
-
The value "${actual}" should include "
|
|
96
|
+
The value "${actual}" should include ":${test_port}"
|
|
97
|
+
The value "${actual}" should include "Start to kill port listener by signal 15..."
|
|
37
98
|
End
|
|
38
99
|
|
|
39
100
|
It 'signal kill'
|
|
40
|
-
|
|
101
|
+
test_port=$((22000 + RANDOM % 20000))
|
|
102
|
+
func() { prc_test_kill_listener "$test_port" 9; }
|
|
41
103
|
When run func
|
|
42
104
|
The status should be success
|
|
43
|
-
The value "${actual}" should include "
|
|
105
|
+
The value "${actual}" should include ":${test_port}"
|
|
106
|
+
The value "${actual}" should include "Start to kill port listener by signal 9..."
|
|
44
107
|
End
|
|
45
108
|
|
|
46
109
|
It 'port not listened'
|
|
@@ -52,39 +115,70 @@ Describe 'prc_kill_by_port'
|
|
|
52
115
|
End
|
|
53
116
|
|
|
54
117
|
|
|
118
|
+
# The default macOS "ps" output depends on the controlling terminal.
|
|
119
|
+
# GitHub Actions has no such terminal, so these unit tests provide an explicit
|
|
120
|
+
# process table. Do not change the public functions' existing `ps` behavior.
|
|
55
121
|
Describe 'prc_filter_by_cmd'
|
|
122
|
+
ps() {
|
|
123
|
+
printf '%s\n' \
|
|
124
|
+
' PID TTY TIME CMD' \
|
|
125
|
+
'999999997 ttys001 0:00.00 /bin/bash -c fixture' \
|
|
126
|
+
'999999998 ttys001 0:00.00 sleep 10'
|
|
127
|
+
}
|
|
128
|
+
|
|
56
129
|
It 'with command'
|
|
57
130
|
When call prc_filter_by_cmd bash
|
|
58
131
|
The status should be success
|
|
59
|
-
The output should include
|
|
132
|
+
The output should include '/bin/bash -c fixture'
|
|
133
|
+
The output should not include 'sleep 10'
|
|
60
134
|
End
|
|
61
135
|
|
|
62
136
|
It 'without command'
|
|
63
137
|
When call prc_filter_by_cmd
|
|
64
138
|
The status should be success
|
|
65
|
-
The output should include
|
|
139
|
+
The output should include '/bin/bash -c fixture'
|
|
140
|
+
The output should include 'sleep 10'
|
|
66
141
|
End
|
|
67
142
|
End
|
|
68
143
|
|
|
69
144
|
|
|
70
145
|
Describe 'prc_kill_by_cmd'
|
|
146
|
+
# ps emits a deterministic table; fake PIDs cannot refer to real processes.
|
|
147
|
+
ps() {
|
|
148
|
+
printf '%s\n' \
|
|
149
|
+
' PID TTY TIME CMD' \
|
|
150
|
+
'999999997 ttys001 0:00.00 /bin/bash -c fixture' \
|
|
151
|
+
'999999998 ttys001 0:00.00 sleep 10'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# xargs invokes an external `kill`, not a shell function. Use ShellSpec's
|
|
155
|
+
# command-based Mock so signals are checked without terminating processes.
|
|
156
|
+
Mock kill
|
|
157
|
+
printf 'MOCK_KILL:'
|
|
158
|
+
printf ' <%s>' "$@"
|
|
159
|
+
printf '\n'
|
|
160
|
+
End
|
|
161
|
+
|
|
71
162
|
It 'signal term'
|
|
72
|
-
|
|
73
|
-
When run func
|
|
163
|
+
When call prc_kill_by_cmd sleep
|
|
74
164
|
The status should be success
|
|
75
|
-
The
|
|
165
|
+
The output should include 'Start to kill command by signal 15...'
|
|
166
|
+
The output should include 'MOCK_KILL: <-15> <999999998>'
|
|
167
|
+
The output should not include 'MOCK_KILL: <-15> <999999997>'
|
|
76
168
|
End
|
|
77
169
|
|
|
78
170
|
It 'signal kill'
|
|
79
|
-
|
|
80
|
-
When run func
|
|
171
|
+
When call prc_kill_by_cmd sleep 9
|
|
81
172
|
The status should be success
|
|
82
|
-
The
|
|
173
|
+
The output should include 'Start to kill command by signal 9...'
|
|
174
|
+
The output should include 'MOCK_KILL: <-9> <999999998>'
|
|
175
|
+
The output should not include 'MOCK_KILL: <-9> <999999997>'
|
|
83
176
|
End
|
|
84
177
|
|
|
85
178
|
It 'not found command'
|
|
86
179
|
When call prc_kill_by_cmd xxxx
|
|
87
180
|
The status should be success
|
|
88
|
-
The output should include
|
|
181
|
+
The output should include 'No command to kill'
|
|
182
|
+
The output should not include 'MOCK_KILL:'
|
|
89
183
|
End
|
|
90
184
|
End
|
package/spec/reflection.spec.sh
CHANGED
|
@@ -120,7 +120,7 @@ Describe 'reflect_function_names_of_file'
|
|
|
120
120
|
When call reflect_function_names_of_file "${shellScriptFile}"
|
|
121
121
|
The status should eq "0"
|
|
122
122
|
The output should include "reflect_nth_arg"
|
|
123
|
-
The lines of output should eq
|
|
123
|
+
The lines of output should eq 67
|
|
124
124
|
|
|
125
125
|
rm -fr "${shellScriptFile}"
|
|
126
126
|
End
|
package/spec/spec_helper.sh
CHANGED
|
@@ -16,3 +16,20 @@ array_describe_equals() {
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
|
|
20
|
+
# Compare ordered array values across Bash and zsh without assuming native
|
|
21
|
+
# indexed-array numbering or typeset/declare output formatting.
|
|
22
|
+
array_values_equal() {
|
|
23
|
+
local array_name="$1"
|
|
24
|
+
shift
|
|
25
|
+
local -a __bb_test_values
|
|
26
|
+
local element
|
|
27
|
+
|
|
28
|
+
eval "__bb_test_values=( \"\${${array_name}[@]}\" )" || return 1
|
|
29
|
+
[[ ${#__bb_test_values[@]} -eq $# ]] || return 1
|
|
30
|
+
for element in "${__bb_test_values[@]}"; do
|
|
31
|
+
[[ "$element" == "$1" ]] || return 1
|
|
32
|
+
shift
|
|
33
|
+
done
|
|
34
|
+
return 0
|
|
35
|
+
}
|
package/spec/string.spec.sh
CHANGED
|
@@ -11,7 +11,7 @@ Describe 'string_split_to_array'
|
|
|
11
11
|
It 'with variable'
|
|
12
12
|
str="a b c"
|
|
13
13
|
When call string_split_to_array ' ' actual "$str"
|
|
14
|
-
The variable actual should satisfy
|
|
14
|
+
The variable actual should satisfy array_values_equal actual 'a' 'b' 'c'
|
|
15
15
|
End
|
|
16
16
|
|
|
17
17
|
It 'with pipe and print to stdout'
|
|
@@ -23,7 +23,7 @@ Describe 'string_split_to_array'
|
|
|
23
23
|
It 'with token string including space'
|
|
24
24
|
str="amy delimiterbmy delimiterc"
|
|
25
25
|
When call string_split_to_array 'my delimiter' actual "$str"
|
|
26
|
-
The variable actual should satisfy
|
|
26
|
+
The variable actual should satisfy array_values_equal actual 'a' 'b' 'c'
|
|
27
27
|
End
|
|
28
28
|
|
|
29
29
|
It 'with stdin'
|
|
@@ -33,7 +33,7 @@ Describe 'string_split_to_array'
|
|
|
33
33
|
* origin/feature/52-new-feature
|
|
34
34
|
EOF
|
|
35
35
|
When call string_split_to_array $'\n' actual "${lines}"
|
|
36
|
-
The variable actual should satisfy
|
|
36
|
+
The variable actual should satisfy array_values_equal actual ' origin/develop' ' origin/integration' '* origin/feature/52-new-feature'
|
|
37
37
|
End
|
|
38
38
|
|
|
39
39
|
It 'with file'
|
|
@@ -43,7 +43,7 @@ Describe 'string_split_to_array'
|
|
|
43
43
|
3. C
|
|
44
44
|
EOF
|
|
45
45
|
When call eval "string_split_to_array $'\n' actual < temp_file.txt"
|
|
46
|
-
The variable actual should satisfy
|
|
46
|
+
The variable actual should satisfy array_values_equal actual ' 1. A D' ' 2. B' ' 3. C'
|
|
47
47
|
|
|
48
48
|
rm -fr temp_file.txt
|
|
49
49
|
End
|
|
@@ -54,12 +54,12 @@ Describe 'string_pick_to_array'
|
|
|
54
54
|
It 'with variable'
|
|
55
55
|
str="[{age:12},{age:15},{age:16}]"
|
|
56
56
|
When call string_pick_to_array '{age:' '}' actual "$str"
|
|
57
|
-
The variable actual should satisfy
|
|
57
|
+
The variable actual should satisfy array_values_equal actual '12' '15' '16'
|
|
58
58
|
End
|
|
59
59
|
|
|
60
60
|
It 'with pipe and print to stdout'
|
|
61
61
|
str="[{age:12},{age:15},{age:16}]"
|
|
62
|
-
When call eval "
|
|
62
|
+
When call eval 'printf "%s\n" "$str" | string_pick_to_array "{age:" "}"'
|
|
63
63
|
The output should eq $'12\n15\n16'
|
|
64
64
|
End
|
|
65
65
|
|
|
@@ -78,7 +78,7 @@ Describe 'string_pick_to_array'
|
|
|
78
78
|
]
|
|
79
79
|
EOF
|
|
80
80
|
When call string_pick_to_array 'age:' $'\n' actual "${lines}"
|
|
81
|
-
The variable actual should satisfy
|
|
81
|
+
The variable actual should satisfy array_values_equal actual '12' '15' '16'
|
|
82
82
|
End
|
|
83
83
|
|
|
84
84
|
It 'with file'
|
|
@@ -96,7 +96,7 @@ Describe 'string_pick_to_array'
|
|
|
96
96
|
]
|
|
97
97
|
EOF
|
|
98
98
|
When call eval "string_pick_to_array 'age:' $'\n' actual < temp_file.txt"
|
|
99
|
-
The variable actual should satisfy
|
|
99
|
+
The variable actual should satisfy array_values_equal actual '12' '15' '16'
|
|
100
100
|
|
|
101
101
|
rm -fr temp_file.txt
|
|
102
102
|
End
|
|
@@ -435,9 +435,8 @@ Describe 'string_before_first'
|
|
|
435
435
|
EFGH
|
|
436
436
|
IJKL
|
|
437
437
|
EOF
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
The variable actual should eq $'ABCD\nE'
|
|
438
|
+
When call eval 'string_before_first "FGH" < temp_file.txt'
|
|
439
|
+
The output should eq $'ABCD\nE'
|
|
441
440
|
rm -fr temp_file.txt
|
|
442
441
|
End
|
|
443
442
|
End
|
|
@@ -465,9 +464,8 @@ Describe 'string_after_first'
|
|
|
465
464
|
EFGH
|
|
466
465
|
IJKL
|
|
467
466
|
EOF
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
The variable actual should eq $'\nIJKL'
|
|
467
|
+
When call eval 'string_after_first "FGH" < temp_file.txt'
|
|
468
|
+
The output should eq $'\nIJKL'
|
|
471
469
|
rm -fr temp_file.txt
|
|
472
470
|
End
|
|
473
471
|
End
|
|
@@ -548,9 +546,8 @@ Describe 'string_index_first'
|
|
|
548
546
|
EFGH
|
|
549
547
|
IJKL
|
|
550
548
|
EOF
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
The variable actual should eq "6"
|
|
549
|
+
When call eval 'string_index_first "FGH" < temp_file.txt'
|
|
550
|
+
The output should eq "6"
|
|
554
551
|
rm -fr temp_file.txt
|
|
555
552
|
End
|
|
556
553
|
End
|
package/src/alias.sh
CHANGED
|
@@ -1,9 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
-
shopt -s expand_aliases 2>/dev/null # This file is for compatibility with previous versions
|
|
4
|
-
|
|
5
3
|
source src/out.sh
|
|
6
4
|
source src/tool.sh
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
# Backward-compatible command names. Use wrapper functions rather than aliases:
|
|
7
|
+
# aliases need different non-interactive expansion rules in Bash and zsh, while
|
|
8
|
+
# functions work identically when this library is sourced by either shell.
|
|
9
|
+
|
|
10
|
+
# @NAME
|
|
11
|
+
# args_confirm -- show the name and value of variables, and continue execute if args_confirm by user, or exit if not
|
|
12
|
+
# @SYNOPSIS
|
|
13
|
+
# args_confirm variableName...
|
|
14
|
+
# @DESCRIPTION
|
|
15
|
+
# **variableName...** some existed variable names to show its value
|
|
16
|
+
# @EXAMPLES
|
|
17
|
+
# a="correct value"
|
|
18
|
+
# b="wrong value"
|
|
19
|
+
# args_confirm a b
|
|
20
|
+
# @SEE_ALSO
|
|
21
|
+
# args_print, stop_if_failed
|
|
22
|
+
args_confirm() {
|
|
23
|
+
confirm_to_continue "$@"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# @NAME
|
|
27
|
+
# args_print -- if LOG_LEVEL<=$LOG_LEVEL_WARN, show the name and value of variables
|
|
28
|
+
# @SYNOPSIS
|
|
29
|
+
# args_print variableName...
|
|
30
|
+
# @DESCRIPTION
|
|
31
|
+
# **variableName...** some existed variable names to show its value
|
|
32
|
+
# @EXAMPLES
|
|
33
|
+
# var1="value 1"
|
|
34
|
+
# var2="value 2"
|
|
35
|
+
# args_print var1 var2
|
|
36
|
+
# @SEE_ALSO
|
|
37
|
+
# print_header, print_error, print_success, print_warn, print_info, print_debug
|
|
38
|
+
args_print() {
|
|
39
|
+
print_args "$@"
|
|
40
|
+
}
|