Home | History | Annotate | Line # | Download | only in atf-sh
      1 #
      2 # Automated Testing Framework (atf)
      3 #
      4 # Copyright (c) 2007 The NetBSD Foundation, Inc.
      5 # All rights reserved.
      6 #
      7 # Redistribution and use in source and binary forms, with or without
      8 # modification, are permitted provided that the following conditions
      9 # are met:
     10 # 1. Redistributions of source code must retain the above copyright
     11 #    notice, this list of conditions and the following disclaimer.
     12 # 2. Redistributions in binary form must reproduce the above copyright
     13 #    notice, this list of conditions and the following disclaimer in the
     14 #    documentation and/or other materials provided with the distribution.
     15 #
     16 # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
     17 # CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
     18 # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     19 # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     20 # IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
     21 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     22 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     23 # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     24 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
     25 # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     26 # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
     27 # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 #
     29 
     30 set -e
     31 
     32 # ------------------------------------------------------------------------
     33 # GLOBAL VARIABLES
     34 # ------------------------------------------------------------------------
     35 
     36 # Values for the expect property.
     37 Expect=pass
     38 Expect_Reason=
     39 
     40 # A boolean variable that indicates whether we are parsing a test case's
     41 # head or not.
     42 Parsing_Head=false
     43 
     44 # The program name.
     45 Prog_Name=${0##*/}
     46 
     47 # The file to which the test case will print its result.
     48 Results_File=
     49 
     50 # The test program's source directory: i.e. where its auxiliary data files
     51 # and helper utilities can be found.  Can be overriden through the '-s' flag.
     52 Source_Dir="$(dirname ${0})"
     53 
     54 # Indicates the test case we are currently processing.
     55 Test_Case=
     56 
     57 # List of meta-data variables for the current test case.
     58 Test_Case_Vars=
     59 
     60 # The list of all test cases provided by the test program.
     61 Test_Cases=
     62 
     63 # ------------------------------------------------------------------------
     64 # PUBLIC INTERFACE
     65 # ------------------------------------------------------------------------
     66 
     67 #
     68 # atf_add_test_case tc-name
     69 #
     70 #   Adds the given test case to the list of test cases that form the test
     71 #   program.  The name provided here must be accompanied by two functions
     72 #   named after it: <tc-name>_head and <tc-name>_body, and optionally by
     73 #   a <tc-name>_cleanup function.
     74 #
     75 atf_add_test_case()
     76 {
     77     Test_Cases="${Test_Cases} ${1}"
     78 }
     79 
     80 #
     81 # atf_check cmd expcode expout experr
     82 #
     83 #   Executes atf-check with given arguments and automatically calls
     84 #   atf_fail in case of failure.
     85 #
     86 atf_check()
     87 {
     88     ${Atf_Check} "${@}" || \
     89         atf_fail "atf-check failed; see the output of the test for details"
     90 }
     91 
     92 #
     93 # atf_check_equal expr1 expr2
     94 #
     95 #   Checks that expr1's value matches expr2's and, if not, raises an
     96 #   error.  Ideally expr1 and expr2 should be provided quoted (not
     97 #   expanded) so that the error message is helpful; otherwise it will
     98 #   only show the values, not the expressions themselves.
     99 #
    100 atf_check_equal()
    101 {
    102     eval _val1=\"${1}\"
    103     eval _val2=\"${2}\"
    104     test "${_val1}" = "${_val2}" || \
    105         atf_fail "${1} != ${2} (${_val1} != ${_val2})"
    106 }
    107 
    108 #
    109 # atf_config_get varname [defvalue]
    110 #
    111 #   Prints the value of a configuration variable.  If it is not
    112 #   defined, prints the given default value.
    113 #
    114 atf_config_get()
    115 {
    116     _varname="__tc_config_var_$(_atf_normalize ${1})"
    117     if [ ${#} -eq 1 ]; then
    118         eval _value=\"\${${_varname}-__unset__}\"
    119         [ "${_value}" = __unset__ ] && \
    120             _atf_error 1 "Could not find configuration variable \`${1}'"
    121         echo ${_value}
    122     elif [ ${#} -eq 2 ]; then
    123         eval echo \${${_varname}-${2}}
    124     else
    125         _atf_error 1 "Incorrect number of parameters for atf_config_get"
    126     fi
    127 }
    128 
    129 #
    130 # atf_config_has varname
    131 #
    132 #   Returns a boolean indicating if the given configuration variable is
    133 #   defined or not.
    134 #
    135 atf_config_has()
    136 {
    137     _varname="__tc_config_var_$(_atf_normalize ${1})"
    138     eval _value=\"\${${_varname}-__unset__}\"
    139     [ "${_value}" != __unset__ ]
    140 }
    141 
    142 #
    143 # atf_expect_death reason
    144 #
    145 #   Sets the expectations to 'death'.
    146 #
    147 atf_expect_death()
    148 {
    149     _atf_validate_expect
    150 
    151     Expect=death
    152     _atf_create_resfile "expected_death: ${*}"
    153 }
    154 
    155 #
    156 # atf_expect_timeout reason
    157 #
    158 #   Sets the expectations to 'timeout'.
    159 #
    160 atf_expect_timeout()
    161 {
    162     _atf_validate_expect
    163 
    164     Expect=timeout
    165     _atf_create_resfile "expected_timeout: ${*}"
    166 }
    167 
    168 #
    169 # atf_expect_exit exitcode reason
    170 #
    171 #   Sets the expectations to 'exit'.
    172 #
    173 atf_expect_exit()
    174 {
    175     _exitcode="${1}"; shift
    176 
    177     _atf_validate_expect
    178 
    179     Expect=exit
    180     if [ "${_exitcode}" = "-1" ]; then
    181         _atf_create_resfile "expected_exit: ${*}"
    182     else
    183         _atf_create_resfile "expected_exit(${_exitcode}): ${*}"
    184     fi
    185 }
    186 
    187 #
    188 # atf_expect_fail reason
    189 #
    190 #   Sets the expectations to 'fail'.
    191 #
    192 atf_expect_fail()
    193 {
    194     _atf_validate_expect
    195 
    196     Expect=fail
    197     Expect_Reason="${*}"
    198 }
    199 
    200 #
    201 # atf_expect_pass
    202 #
    203 #   Sets the expectations to 'pass'.
    204 #
    205 atf_expect_pass()
    206 {
    207     _atf_validate_expect
    208 
    209     Expect=pass
    210     Expect_Reason=
    211 }
    212 
    213 #
    214 # atf_expect_signal signo reason
    215 #
    216 #   Sets the expectations to 'signal'.
    217 #
    218 atf_expect_signal()
    219 {
    220     _signo="${1}"; shift
    221 
    222     _atf_validate_expect
    223 
    224     Expect=signal
    225     if [ "${_signo}" = "-1" ]; then
    226         _atf_create_resfile "expected_signal: ${*}"
    227     else
    228         _atf_create_resfile "expected_signal(${_signo}): ${*}"
    229     fi
    230 }
    231 
    232 #
    233 # atf_expected_failure msg1 [.. msgN]
    234 #
    235 #   Makes the test case report an expected failure with the given error
    236 #   message.  Multiple words can be provided, which are concatenated with
    237 #   a single blank space.
    238 #
    239 atf_expected_failure()
    240 {
    241     _atf_create_resfile "expected_failure: ${Expect_Reason}: ${*}"
    242     exit 0
    243 }
    244 
    245 #
    246 # atf_fail msg1 [.. msgN]
    247 #
    248 #   Makes the test case fail with the given error message.  Multiple
    249 #   words can be provided, in which case they are joined by a single
    250 #   blank space.
    251 #
    252 atf_fail()
    253 {
    254     case "${Expect}" in
    255         fail)
    256             atf_expected_failure "${@}"
    257             ;;
    258         pass)
    259             _atf_create_resfile "failed: ${*}"
    260             exit 1
    261             ;;
    262         *)
    263             _atf_error 128 "Unreachable"
    264             ;;
    265     esac
    266 }
    267 
    268 #
    269 # atf_get varname
    270 #
    271 #   Prints the value of a test case-specific variable.  Given that one
    272 #   should not get the value of non-existent variables, it is fine to
    273 #   always use this function as 'val=$(atf_get var)'.
    274 #
    275 atf_get()
    276 {
    277     eval echo \${__tc_var_${Test_Case}_$(_atf_normalize ${1})}
    278 }
    279 
    280 #
    281 # atf_get_srcdir
    282 #
    283 #   Prints the value of the test case's source directory.
    284 #
    285 atf_get_srcdir()
    286 {
    287     echo ${Source_Dir}
    288 }
    289 
    290 #
    291 # atf_pass
    292 #
    293 #   Makes the test case pass.  Shouldn't be used in general, as a test
    294 #   case that does not explicitly fail is assumed to pass.
    295 #
    296 atf_pass()
    297 {
    298     case "${Expect}" in
    299         fail)
    300             Expect=pass
    301             atf_fail "Test case was expecting a failure but got a pass instead"
    302             ;;
    303         pass)
    304             _atf_create_resfile passed
    305             exit 0
    306             ;;
    307         *)
    308             _atf_error 128 "Unreachable"
    309             ;;
    310     esac
    311 }
    312 
    313 #
    314 # atf_require_prog prog
    315 #
    316 #   Checks that the given program name (either provided as an absolute
    317 #   path or as a plain file name) can be found.  If it is not available,
    318 #   automatically skips the test case with an appropriate message.
    319 #
    320 #   Relative paths are not allowed because the test case cannot predict
    321 #   where it will be executed from.
    322 #
    323 atf_require_prog()
    324 {
    325     _prog=
    326     case ${1} in
    327     /*)
    328         _prog="${1}"
    329         [ -x ${_prog} ] || \
    330             atf_skip "The required program ${1} could not be found"
    331         ;;
    332     */*)
    333         atf_fail "atf_require_prog does not accept relative path names \`${1}'"
    334         ;;
    335     *)
    336         _prog=$(_atf_find_in_path "${1}")
    337         [ -n "${_prog}" ] || \
    338             atf_skip "The required program ${1} could not be found" \
    339                      "in the PATH"
    340         ;;
    341     esac
    342 }
    343 
    344 #
    345 # atf_set varname val1 [.. valN]
    346 #
    347 #   Sets the test case's variable 'varname' to the specified values
    348 #   which are concatenated using a single blank space.  This function
    349 #   is supposed to be called form the test case's head only.
    350 #
    351 atf_set()
    352 {
    353     ${Parsing_Head} || \
    354         _atf_error 128 "atf_set called from the test case's body"
    355 
    356     Test_Case_Vars="${Test_Case_Vars} ${1}"
    357     _var=$(_atf_normalize ${1}); shift
    358     eval __tc_var_${Test_Case}_${_var}=\"\${*}\"
    359 }
    360 
    361 #
    362 # atf_skip msg1 [.. msgN]
    363 #
    364 #   Skips the test case because of the reason provided.  Multiple words
    365 #   can be given, in which case they are joined by a single blank space.
    366 #
    367 atf_skip()
    368 {
    369     _atf_create_resfile "skipped: ${*}"
    370     exit 0
    371 }
    372 
    373 #
    374 # atf_test_case tc-name cleanup
    375 #
    376 #   Defines a new test case named tc-name.  The name provided here must be
    377 #   accompanied by two functions named after it: <tc-name>_head and
    378 #   <tc-name>_body.  If cleanup is set to 'cleanup', then this also expects
    379 #   a <tc-name>_cleanup function to be defined.
    380 #
    381 atf_test_case()
    382 {
    383     eval "${1}_head() { :; }"
    384     eval "${1}_body() { atf_fail 'Test case not implemented'; }"
    385     if [ "${2}" = cleanup ]; then
    386         eval __has_cleanup_${1}=true
    387         eval "${1}_cleanup() { :; }"
    388     else
    389         eval "${1}_cleanup() {
    390             _atf_error 1 'Test case ${1} declared without a cleanup routine'; }"
    391     fi
    392 }
    393 
    394 # ------------------------------------------------------------------------
    395 # PRIVATE INTERFACE
    396 # ------------------------------------------------------------------------
    397 
    398 #
    399 # _atf_config_set varname val1 [.. valN]
    400 #
    401 #   Sets the test case's private variable 'varname' to the specified
    402 #   values which are concatenated using a single blank space.
    403 #
    404 _atf_config_set()
    405 {
    406     _var=$(_atf_normalize ${1}); shift
    407     eval __tc_config_var_${_var}=\"\${*}\"
    408     Config_Vars="${Config_Vars} __tc_config_var_${_var}"
    409 }
    410 
    411 #
    412 # _atf_config_set_str varname=val
    413 #
    414 #   Sets the test case's private variable 'varname' to the specified
    415 #   value.  The parameter is of the form 'varname=val'.
    416 #
    417 _atf_config_set_from_str()
    418 {
    419     _oldifs=${IFS}
    420     IFS='='
    421     set -- ${*}
    422     _var=${1}
    423     shift
    424     _val="${@}"
    425     IFS=${_oldifs}
    426     _atf_config_set "${_var}" "${_val}"
    427 }
    428 
    429 #
    430 # _atf_create_resfile contents
    431 #
    432 #   Creates the results file.
    433 #
    434 _atf_create_resfile()
    435 {
    436     if [ -n "${Results_File}" ]; then
    437         echo "${*}" >"${Results_File}" || \
    438             _atf_error 128 "Cannot create results file '${Results_File}'"
    439     else
    440         echo "${*}"
    441     fi
    442 }
    443 
    444 #
    445 # _atf_error error_code [msg1 [.. msgN]]
    446 #
    447 #   Prints the given error message (which can be composed of multiple
    448 #   arguments, in which case are joined by a single space) and exits
    449 #   with the specified error code.
    450 #
    451 #   This must not be used by test programs themselves (hence making
    452 #   the function private) to indicate a test case's failure.  They
    453 #   have to use the atf_fail function.
    454 #
    455 _atf_error()
    456 {
    457     _error_code="${1}"; shift
    458 
    459     echo "${Prog_Name}: ERROR:" "$@" 1>&2
    460     exit ${_error_code}
    461 }
    462 
    463 #
    464 # _atf_warning msg1 [.. msgN]
    465 #
    466 #   Prints the given warning message (which can be composed of multiple
    467 #   arguments, in which case are joined by a single space).
    468 #
    469 _atf_warning()
    470 {
    471     echo "${Prog_Name}: WARNING:" "$@" 1>&2
    472 }
    473 
    474 #
    475 # _atf_find_in_path program
    476 #
    477 #   Looks for a program in the path and prints the full path to it or
    478 #   nothing if it could not be found.  It also returns true in case of
    479 #   success.
    480 #
    481 _atf_find_in_path()
    482 {
    483     _prog="${1}"
    484 
    485     _oldifs=${IFS}
    486     IFS=:
    487     for _dir in ${PATH}
    488     do
    489         if [ -x ${_dir}/${_prog} ]; then
    490             IFS=${_oldifs}
    491             echo ${_dir}/${_prog}
    492             return 0
    493         fi
    494     done
    495     IFS=${_oldifs}
    496 
    497     return 1
    498 }
    499 
    500 #
    501 # _atf_has_tc name
    502 #
    503 #   Returns true if the given test case exists.
    504 #
    505 _atf_has_tc()
    506 {
    507     for _tc in ${Test_Cases}; do
    508         [ "${_tc}" != "${1}" ] || return 0
    509     done
    510     return 1
    511 }
    512 
    513 #
    514 # _atf_list_tcs
    515 #
    516 #   Describes all test cases and prints the list to the standard output.
    517 #
    518 _atf_list_tcs()
    519 {
    520     echo 'Content-Type: application/X-atf-tp; version="1"'
    521     echo
    522 
    523     set -- ${Test_Cases}
    524     while [ ${#} -gt 0 ]; do
    525         _atf_parse_head ${1}
    526 
    527         echo "ident: $(atf_get ident)"
    528         for _var in ${Test_Case_Vars}; do
    529 	    # Elide ksh bug!
    530 	    set +e
    531             [ "${_var}" != "ident" ] && echo "${_var}: $(atf_get ${_var})"
    532 	    set -e
    533         done
    534 
    535         [ ${#} -gt 1 ] && echo
    536         shift
    537     done
    538 }
    539 
    540 #
    541 # _atf_normalize str
    542 #
    543 #   Normalizes a string so that it is a valid shell variable name.
    544 #
    545 _atf_normalize()
    546 {
    547     while :
    548     do
    549 	case "${1}" in
    550 	(*.*)	set -- "${1%.*}_${1##*.}";;
    551 	(*-*)	set -- "${1%-*}_${1##*-}";;
    552 	(*)	break;;
    553 	esac
    554     done
    555     printf "%s\n" "$1"
    556 }
    557 
    558 #
    559 # _atf_parse_head tcname
    560 #
    561 #   Evaluates a test case's head to gather its variables and prepares the
    562 #   test program to run it.
    563 #
    564 _atf_parse_head()
    565 {
    566     Parsing_Head=true
    567 
    568     Test_Case="${1}"
    569     Test_Case_Vars=
    570 
    571     if _atf_has_cleanup "${1}"; then
    572         atf_set has.cleanup "true"
    573     fi
    574 
    575     ${1}_head
    576     atf_set ident "${1}"
    577 
    578     Parsing_Head=false
    579 }
    580 
    581 #
    582 # _atf_run_tc tc
    583 #
    584 #   Runs the specified test case.  Prints its exit status to the
    585 #   standard output and returns a boolean indicating if the test was
    586 #   successful or not.
    587 #
    588 _atf_run_tc()
    589 {
    590     case ${1} in
    591     *:*)
    592         _tcname=${1%%:*}
    593         _tcpart=${1#*:}
    594 
    595         if [ "${_tcpart}" != body -a "${_tcpart}" != cleanup ]; then
    596             _atf_syntax_error "Unknown test case part \`${_tcpart}'"
    597         fi
    598         ;;
    599 
    600     *)
    601         _tcname=${1}
    602         _tcpart=body
    603         ;;
    604     esac
    605 
    606     _atf_has_tc "${_tcname}" || _atf_syntax_error "Unknown test case \`${1}'"
    607 
    608     if [ "${__RUNNING_INSIDE_ATF_RUN}" != "internal-yes-value" ]; then
    609         _atf_warning "Running test cases without atf-run(1) is unsupported"
    610         _atf_warning "No isolation nor timeout control is being applied;" \
    611             "you may get unexpected failures; see atf-test-case(4)"
    612     fi
    613 
    614     _atf_parse_head ${_tcname}
    615 
    616     case ${_tcpart} in
    617     body)
    618         if ${_tcname}_body; then
    619             _atf_validate_expect
    620             _atf_create_resfile passed
    621         else
    622             Expect=pass
    623             atf_fail "Test case body returned a non-ok exit code, but" \
    624                 "this is not allowed"
    625         fi
    626         ;;
    627     cleanup)
    628         if _atf_has_cleanup "${_tcname}"; then
    629             ${_tcname}_cleanup || _atf_error 128 "The test case cleanup" \
    630                 "returned a non-ok exit code, but this is not allowed"
    631         fi
    632         ;;
    633     *)
    634         _atf_error 128 "Unknown test case part"
    635         ;;
    636     esac
    637 }
    638 
    639 #
    640 # _atf_syntax_error msg1 [.. msgN]
    641 #
    642 #   Formats and prints a syntax error message and terminates the
    643 #   program prematurely.
    644 #
    645 _atf_syntax_error()
    646 {
    647     echo "${Prog_Name}: ERROR: ${@}" 1>&2
    648     echo "${Prog_Name}: See atf-test-program(1) for usage details." 1>&2
    649     exit 1
    650 }
    651 
    652 #
    653 # _atf_has_cleanup tc-name
    654 #
    655 #   Returns a boolean indicating if the given test case has a cleanup
    656 #   routine or not.
    657 #
    658 _atf_has_cleanup()
    659 {
    660     _found=true
    661     eval "[ x\"\${__has_cleanup_${1}}\" = xtrue ] || _found=false"
    662     [ "${_found}" = true ]
    663 }
    664 
    665 #
    666 # _atf_validate_expect
    667 #
    668 #   Ensures that the current test case state is correct regarding the expect
    669 #   status.
    670 #
    671 _atf_validate_expect()
    672 {
    673     case "${Expect}" in
    674         death)
    675             Expect=pass
    676             atf_fail "Test case was expected to terminate abruptly but it" \
    677                 "continued execution"
    678             ;;
    679         exit)
    680             Expect=pass
    681             atf_fail "Test case was expected to exit cleanly but it continued" \
    682                 "execution"
    683             ;;
    684         fail)
    685             Expect=pass
    686             atf_fail "Test case was expecting a failure but none were raised"
    687             ;;
    688         pass)
    689             ;;
    690         signal)
    691             Expect=pass
    692             atf_fail "Test case was expected to receive a termination signal" \
    693                 "but it continued execution"
    694             ;;
    695         timeout)
    696             Expect=pass
    697             atf_fail "Test case was expected to hang but it continued execution"
    698             ;;
    699         *)
    700             _atf_error 128 "Unreachable"
    701             ;;
    702     esac
    703 }
    704 
    705 #
    706 # _atf_warning [msg1 [.. msgN]]
    707 #
    708 #   Prints the given warning message (which can be composed of multiple
    709 #   arguments, in which case are joined by a single space).
    710 #
    711 #   This must not be used by test programs themselves (hence making
    712 #   the function private).
    713 #
    714 _atf_warning()
    715 {
    716     echo "${Prog_Name}: WARNING:" "$@" 1>&2
    717 }
    718 
    719 #
    720 # main [options] test_case
    721 #
    722 #   Test program's entry point.
    723 #
    724 main()
    725 {
    726     # Process command-line options first.
    727     _numargs=${#}
    728     _lflag=false
    729     while getopts :lr:s:v: arg; do
    730         case ${arg} in
    731         l)
    732             _lflag=true
    733             ;;
    734 
    735         r)
    736             Results_File=${OPTARG}
    737             ;;
    738 
    739         s)
    740             Source_Dir=${OPTARG}
    741             ;;
    742 
    743         v)
    744             _atf_config_set_from_str "${OPTARG}"
    745             ;;
    746 
    747         \?)
    748             _atf_syntax_error "Unknown option -${OPTARG}."
    749             # NOTREACHED
    750             ;;
    751         esac
    752     done
    753     shift `expr ${OPTIND} - 1`
    754 
    755     # First of all, make sure that the source directory is correct.  It
    756     # doesn't matter if the user did not change it, because the default
    757     # value may not work.  (TODO: It possibly should, even though it is
    758     # not a big deal because atf-run deals with this.)
    759     case ${Source_Dir} in
    760         /*)
    761             ;;
    762         *)
    763             Source_Dir=$(pwd)/${Source_Dir}
    764             ;;
    765     esac
    766     [ -f ${Source_Dir}/${Prog_Name} ] || \
    767         _atf_error 1 "Cannot find the test program in the source" \
    768                      "directory \`${Source_Dir}'"
    769 
    770     # Call the test program's hook to register all available test cases.
    771     atf_init_test_cases
    772 
    773     # Run or list test cases.
    774     if `${_lflag}`; then
    775         if [ ${#} -gt 0 ]; then
    776             _atf_syntax_error "Cannot provide test case names with -l"
    777         fi
    778         _atf_list_tcs
    779     else
    780         if [ ${#} -eq 0 ]; then
    781             _atf_syntax_error "Must provide a test case name"
    782         elif [ ${#} -gt 1 ]; then
    783             _atf_syntax_error "Cannot provide more than one test case name"
    784         else
    785             _atf_run_tc "${1}"
    786         fi
    787     fi
    788 }
    789 
    790 # vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
    791