1#!/bin/sh 2 3# A tester for extfs helpers. 4# 5# Copyright (C) 2016-2017 6# The Free Software Foundation, Inc. 7# 8# This file is part of the Midnight Commander. 9# 10# The Midnight Commander is free software: you can redistribute it 11# and/or modify it under the terms of the GNU General Public License as 12# published by the Free Software Foundation, either version 3 of the License, 13# or (at your option) any later version. 14# 15# The Midnight Commander is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License 21# along with this program. If not, see <http://www.gnu.org/licenses/>. 22 23help() { 24 cat << EOS 25 26NAME 27 28$(basename "$0") - Tests the 'list' command of extfs helpers. 29 30SYNOPSIS 31 32 $(basename "$0") \\ 33 --data-dir /path/to/where/data/files/are/stored \\ 34 --helpers-dir /path/to/where/helpers/are/stored \\ 35 --data-build-dir /path/to/where/config.sh/is/stored 36 37(But you're more likely to invoke this program with the 'run' script 38created by 'make check'; or by 'make check' itself.) 39 40DESCRIPTION 41 42This program tests extfs helpers by feeding them input and comparing 43their output to the expected output. 44 45See README for full details. 46 47You need to tell this program primarily two things: where the helpers are 48stored, and where the "data files" are stored. The data files are *.input 49files that are fed to the helpers and *.output files that are the correct 50output expected from these helpers. 51 52You also need to tell this program where the build flavor of the "data 53files" is stored. Most notably this is where the 'config.sh' file is 54created during build time. You do this with '--data-build-dir'. 55 56EOS 57} 58 59#"' 60 61# 62# Some helpers use the 'sort' utility. The "expected output" files we 63# provide must be generated in the same locale these helpers are to be run 64# by the tester or else 'sort' will produce a different output than ours, 65# failing the tests. 66# 67# We settle on the C locale. 68# 69LC_ALL=C 70export LC_ALL 71 72############################ Global variables ############################## 73 74# The directories used. 75data_dir= 76data_build_dir= 77helpers_dir1= 78helpers_dir2= 79 80opt_create_output=no # "yes" if '--create-output' provided. 81opt_run_mcdiff_on_error=no # "yes" if '--mcdiff' provided. 82 83############################ Coding guidance ############################### 84 85# 86# Portability notes: 87# 88# - We do `local var="$whatever"` instead of `local var=$whatever` for 89# compatibility with Dash. See http://unix.stackexchange.com/questions/97560. 90# 91# - The 'local' keyword used in this file isn't mandatory. Feel free to 92# remove it if it isn't supported by your archaic shell. 93# 94 95############################ Utility functions ############################# 96 97# 98# Does $1 contain $2? 99# 100# Accepts basic regex. 101# 102has_string() { 103 local haystack="$1" # quotes needed for Dash, as may contain spaces (see notes above). 104 local needle="$2" 105 echo "$haystack" | grep "$needle" > /dev/null 106} 107 108# 109# Given "/path/to/basename.and.some.ext", returns "basename" 110# 111basename_sans_extensions() { 112 local base="$(basename "$1")" 113 echo "${base%%.*}" 114} 115 116# 117# Does an executable exist? 118# 119has_prog() { 120 # see http://stackoverflow.com/questions/592620 121 command -v "$1" >/dev/null 2>&1 122} 123 124# 125# Are we running interactively? Or is our output redirected to a file/pipe? 126# 127is_interactive() { 128 [ -t 1 ] 129} 130 131# 132# Can we use colors? 133# 134has_colors() { 135 is_interactive && has_string "$TERM" 'linux\|xterm\|screen\|tmux\|putty' 136} 137 138init_colors() { 139 if has_colors; then 140 local esc="$(printf '\033')" # for portability 141 C_bold="$esc[1m" 142 C_green="$esc[1;32m" 143 C_red="$esc[1;31m" 144 C_magenta="$esc[1;35m" 145 C_norm="$esc[0m" 146 fi 147} 148 149# 150# A few colorful alternatives to 'echo'. 151# 152header() { echo $C_bold"$@"$C_norm; } 153err() { echo $C_red"$@"$C_norm; } 154notice() { echo $C_magenta"$@"$C_norm; } 155success() { echo $C_green"$@"$C_norm; } 156 157die() { 158 err "Error: $@" 159 exit 1 160} 161 162assert_dir_exists() { 163 [ -d "$1" ] || die "The directory '$1' doesn't exist, or is not a directory." 164} 165 166# 167# Creates a temporary file. 168# 169temp_file() { 170 local template="$1" 171 # BSD's doesn't support -t. 172 mktemp "${TMPDIR:-/tmp}/$template" 173} 174 175################################ Main code ################################# 176 177# 178# Prints out the command to run a helper, if it can find it. 179# 180# For example, 181# 182# find_helper uzip /path/to/helpers/dir 183# 184# prints: 185# 186# /usr/bin/perl -w /path/to/helpers/dir/uzip 187# 188# Since helpers in the build tree don't yet have executable bit set, we 189# need to extract the shebang line. 190# 191find_helper() { 192 local helper_name="$1" 193 local dir="$2" 194 195 local try="$dir/$helper_name" 196 if [ -f "$try" ]; then 197 helper_CMD="$(head -1 $try | cut -c 3-) $try" # reason #1 we don't allow spaces in pathnames. 198 true 199 else 200 false 201 fi 202} 203 204# 205# Returns the path of 'config.sh'. 206# 207path_of_config_sh() { 208 echo "$data_build_dir/config.sh" 209} 210 211# 212# Export variables to be used by tests. 213# 214# See README for their documentation. 215# 216export_useful_variables() { 217 local input="$1" 218 219 # Frequently used variables: 220 MC_TEST_EXTFS_LIST_CMD="mc_xcat $input" # reason #2 we don't allow spaces in pathnames. 221 export MC_TEST_EXTFS_LIST_CMD 222 223 # Infrequently used variables: 224 MC_TEST_EXTFS_INPUT=$input 225 export MC_TEST_EXTFS_INPUT 226 MC_TEST_EXTFS_DATA_DIR=$data_dir 227 export MC_TEST_EXTFS_DATA_DIR 228 MC_TEST_EXTFS_DATA_BUILD_DIR=$data_build_dir 229 export MC_TEST_EXTFS_DATA_BUILD_DIR 230 MC_TEST_EXTFS_CONFIG_SH=$(path_of_config_sh) 231 export MC_TEST_EXTFS_CONFIG_SH 232} 233 234# 235# The crux of this program. 236# 237run() { 238 239 local error_count=0 240 local pass_count=0 241 242 for input in "$data_dir"/*.input; do 243 244 has_string "$input" '\*' && break # we can't use 'shopt -s nullglob' as it's bash-specific. 245 246 header "Testing $input" 247 248 has_string "$input" " " && die "Error: filename contains spaces." 249 250 # 251 # Set up variables: 252 # 253 254 local helper_name="$(basename_sans_extensions "$input")" 255 local expected_parsed_output="${input%.input}.output" 256 local env_vars_file="${input%.input}.env_vars" 257 local args_file="${input%.input}.args" 258 259 local do_create_output=no 260 261 if [ ! -f "$expected_parsed_output" ]; then 262 # Corresponding *.output file doesn't exist. We either create it, later, or exit with error. 263 if [ $opt_create_output = "yes" ]; then 264 do_create_output=yes 265 else 266 err 267 err "Missing file: '$expected_parsed_output'." 268 err "You have to create an '.output' file for each '.input' one." 269 err 270 notice "Tip: invoke this program with '--create-output' to" 271 notice "automatically create missing '.output' files." 272 notice 273 exit 1 274 fi 275 fi 276 277 find_helper "$helper_name" "$helpers_dir1" || 278 find_helper "$helper_name" "$helpers_dir2" || 279 die "I can't find helper '$helper_name' in either $helpers_dir1 or $helpers_dir2" 280 281 local extra_parser_args="" 282 [ -f "$args_file" ] && extra_parser_args="$(cat "$args_file")" 283 284 local actual_output="$(temp_file $helper_name.actual-output.XXXXXXXX)" 285 local actual_parsed_output="$(temp_file $helper_name.actual-parsed-output.XXXXXXXX)" 286 287 # 288 # Variables are all set. Now do the actual stuff: 289 # 290 291 ( 292 export_useful_variables "$input" 293 if [ -f "$env_vars_file" ]; then 294 set -a # "allexport: Export all variables assigned to." 295 . "$env_vars_file" 296 set +a 297 fi 298 $helper_CMD list /dev/null > "$actual_output" 299 ) 300 301 error_count=$((error_count + 1)) # we'll decrement it later. 302 303 if [ ! -s "$actual_output" ]; then 304 err 305 err "The helper '$helper_name' produced no output for this input. Something is wrong." 306 err 307 err "Make sure this helper supports testability: that it uses \$MC_TEST_EXTFS_LIST_CMD." 308 err 309 err "You may try running the helper yourself with:" 310 err 311 err " \$ MC_TEST_EXTFS_LIST_CMD=\"mc_xcat $input\" \\" 312 err " $helper_CMD list /dev/null" 313 err 314 continue 315 fi 316 317 # '--symbolic-ids': uid/gid aren't portable between computers, 318 # of course, so we always represent them symbolically when possible. 319 if ! mc_parse_ls_l --symbolic-ids $extra_parser_args "$actual_output" > "$actual_parsed_output"; then 320 err 321 err "ERROR: Parsing of the output of the helper '$helper_name' has failed." 322 err "This means that $helper_name has produced output that MC won't be able to parse." 323 err "Run the parsing command yourself ('mc_parse_ls_l $extra_parser_args $actual_output')" 324 err "to figure out the problem." 325 err 326 continue 327 fi 328 329 if [ $do_create_output = "yes" ]; then 330 # We arrive here if we were invoked with '--create-output' and 331 # the .output file doesn't exist. We create it and move to the next iteration. 332 cp "$actual_parsed_output" "$expected_parsed_output" 333 notice "The output file has been created in $expected_parsed_output" 334 continue 335 fi 336 337 if ! cmp "$expected_parsed_output" "$actual_parsed_output"; then 338 err 339 err "ERROR: $helper_name has produced output that's different than the expected output." 340 err 341 err " Expected output (after parsing): $expected_parsed_output" 342 err " Actual output (after parsing): $actual_parsed_output" 343 err 344 err "This might mean that a bug was introduced into $helper_name. Or that a bug was fixed." 345 err "Please compare the files." 346 err 347 err "If the actual output is the correct one, just copy the latter file" 348 err "onto the former (and commit to the git repository)." 349 err 350 if is_interactive; then 351 if [ $opt_run_mcdiff_on_error = "yes" ]; then 352 notice "Hit ENTER to launch mcdiff ..." 353 read dummy_var # dash needs this. 354 ${MCDIFF:-mcdiff} "$expected_parsed_output" "$actual_parsed_output" 355 else 356 notice "Tip: invoke this program with '--mcdiff' to automatically launch" 357 notice "mcdiff to visually inspect the diff." 358 notice 359 notice "(Running this program non-interactively (i.e., redirecting the" 360 notice "output to a file or pipe) automatically adds diff to the output.)" 361 notice 362 fi 363 else 364 err "------------ diff of the expected output vs the actual output: -------------" 365 diff -U2 "$expected_parsed_output" "$actual_parsed_output" 366 err "------------------------------- end of diff --------------------------------" 367 fi 368 continue 369 fi 370 371 rm "$actual_output" "$actual_parsed_output" 372 373 error_count=$((error_count - 1)) # cancel the earlier "+1". 374 pass_count=$((pass_count + 1)) 375 376 success "PASSED." 377 378 done 379 380 [ $pass_count = "0" -a $error_count = "0" ] && notice "Note: The data directory contains no *.input files." 381 382 [ $error_count = "0" ] # exit status of function. 383} 384 385parse_command_line_arguments() { 386 # We want --long-options, so we don't use 'getopts'. 387 while [ -n "$1" ]; do 388 case "$1" in 389 --data-dir) 390 data_dir=$2 391 shift 2 392 ;; 393 --data-build-dir) 394 data_build_dir=$2 395 shift 2 396 ;; 397 --helpers-dir) 398 if [ -z "$helpers_dir1" ]; then 399 helpers_dir1=$2 400 else 401 helpers_dir2=$2 402 fi 403 shift 2 404 ;; 405 --create-output) 406 opt_create_output=yes 407 shift 408 ;; 409 --mcdiff) 410 opt_run_mcdiff_on_error=yes 411 shift 412 ;; 413 --help|-h) 414 help 415 exit 416 ;; 417 *) 418 die "Unknown command-line option $1" 419 ;; 420 esac 421 done 422} 423 424# 425# Check that everything is set up correctly. 426# 427verify_setup() { 428 [ -n "$data_dir" ] || die "You didn't specify the data dir (--data-dir). Run me with --help for info." 429 [ -n "$data_build_dir" ] || die "You didn't specify the data build dir (--data-build-dir). Run me with --help for info." 430 [ -n "$helpers_dir1" ] || die "You didn't specify the helpers dir (--helpers-dir). Run me with --help for info." 431 [ -z "$helpers_dir2" ] && helpers_dir2=$helpers_dir1 # we're being lazy. 432 433 local dir 434 for dir in "$data_dir" "$data_build_dir" "$helpers_dir1" "$helpers_dir2"; do 435 assert_dir_exists "$dir" 436 has_string "$dir" " " && die "$dir: Sorry, spaces aren't allowed in pathnames." # search "reason", twice, above. 437 done 438 439 [ -e "$(path_of_config_sh)" ] || die "Missing file $(path_of_config_sh). You probably have a mistake in the '--data-build-dir' path." 440 441 local missing_progs="" 442 check_prog() { 443 if ! has_prog "$1"; then 444 err "I can't see the program '$1'." 445 missing_progs="${missing_progs}${missing_progs:+ and }'$1'" 446 fi 447 } 448 449 check_prog "mc_parse_ls_l" 450 check_prog "mc_xcat" 451 check_prog "mktemp" # non-POSIX 452 [ -z "$missing_progs" ] || die "You need to add to your PATH the directories containing the executables $missing_progs." 453} 454 455main() { 456 init_colors 457 parse_command_line_arguments "$@" 458 verify_setup 459 run # being the last command executed, its exit status is that of this whole script. 460} 461 462main "$@" 463