NBash
476 строк · 11.9 Кб
1#!/usr/bin/env bash
2# -*- tab-width: 4; encoding: utf-8 -*-
3#
4#
5#
6
7PS4='+ ${FUNCNAME:-main}${LINENO:+:$LINENO}>'
8
9# If it works with failglob and nounset, it will work without.
10shopt -s failglob
11set -o nounset
12
13. ./argsparse.sh
14
15shopt -s extdebug
16errors=0
17disabled=0
18
19rm -f unittest.log unittest.env.log
20
21err_trap() {
22: $((errors+=1))
23return 1
24}
25
26trap err_trap ERR
27
28print_exit_status() {
29# Prints [OK] at the end of the screen of first argument is 0,
30# else [FAILURE].
31# 1st Parameter: a number, usually the exit status of your
32# previous command. If omitted, will use $?.
33# returns the first parameter value
34local ret="${1:-$?}"
35# If you want you can override the FAILED and OK messages by
36# pre-defining those variables.
37local FAILED=${FAILED:-FAILED}
38local OK=${OK:-OK}
39# Move to column default is 70
40local COL=${COL:=70}
41[[ -t 1 ]] && echo -en "\033[${COL}G"
42# Colors
43if [[ -t 1 ]]
44then
45local COLOR_SUCCESS=${COLOR_SUCCESS:-'\033[1;32m'}
46local COLOR_FAILURE=${COLOR_FAILURE:-'\033[1;31m'}
47local COLOR_WARNING='\033[1;33m' COLOR_NORMAL='\033[0;39m'
48else
49local COLOR_SUCCESS= COLOR_FAILURE= COLOR_WARNING= COLOR_NORMAL=
50fi
51[[ "$ret" -eq 0 ]] && echo -e "[$COLOR_SUCCESS$OK$COLOR_NORMAL]" || \
52echo -e "[$COLOR_FAILURE$FAILED$COLOR_NORMAL]"
53return $ret
54}
55
56echo_for_print_status() {
57local m=$1
58if tty >/dev/null 2>&1 && [[ -n "${PRINT_STATUS_COLOR:-}" ]]
59then
60m="$(tput setaf $PRINT_STATUS_COLOR)$m$(tput sgr0)"
61fi
62printf "%s: " "$m"
63}
64
65exec_and_print_status() {
66# prints a message, execute a command and print its exit status
67# using print_exit_status function.
68# 1st Parameter: a message
69# all other parameter: the command to execute
70# returns the exit status code of the command
71[[ $# -lt 2 ]] && return 1
72local m=$1 ; shift
73PRINT_STATUS_COLOR="${PRINT_STATUS_COLOR-}" \
74echo_for_print_status "$m"
75"$@"
76print_exit_status $?
77}
78
79default_test() {
80[[ $? -eq 0 ]]
81}
82
83failure() {
84[[ $? -ne 0 ]]
85}
86
87shell_env() {
88# Greps are:
89# * variables automatically altered by bash itself.
90# * local environment variables, used by the environment checking routines.
91# * argsparse private variables, subjects to modifications/deletion.
92# * argsparse public variables.
93# * argsparse results, which are expected to change.
94set | \
95grep -vE '^(FIRST|SECOND|SHELLOPTS|FUNCNAME|_|LINENO|BASH_(ARG[CV]|LINENO|SOURCE|REMATCH)|PIPESTATUS)=' | \
96grep -vE '^before=' | \
97grep -vE '^__argsparse_(options_descriptions|short_options|tmp_identifiers)=' | \
98grep -vE '^argsparse_usage_description=' | \
99grep -vE '^(program_(params|options))|cumulated_values_[0-9a-zA-Z_]+='
100shopt
101set -o | grep -v xtrace
102}
103
104check_env() {
105{
106printf "For test: %s\n" "$message"
107diff -u <(echo "$before") <(shell_env)
108} >> unittest.env.log
109}
110
111parse_option_wrapper() {
112local message=$1
113local TEST=${TEST:-default_test}
114shift
115local before=$(shell_env)
116echo_for_print_status "Checking $message"
117(
118printf "Test is: %s\n" "$message"
119printf "Validation is: %s\n" "$TEST"
120set -x
121(
122trap check_env EXIT
123argsparse_parse_options "$@"
124${INNERTEST:-exit $?}
125)
126) >>unittest.log 2>&1
127$TEST
128print_exit_status || exit
129}
130
131(
132argsparse_usage_description="Usage has been triggered"
133echo_for_print_status "Checking no parameter triggers usage"
134(
135argsparse_parse_options
136) | grep -q "$argsparse_usage_description"
137[ ${PIPESTATUS[0]} -ne 0 -a ${PIPESTATUS[1]} -eq 0 ]
138print_exit_status
139)
140
141(
142argsparse_allow_no_argument yes
143argsparse_use_option option ""
144parse_option_wrapper "argsparse_allow_no_argument yes"
145)
146
147(
148argsparse_usage_description="Usage has been triggered"
149echo_for_print_status "Checking dummy option triggers usage"
150(
151argsparse_parse_options --dumb 2>/dev/null
152) | grep -q "$argsparse_usage_description"
153[ ${PIPESTATUS[0]} -ne 0 -a ${PIPESTATUS[1]} -eq 0 ]
154print_exit_status
155)
156
157(
158argsparse_use_option first-option "first option description"
159echo_for_print_status "Checking if option description appear in usage"
160(
161argsparse_parse_options -h
162) | grep -q "first option description"
163print_exit_status
164)
165
166(
167argsparse_use_option shortcut ""
168parse_option_wrapper "option detection" --shortcut
169
170argsparse_set_option_property short:S shortcut
171parse_option_wrapper "short property" -S
172)
173
174(
175argsparse_use_option =shortcut ""
176parse_option_wrapper "= in optstring" -s
177)
178
179(
180argsparse_use_option option1 "" type:weird:value
181echo_for_print_status "Checking 'weird:value' property value"
182[[ $(argsparse_has_option_property option1 type) = 'weird:value' ]]
183print_exit_status
184)
185
186(
187argsparse_use_option option1 ""
188for weirdo in '?' , '*' '!'
189do
190(
191echo_for_print_status "Checking forbidden '$weirdo' property value"
192argsparse_set_option_property type:"foo${weirdo}bar" option1 2>/dev/null
193failure
194print_exit_status
195)
196done
197)
198
199value_check_test() {
200[[ ${program_options[$value_check_option]} = $value_check_value ]]
201}
202
203value_check() {
204local value_check_option=$1
205local value_check_value=$2
206shift 2
207INNERTEST=value_check_test parse_option_wrapper "$@"
208}
209
210(
211argsparse_use_option value ""
212argsparse_set_option_property value value
213TEST=failure parse_option_wrapper "if value property expects value" --value
214value_check value 1 "if value is correctly detected" --value 1
215)
216
217(
218argsparse_use_option value: ""
219value_check value 1 "if value is correctly detected with optstring" --value 1
220TEST=failure parse_option_wrapper "if value property expects value if set with optstring" --value
221)
222
223(
224argsparse_use_option option1 ""
225argsparse_use_option option2 "" mandatory
226TEST=failure parse_option_wrapper "if missing mandatory option triggers error" --option1
227parse_option_wrapper "if mandatory option makes return code 0" --option1 --option2
228argsparse_use_option option3 "" mandatory
229TEST=failure parse_option_wrapper "if missing mandatory options trigger error (1)" --option1
230TEST=failure parse_option_wrapper "if missing mandatory options trigger error (2)" --option1 --option2
231TEST=failure parse_option_wrapper "if missing mandatory options trigger error (3)" --option1 --option3
232parse_option_wrapper "if missing mandatory options trigger error (4)" --option3 --option2
233)
234
235cumul_test() {
236[[ $? -ne 0 ]] && return 1
237local cumul="cumulated_values_$option[@]"
238# Shoud never trigger nounset
239local -a array=( "${!cumul}" )
240local i
241[[ ${#array[@]} -eq "${#cumul_array[@]}" ]] || return 1
242for ((i=0 ; i < ${#array[@]} ; i++))
243do
244[[ ${array[$i]} = "${cumul_array[$i]}" ]] || return 1
245done
246return 0
247}
248
249(
250argsparse_use_option option1 "" cumulative
251option=option1
252cumul_array=(1 2 1 2)
253INNERTEST=cumul_test parse_option_wrapper "cumulative property behaviour" "${cumul_array[@]/#/--option1=}"
254)
255
256(
257option=option2
258cumul_array=(1 2)
259argsparse_use_option option2 "" cumulativeset
260INNERTEST=cumul_test parse_option_wrapper "cumulativeset property behaviour" "${cumul_array[@]/#/--option2=}" "${cumul_array[@]/#/--option2=}"
261)
262
263(
264argsparse_use_option option1 ""
265argsparse_use_option option2 "" alias:option1
266value_check option1 1 "simple alias" --option1
267argsparse_use_option option3 "" alias:option2
268value_check option1 1 "recursive alias" --option1
269)
270
271(
272argsparse_use_option option1 ""
273argsparse_use_option option2 "" require:option1
274parse_option_wrapper "simple require (1)" --option2 --option1
275TEST=failure parse_option_wrapper "simple require (2)" --option2
276argsparse_use_option option3 "" require:option2
277TEST=failure parse_option_wrapper "recursive require (1)" --option3
278TEST=failure parse_option_wrapper "recursive require (2)" --option3 --option2
279parse_option_wrapper "recursive require (3)" --option3 --option2 --option1
280)
281
282(
283argsparse_use_option option1 ""
284argsparse_use_option option2 "" exclude:option1
285TEST=failure parse_option_wrapper "exclusion" --option2 --option1
286)
287
288(
289argsparse_use_option option1 ""
290argsparse_use_option option2 "" default:1 exclude:option1
291parse_option_wrapper "exclusion does not prevent default values" --option1
292)
293
294(
295argsparse_use_option option1 ""
296argsparse_use_option option2 ""
297argsparse_use_option option3 "" require:"option1 option2"
298TEST=failure parse_option_wrapper "multiple require (1)" --option3
299TEST=failure parse_option_wrapper "multiple require (2)" --option3 --option2
300TEST=failure parse_option_wrapper "multiple require (3)" --option3 --option1
301parse_option_wrapper "multiple require (4)" --option3 --option2 --option1
302)
303
304
305(
306declare -a option_option1_values=(accept)
307argsparse_use_option option1: ""
308TEST=failure parse_option_wrapper "accepted values (bad value)" \
309--option1 foo
310parse_option_wrapper "accepted values (good value)" --option1 accept
311)
312
313dir=$(mktemp -d)
314file=$(mktemp --tmpdir="$dir")
315declare -A types=(
316[link]="$dir/link1 $dir/link2"
317[file]="$file $dir/link1"
318[directory]=". .. $dir $dir/linkdir"
319[char]="c . 0 @"
320[unsignedint]=123
321[int]="1 123 0 -1 -938945349"
322[hexa]="0x123abc 71234 abc"
323[ipv4]="192.168.40.254 127.0.0.1 1.2.3.4"
324[ipv6]="2001:7a8:b018::1 ::1 2001:7a8:b018:0:21f:c6ff:fe59:71fd"
325[username]=$(whoami)
326[group]=$(id -gn)
327[port]="ssh 80"
328[portnumber]=1234
329)
330
331fifo="$dir/fifo"
332if mkfifo "$fifo"
333then
334types[pipe]=$fifo
335else
336printf "Could not create persistent fifo. Disabling pipe type test.\n"
337: $((disabled++))
338fi
339
340for socket in /dev/log
341do
342[[ -S $socket ]] || continue
343types[socket]=$socket
344done
345
346# Look for a terminal
347for stdfd in 0
348do
349[[ -t $stdfd ]] || continue
350types[terminal]=$stdfd
351done
352
353for t in terminal socket
354do
355if [[ ${types[$t]-unset} = unset ]]
356then
357: $((disabled++))
358printf "No %s found: won't test.\n" "$t"
359fi
360done
361
362# Default 'host' type test case:
363types[host]="${types[ipv4]} ${types[ipv6]}"
364# Test hostnames only if we can actually resolv.
365if host localhost >/dev/null 2>&1
366then
367types[hostname]="livna.org localhost"
368types[host]+=" ${types[hostname]}"
369else
370printf "Cannot resolv localhost. Not testing hostname type.\n"
371: $((disabled++))
372fi
373
374(cd "$dir" && ln -s "$file" link1 && ln -s asdf link2 && ln -s . linkdir)
375
376for type in "${!types[@]}"
377do
378(
379argsparse_use_option option: "" "type:$type"
380# Left unquoted on purpose. There's no funny chars in this array.
381i=1
382for value in ${types[$type]}
383do
384parse_option_wrapper "type '$type' ($i)" --option "$value"
385: $((i++))
386done
387)
388done
389
390declare -A bad_types=(
391[file]=". .. $dir $fifo"
392[directory]="${types[file]} $fifo $dir/link1"
393[pipe]="${types[file]} ${types[dir]-}"
394[socket]="${types[file]} $fifo $dir/link1"
395[link]="$file $fifo"
396[char]="12 abc"
397[unsignedint]="-1 -2234958 abc"
398[int]="a b casdf"
399[ipv4]="${types[ipv6]}"
400[ipv6]="${types[ipv4]}"
401[host]="livna.org1"
402[username]="asdkfjasdkfanfk1234"
403[group]="asdkfjasdkfanfk1234"
404[port]="12345678 foobar"
405[portnumber]=12345678
406[hostname]="livna.org1 google.com1"
407)
408
409for type in "${!bad_types[@]}"
410do
411(
412argsparse_use_option option: "" "type:$type"
413# Left unquoted on purpose. (again)
414i=1
415for value in ${bad_types[$type]}
416do
417TEST=failure parse_option_wrapper "failure with type '$type' ($i)" --option "$value"
418: $((i++))
419done
420)
421done
422
423(
424argsparse_use_option option: "" "type:terminal"
425TEST=failure parse_option_wrapper "failure with type 'terminal' (1)" --option 0 </dev/null
426)
427rm -Rf "$dir"
428
429(
430check_option_type_unittest() {
431local value=$1
432[[ $value = 1 ]]
433}
434argsparse_use_option option: "" type:unittest
435parse_option_wrapper "custom type (1)" --option 1
436TEST=failure parse_option_wrapper "custom type (2)" --option asdf
437)
438
439printf "Tests report:\n"
440if [[ $disabled -ne 0 ]]
441then
442printf "* %d test(s) disabled.\n" "$disabled"
443fi
444
445if [[ $errors -ne 0 ]]
446then
447printf "* %d error(s) encountered. (See above)\n" "$errors"
448exit_code=1
449else
450printf "All tests passed.\n"
451exit_code=0
452fi
453
454printf "Environment alteration detected:\n"
455if grep -v -B 1 '^For test:' unittest.env.log
456then
457: exit_code $((exit_code))
458else
459printf "None.\n"
460fi
461
462if [[ $exit_code -ne 0 ]]
463then
464printf "Runtime environment was:\n"
465set -x
466{
467command -v bash
468bash --version
469shopt
470set -o
471} 2>&1
472fi
473
474printf "Completed in %d seconds.\n" "$SECONDS"
475
476exit "$exit_code"
477