test.sh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. #!/bin/bash -e
  2. # Script for running various package tests via the NPM 'test' sub-command.
  3. # Configuration occurs either through the environment variables set thru the
  4. # config section of the package.json file or via identical command line options.
  5. ###############################################################################
  6. CMD_PWD=$(pwd)
  7. CMD="$0"
  8. CMD_DIR=$(cd "$(dirname "$CMD")"; pwd)
  9. # Defaults and command line options
  10. VERBOSE=
  11. DEBUG=
  12. NO_SYNTAX=
  13. NO_UNIT=
  14. NO_COVERAGE=
  15. # Shortcut for running echo and then exit
  16. die() {
  17. echo "$1" 1>&2
  18. [ -n "$2" ] && exit $2 || exit 1
  19. }
  20. # Show help function to be used below
  21. show_help() {
  22. awk 'NR>1,/^(###|$)/{print $0; exit}' "$CMD"
  23. echo "USAGE: $(basename "$CMD") [arguments]"
  24. echo "ARGS:"
  25. MSG=$(awk '/^NARGS=-1; while/,/^esac; done/' "$CMD" | sed -e 's/^[[:space:]]*/ /' -e 's/|/, /' -e 's/)//' | grep '^ -')
  26. EMSG=$(eval "echo \"$MSG\"")
  27. echo "$EMSG"
  28. }
  29. # Parse command line options (odd formatting to simplify show_help() above)
  30. NARGS=-1; while [ "$#" -ne "$NARGS" ]; do NARGS=$#; case $1 in
  31. # SWITCHES
  32. -h|--help) # This help message
  33. show_help; exit 1; ;;
  34. -d|--debug) # Enable debugging messages (implies verbose)
  35. DEBUG=$(( $DEBUG + 1 )) && VERBOSE="$DEBUG" && shift && echo "#-INFO: DEBUG=$DEBUG (implies VERBOSE=$VERBOSE)"; ;;
  36. -v|--verbose) # Enable verbose messages
  37. VERBOSE=$(( $VERBOSE + 1 )) && shift && echo "#-INFO: VERBOSE=$VERBOSE"; ;;
  38. -S|--no-syntax) # Disable syntax tests
  39. NO_SYNTAX=$(( $NO_SYNTAX + 1 )) && shift && echo "#-INFO: NO_SYNTAX=$NO_SYNTAX"; ;;
  40. -U|--no-unit) # Disable unit tests
  41. NO_UNIT=$(( $NO_UNIT + 1 )) && shift && echo "#-INFO: NO_UNIT=$NO_UNIT"; ;;
  42. -C|--no-coverage) # Enable coverage tests
  43. NO_COVERAGE=$(( $NO_COVERAGE + 1 )) && shift && echo "#-INFO: NO_COVERAGE=$NO_COVERAGE"; ;;
  44. # PAIRS
  45. # -t|--thing) # Set a thing to a value (DEFAULT: $THING)
  46. # shift && THING="$1" && shift && [ -n "$VERBOSE" ] && echo "#-INFO: THING=$THING"; ;;
  47. esac; done
  48. ###############################################################################
  49. [ $# -eq 0 ] || die "ERROR: Unexpected commands!"
  50. # Enable debug messages in silly mode
  51. [ "$npm_config_loglevel" = "silly" ] && DEBUG=1
  52. [ -n "$DEBUG" ] && set -x
  53. # Show all of the package config variables for debugging if non-standard loglevel
  54. [ -n "$npm_config_loglevel" ] && [ "$npm_config_loglevel" != "http" ] && VERBOSE=1
  55. [ -n "$VERBOSE" ] && env | egrep -i '^(npm|jenkins)_' | sort | sed 's/^/#-INFO: /g'
  56. # Change to root directory of package
  57. cd "$CMD_DIR/../../" # assuming that this is $PKG_ROOT/npm_scripts/MyAwesomeScript/MyAwesomeScript.sh or similar
  58. [ -f "package.json" ] || die "ERROR: Unable to find the \"package.json\" file in \"$(pwd)\"!"
  59. # Basic sanity check for node_modules directory (to ensure that 'npm install' has been run)
  60. [ -d "node_modules" ] || die "ERROR: Unable to find the \"node_modules\" dir in \"$(pwd)\"!. Run \"npm install\" first!"
  61. # Determing package name
  62. PKG_NAME="$npm_package_name"
  63. [ -n "$PKG_NAME" ] || PKG_NAME="$npm_config_package_name"
  64. [ -n "$PKG_NAME" ] || PKG_NAME=$(node -e 'console.log(require("./package.json").name)')
  65. [ -n "$PKG_NAME" ] || die "ERROR: Unable to determine package name! Broken package?"
  66. # Determine code directory
  67. CODE_DIR="$npm_package_config_code_dir"
  68. [ -n "$CODE_DIR" ] && [ -d "$CODE_DIR" ] || CODE_DIR="$npm_config_default_code_dir"
  69. [ -n "$CODE_DIR" ] && [ -d "$CODE_DIR" ] || CODE_DIR="lib"
  70. [ -n "$CODE_DIR" ] && [ -d "$CODE_DIR" ] || die "ERROR: Unable to find code directory at \"$CODE_DIR\"!"
  71. CODE_DIR=$(echo "$CODE_DIR" | sed 's/\/$//') # remove trailing slash
  72. [ -n "$VERBOSE" ] && echo "CODE_DIR=$CODE_DIR"
  73. # Determine test directory
  74. TEST_DIR="$npm_package_config_test_dir"
  75. [ -n "$TEST_DIR" ] && [ -d "$TEST_DIR" ] || TEST_DIR="$npm_config_default_test_dir"
  76. [ -n "$TEST_DIR" ] && [ -d "$TEST_DIR" ] || TEST_DIR="test/lib"
  77. [ -d "$TEST_DIR" ] || die "ERROR: Unable to find test directory at \"$TEST_DIR\"!"
  78. TEST_DIR=$(echo "$TEST_DIR" | sed 's/\/$//') # remove trailing slash
  79. [ -n "$VERBOSE" ] && echo "TEST_DIR=$TEST_DIR"
  80. # Helper to check if given file is valid XML
  81. XMLLINT_BIN=$(which xmllint || echo)
  82. validate_xml() {
  83. REPORT_FILE="$1"
  84. if [ -n "$XMLLINT_BIN" ]; then
  85. if [ -s "$REPORT_FILE" ]; then
  86. "$XMLLINT_BIN" --noout "$REPORT_FILE" || die "ERROR: Invalid XML in \"$REPORT_FILE\"!"
  87. else
  88. echo "WARNING: expected XML data in empty file at \"$REPORT_FILE\"."
  89. fi
  90. else
  91. echo "WARNING: xmllint not in PATH so skipping XML check of \"$REPORT_FILE\"."
  92. fi
  93. }
  94. # Syntax tests
  95. [ "$npm_package_config_test_syntax" = "false" ] && NO_SYNTAX=1
  96. if [ -z "$NO_SYNTAX" ]; then
  97. echo "Running syntax checks ..."
  98. # Deps
  99. JSHINT_BIN="$npm_package_config_jshint_bin"
  100. #[ -n "$JSHINT_BIN" ] && [ -x "$JSHINT_BIN" ] || JSHINT_BIN=$(which jshint || echo)
  101. [ -n "$JSHINT_BIN" ] && [ -x "$JSHINT_BIN" ] || JSHINT_BIN="./node_modules/.bin/jshint"
  102. [ -n "$JSHINT_BIN" ] && [ -x "$JSHINT_BIN" ] || die "ERROR: Unable to find 'jshint' binary! Install via 'npm install jshint' to proceed!"
  103. # Prep
  104. JSHINT_OUTPUT_DIR="$npm_package_config_jshint_output_dir"
  105. [ -n "$JSHINT_OUTPUT_DIR" ] || JSHINT_OUTPUT_DIR="$npm_config_default_jshint_output_dir"
  106. [ -n "$JSHINT_OUTPUT_DIR" ] || [ -n "$npm_config_default_reports_output_dir" ] && JSHINT_OUTPUT_DIR="$npm_config_default_reports_output_dir/syntax"
  107. [ -n "$JSHINT_OUTPUT_DIR" ] || JSHINT_OUTPUT_DIR="reports/syntax"
  108. [ -d "$JSHINT_OUTPUT_DIR" ] || mkdir -p "$JSHINT_OUTPUT_DIR" || die "ERROR: Unable to mkdir \"$JSHINT_OUTPUT_DIR\", the jshint output dir!"
  109. # Exec require on all js files
  110. echo " Testing via NodeJS require function ..."
  111. node -e "[$(find "./$CODE_DIR" "./$TEST_DIR" -type f -name '*.js' -not -iregex '.*/public/.*' -not -iregex '.*/node_modules/.*' | sed -e 's/^/ "/' -e 's/$/",/')].forEach(require);" \
  112. || die "ERROR: NodeJS require error!"
  113. # Exec jshint to get jslint output #TODO: is this even needed?
  114. echo " Checking via JSHint jslint reporter ..."
  115. REPORT_FILE="$JSHINT_OUTPUT_DIR/$PKG_NAME-jshint-jslint.xml"
  116. "$JSHINT_BIN" --extra-ext ".js,.json" --jslint-reporter "$CODE_DIR" "$TEST_DIR" &> "$REPORT_FILE" \
  117. || die "ERROR: JSHint errors on jslint reporter! $(echo; cat "$REPORT_FILE")"
  118. [ -n "$VERBOSE" ] && echo "REPORT OUTPUT: $REPORT_FILE" && cat "$REPORT_FILE" && echo
  119. validate_xml "$REPORT_FILE" || die "ERROR: INVALID REPORT FILE!"
  120. # Exec jshint to get checkstyle output
  121. echo " Checking via JSHint checkstyle reporter ..."
  122. REPORT_FILE="$JSHINT_OUTPUT_DIR/$PKG_NAME-jshint-checkstyle.xml"
  123. "$JSHINT_BIN" --extra-ext ".js,.json" --checkstyle-reporter "$CODE_DIR" "$TEST_DIR" > "$REPORT_FILE" \
  124. || die "ERROR: JSHint errors on checkstyle reporter! $(echo; cat "$REPORT_FILE")"
  125. echo " ERRORS: $(egrep -c '<error .* severity="error"' "$REPORT_FILE")"
  126. echo " WARNINGS: $(egrep -c '<error .* severity="warning"' "$REPORT_FILE")"
  127. [ -n "$VERBOSE" ] && echo "REPORT OUTPUT: $REPORT_FILE" && cat "$REPORT_FILE" && echo
  128. validate_xml "$REPORT_FILE" || die "ERROR: INVALID REPORT FILE!"
  129. echo " Checking custom code rules ..."
  130. BAD_INSTANCEOF=$(egrep --include '*.js' --recursive ' instanceof (Boolean|Number|String)' "$CODE_DIR" || true)
  131. [ -z "$BAD_INSTANCEOF" ] || die "ERROR: Found uses of instanceof that are likely to be broken! $(echo; echo "$BAD_INSTANCEOF")"
  132. echo
  133. fi
  134. # Unit tests
  135. [ "$npm_package_config_test_unit" = "false" ] && NO_UNIT=1
  136. if [ -z "$NO_UNIT" ]; then
  137. echo "Running unit tests ..."
  138. # Deps
  139. MOCHA_BIN="$npm_package_config_mocha_bin"
  140. [ -n "$MOCHA_BIN" ] && [ -x "$MOCHA_BIN" ] || MOCHA_BIN=$(which mocha || echo)
  141. [ -n "$MOCHA_BIN" ] && [ -x "$MOCHA_BIN" ] || die "ERROR: Unable to find 'mocha' binary! Install via 'npm install mocha' to proceed!"
  142. # Prep
  143. MOCHA_REPORTER="spec"
  144. if [ -n "$JENKINS_URL" ]; then
  145. MOCHA_REPORTER="$npm_package_config_test_reporter"
  146. [ -n "$MOCHA_REPORTER" ] || MOCHA_REPORTER="xunit"
  147. fi
  148. MOCHA_OUTPUT_DIR="$npm_package_config_mocha_output_dir"
  149. [ -n "$MOCHA_OUTPUT_DIR" ] || MOCHA_OUTPUT_DIR="$npm_config_default_mocha_output_dir"
  150. [ -n "$MOCHA_OUTPUT_DIR" ] || [ -n "$npm_config_default_reports_output_dir" ] && MOCHA_OUTPUT_DIR="$npm_config_default_reports_output_dir/unit"
  151. [ -n "$MOCHA_OUTPUT_DIR" ] || MOCHA_OUTPUT_DIR="reports/unit"
  152. [ -d "$MOCHA_OUTPUT_DIR" ] || mkdir -p "$MOCHA_OUTPUT_DIR" || die "ERROR: Unable to mkdir \"$MOCHA_OUTPUT_DIR\", the mocha output dir!"
  153. # Exec
  154. [ "$MOCHA_REPORTER" == "xunit" ] && UNIT_TEST_EXTENSION=xml || UNIT_TEST_EXTENSION=txt
  155. [ "$MOCHA_REPORTER" == "xunit" ] && MOCHA_EXTRA_FLAGS= || MOCHA_EXTRA_FLAGS=--colors
  156. REPORT_FILE_BASE="$MOCHA_OUTPUT_DIR/$PKG_NAME-report"
  157. REPORT_FILE="$REPORT_FILE_BASE.$UNIT_TEST_EXTENSION"
  158. REPORT_FILE_ERR="$REPORT_FILE_BASE.err"
  159. LOGGER_PREFIX='' LOGGER_LEVEL=NOTICE "$MOCHA_BIN" --ui exports --reporter "$MOCHA_REPORTER" $MOCHA_EXTRA_FLAGS --recursive "$TEST_DIR" 2> "$REPORT_FILE_ERR" 1> "$REPORT_FILE" \
  160. || die "ERROR: Mocha errors during unit tests! $(echo; cat "$REPORT_FILE"; cat "$REPORT_FILE_ERR")"
  161. [ -n "$VERBOSE" ] && echo "REPORT OUTPUT: $REPORT_FILE" && cat "$REPORT_FILE" && echo
  162. [ -s "$REPORT_FILE" ] || die "ERROR: no report data, units tests probably failed!"
  163. echo
  164. fi
  165. # Coverage tests
  166. [ "$npm_package_config_test_coverage" = "false" ] && NO_COVERAGE=1
  167. if [ -z "$NO_COVERAGE" ]; then
  168. echo "Running coverage tests ..."
  169. # Deps
  170. JSCOVERAGE_BIN="$npm_package_config_jscoverage_bin"
  171. [ -n "$JSCOVERAGE_BIN" ] && [ -x "$JSCOVERAGE_BIN" ] || JSCOVERAGE_BIN=$(which jscoverage || echo "./node_modules/visionmedia-jscoverage/jscoverage") # TODO: jscoverage does not install itself. Renable this when it does.
  172. [ -n "$JSCOVERAGE_BIN" ] && [ -x "$JSCOVERAGE_BIN" ] || die "$(cat<<-ERROR_DOCS_EOF
  173. ERROR: Unable to find node.js jscoverage binary!
  174. To install the nodejs jscoverage binary run the following commands:
  175. # git clone https://github.com/visionmedia/node-jscoverage.git
  176. # cd node-coverage
  177. # ./configure && make && make install
  178. ERROR_DOCS_EOF
  179. )"
  180. # Prep
  181. JSCOVERAGE_OUTPUT_DIR="$npm_package_config_jscoverage_output_dir"
  182. [ -n "$JSCOVERAGE_OUTPUT_DIR" ] || JSCOVERAGE_OUTPUT_DIR="$npm_config_default_jscoverage_output_dir"
  183. [ -n "$JSCOVERAGE_OUTPUT_DIR" ] || [ -n "$npm_config_default_reports_output_dir" ] && JSCOVERAGE_OUTPUT_DIR="$npm_config_default_reports_output_dir/html/jscoverage"
  184. [ -n "$JSCOVERAGE_OUTPUT_DIR" ] || JSCOVERAGE_OUTPUT_DIR="reports/html/jscoverage"
  185. [ -d "$JSCOVERAGE_OUTPUT_DIR" ] || mkdir -p "$JSCOVERAGE_OUTPUT_DIR" || die "ERROR: Unable to mkdir \"$MOCHA_OUTPUT_DIR\", the mocha output dir!"
  186. JSCOVERAGE_TMP_DIR="$CODE_DIR.jscoverage"
  187. if [ -d "$JSCOVERAGE_TMP_DIR" ]; then
  188. rm -fr "$JSCOVERAGE_TMP_DIR" || die "ERROR: Unable to remove obstruting \"$JSCOVERAGE_TMP_DIR\" temp directory!"
  189. fi
  190. # Exec
  191. "$JSCOVERAGE_BIN" "$CODE_DIR" "$JSCOVERAGE_TMP_DIR"
  192. # - Backup the actual code and replace it with jscoverage results
  193. [ -n "$VERBOSE" ] && echo "Replacing $CODE_DIR with $JSCOVERAGE_TMP_DIR ..."
  194. REPORT_FILE_BASE="$JSCOVERAGE_OUTPUT_DIR/$PKG_NAME-coverage"
  195. REPORT_FILE="$REPORT_FILE_BASE.html"
  196. REPORT_FILE_ERR="$REPORT_FILE_BASE.err"
  197. mv "$CODE_DIR" "$CODE_DIR.ORIGINAL" \
  198. && mv "$JSCOVERAGE_TMP_DIR" "$CODE_DIR" \
  199. && LOGGER_PREFIX='' LOGGER_LEVEL=NOTICE "$MOCHA_BIN" --ui exports --reporter "html-cov" --recursive "$TEST_DIR" 2> "$REPORT_FILE_ERR" 1> "$REPORT_FILE" \
  200. || echo "WARNING: JSCoverage: insufficient coverage (exit code $?)."
  201. # || die "ERROR: JSCoverage errors during coverage tests! $(rm -fr "$CODE_DIR" && mv "$CODE_DIR.ORIGINAL" "$CODE_DIR"; echo; cat "$REPORT_FILE")"
  202. # [ -n "$VERBOSE" ] && echo "REPORT OUTPUT: $REPORT_FILE" && cat "$REPORT_FILE" && echo
  203. # Cleanup
  204. rm -rf "$CODE_DIR" \
  205. && mv "$CODE_DIR.ORIGINAL" "$CODE_DIR" \
  206. || die "ERROR: Unable to put code directory \"$CODE_DIR.ORIGNAL\" back where it belongs!"
  207. #TODO: verifying reports should be part of checking test coverage
  208. echo
  209. fi
  210. # This is used by both the PMD and jscheckstyle.
  211. ANALYSIS_TARGET="$npm_package_config_analyze_dirs"
  212. [ -n "$ANALYSIS_TARGET" ] || ANALYSIS_TARGET="$CODE_DIR"
  213. # Static analysis.
  214. [ "$npm_package_config_test_static_analysis" = "false" ] && NO_STATIC_ANALYSIS=1
  215. if [ -z "$NO_STATIC_ANALYSIS" ]; then
  216. echo "Running static analysis ..."
  217. PMD_BIN="$npm_package_config_pmd_bin"
  218. [ -n "$PMD_BIN" ] && [ -x "$PMD_BIN" ] || PMD_BIN="/srv/jenkins/tools/pmd/bin/run.sh"
  219. if [ -n "$PMD_BIN" ] && [ -x "$PMD_BIN" ]; then
  220. PMD_OUTPUT_DIR="$npm_package_config_pmd_output_dir"
  221. [ -n "$PMD_OUTPUT_DIR" ] || PMD_OUTPUT_DIR="$npm_package_config_pmd_output_dir"
  222. [ -n "$PMD_OUTPUT_DIR" ] || [ -n "$npm_config_default_reports_output_dir" ] && PMD_OUTPUT_DIR="$npm_config_default_reports_output_dir/static-analysis"
  223. [ -n "$PMD_OUTPUT_DIR" ] || PMD_OUTPUT_DIR="reports/static-analysis"
  224. [ -d "$PMD_OUTPUT_DIR" ] || mkdir -p "$PMD_OUTPUT_DIR" || die "ERROR: Unable to mkdir \"$PMD_OUTPUT_DIR\", the PMD static analysis output dir!"
  225. REPORT_FILE="$PMD_OUTPUT_DIR/$PKG_NAME-cpd.xml"
  226. "$PMD_BIN" cpd --minimum-tokens 90 $(for TARGET in $ANALYSIS_TARGET; do echo "--files $TARGET "; done) --format xml --language js > "$REPORT_FILE" || echo "WARNING: PMD found issues (exit code $?)."
  227. validate_xml "$REPORT_FILE" || die "ERROR: INVALID REPORT FILE!"
  228. echo
  229. fi
  230. fi
  231. # jscheckstyle, different than mocha's checkstyle.
  232. [ "$npm_package_config_test_jscheckstyle" = "false" ] && NO_JSCHECKSTYLE=1
  233. if [ -z "$NO_JSCHECKSTYLE" ]; then
  234. echo "Running jscheckstyle ..."
  235. JSCHECKSTYLE_BIN="$npm_package_config_jscheckstyle_bin"
  236. #[ -n "$JSCHECKSTYLE_BIN" ] && [ -x "$JSCHECKSTYLE_BIN" ] || JSCHECKSTYLE_BIN=$(which jscheckstyle || echo)
  237. [ -n "$JSCHECKSTYLE_BIN" ] && [ -x "$JSCHECKSTYLE_BIN" ] || JSCHECKSTYLE_BIN="./node_modules/.bin/jscheckstyle"
  238. [ -n "$JSCHECKSTYLE_BIN" ] && [ -x "$JSCHECKSTYLE_BIN" ] || die "ERROR: Unable to find 'jscheckstyle' binary! Install via 'npm install jscheckstyle' to proceed!"
  239. JSCHECKSTYLE_OUTPUT_DIR="$npm_package_config_jscheckstyle_output_dir"
  240. [ -n "$JSCHECKSTYLE_OUTPUT_DIR" ] || JSCHECKSTYLE_OUTPUT_DIR="$npm_package_config_jscheckstyle_output_dir"
  241. [ -n "$JSCHECKSTYLE_OUTPUT_DIR" ] || [ -n "$npm_config_default_reports_output_dir" ] && JSCHECKSTYLE_OUTPUT_DIR="$npm_config_default_reports_output_dir/jscheckstyle"
  242. [ -n "$JSCHECKSTYLE_OUTPUT_DIR" ] || JSCHECKSTYLE_OUTPUT_DIR="reports/jscheckstyle"
  243. [ -d "$JSCHECKSTYLE_OUTPUT_DIR" ] || mkdir -p "$JSCHECKSTYLE_OUTPUT_DIR" || die "ERROR: Unable to mkdir \"$JSCHECKSTYLE_OUTPUT_DIR\", the jscheckstyle output dir!"
  244. REPORT_FILE="$JSCHECKSTYLE_OUTPUT_DIR/$PKG_NAME-jscheckstyle.xml"
  245. "$JSCHECKSTYLE_BIN" --checkstyle $ANALYSIS_TARGET 2> /dev/null 1> "$REPORT_FILE" || echo "WARNING: jscheckstyle: code is too complex"
  246. validate_xml "$REPORT_FILE" || die "ERROR: INVALID REPORT FILE!"
  247. fi