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