5416: Support testargs for go bits, like "services/arv-git-httpd_test=-check.vv".
[arvados-dev.git] / jenkins / run-tests.sh
1 #!/bin/bash
2
3 read -rd "\000" helpmessage <<EOF
4 $(basename $0): Install and test Arvados components.
5
6 Exit non-zero if any tests fail.
7
8 Syntax:
9         $(basename $0) WORKSPACE=/path/to/arvados [options]
10
11 Options:
12
13 --skip FOO     Do not test the FOO component.
14 --only FOO     Do not test anything except the FOO component.
15 --leave-temp   Do not remove GOPATH, virtualenv, and other temp dirs at exit.
16                Instead, show which directories were used this time so they
17                can be reused in subsequent invocations.
18 --skip-install Do not run any install steps. Just run tests.
19                You should provide GOPATH, GEMHOME, and VENVDIR options
20                from a previous invocation if you use this option.
21 WORKSPACE=path Arvados source tree to test.
22 CONFIGSRC=path Dir with api server config files to copy into source tree.
23                (If none given, leave config files alone in source tree.)
24 services/api_test="TEST=test/functional/arvados/v1/collections_controller_test.rb"
25                Restrict apiserver tests to the given file
26 sdk/python_test="--test-suite test.test_keep_locator"
27                Restrict Python SDK tests to the given class
28 apps/workbench_test="TEST=test/integration/pipeline_instances_test.rb"
29                Restrict Workbench tests to the given file
30 services/arv-git-httpd_test="-check.vv"
31                Show all log messages, even when tests pass (also works
32                with services/keepstore_test etc.)
33 ARVADOS_DEBUG=1
34                Print more debug messages
35 envvar=value   Set \$envvar to value. Primarily useful for WORKSPACE,
36                *_test, and other examples shown above.
37
38 Assuming --skip-install is not given, all components are installed
39 into \$GOPATH, \$VENDIR, and \$GEMHOME before running any tests. Many
40 test suites depend on other components being installed, and installing
41 everything tends to be quicker than debugging dependencies.
42
43 As a special concession to the current CI server config, CONFIGSRC
44 defaults to $HOME/arvados-api-server if that directory exists.
45
46 More information and background:
47
48 https://arvados.org/projects/arvados/wiki/Running_tests
49
50 Available tests:
51
52 apps/workbench
53 apps/workbench_benchmark
54 apps/workbench_profile
55 doc
56 services/api
57 services/crunchstat
58 services/fuse
59 services/keepproxy
60 services/keepstore
61 services/nodemanager
62 services/arv-git-httpd
63 sdk/cli
64 sdk/python
65 sdk/ruby
66 sdk/go/arvadosclient
67 sdk/go/keepclient
68 sdk/go/streamer
69
70 EOF
71
72 # First make sure to remove any ARVADOS_ variables from the calling
73 # environment that could interfere with the tests.
74 unset $(env | cut -d= -f1 | grep \^ARVADOS_)
75
76 # Reset other variables that could affect our [tests'] behavior by
77 # accident.
78 GITDIR=
79 GOPATH=
80 VENVDIR=
81 PYTHONPATH=
82 GEMHOME=
83
84 COLUMNS=80
85
86 leave_temp=
87 skip_install=
88
89 declare -A leave_temp
90 clear_temp() {
91     leaving=""
92     for var in VENVDIR GOPATH GITDIR GEMHOME
93     do
94         if [[ -z "${leave_temp[$var]}" ]]
95         then
96             if [[ -n "${!var}" ]]
97             then
98                 rm -rf "${!var}"
99             fi
100         else
101             leaving+=" $var=\"${!var}\""
102         fi
103     done
104     if [[ -n "$leaving" ]]; then
105         echo "Leaving behind temp dirs: $leaving"
106     fi
107 }
108
109 fatal() {
110     clear_temp
111     echo >&2 "Fatal: $* in ${FUNCNAME[1]} at ${BASH_SOURCE[1]} line ${BASH_LINENO[0]}"
112     exit 1
113 }
114
115 report_outcomes() {
116     for x in "${successes[@]}"
117     do
118         echo "Pass: $x"
119     done
120
121     if [[ ${#failures[@]} == 0 ]]
122     then
123         echo "All test suites passed."
124     else
125         echo "Failures (${#failures[@]}):"
126         for x in "${failures[@]}"
127         do
128             echo "Fail: $x"
129         done
130     fi
131 }
132
133 exit_cleanly() {
134     trap - INT
135     rotate_logfile "$WORKSPACE/apps/workbench/log/" "test.log"
136     stop_services
137     rotate_logfile "$WORKSPACE/services/api/log/" "test.log"
138     report_outcomes
139     clear_temp
140     exit ${#failures}
141 }
142
143 sanity_checks() {
144   # Make sure WORKSPACE is set
145   if ! [[ -n "$WORKSPACE" ]]; then
146     echo >&2 "$helpmessage"
147     echo >&2
148     echo >&2 "Error: WORKSPACE environment variable not set"
149     echo >&2
150     exit 1
151   fi
152
153   # Make sure virtualenv is installed
154   `virtualenv --help >/dev/null 2>&1`
155
156   if [[ "$?" != "0" ]]; then
157     echo >&2
158     echo >&2 "Error: virtualenv could not be found"
159     echo >&2
160     exit 1
161   fi
162
163   # Make sure go is installed
164   `go env >/dev/null 2>&1`
165
166   if [[ "$?" != "0" ]]; then
167     echo >&2
168     echo >&2 "Error: go could not be found"
169     echo >&2
170     exit 1
171   fi
172
173   # Make sure gcc is installed
174   `gcc --help >/dev/null 2>&1`
175
176   if [[ "$?" != "0" ]]; then
177     echo >&2
178     echo >&2 "Error: gcc could not be found"
179     echo >&2
180     exit 1
181   fi
182 }
183
184 rotate_logfile() {
185   # $BUILD_NUMBER is set by Jenkins if this script is being called as part of a Jenkins run
186   if [[ -f "$1/$2" ]]; then
187     THEDATE=`date +%Y%m%d%H%M%S`
188     mv "$1/$2" "$1/$THEDATE-$BUILD_NUMBER-$2"
189     gzip "$1/$THEDATE-$BUILD_NUMBER-$2"
190   fi
191 }
192
193 declare -a failures
194 declare -A skip
195 declare -A testargs
196 skip[apps/workbench_profile]=1
197
198 while [[ -n "$1" ]]
199 do
200     arg="$1"; shift
201     case "$arg" in
202         --help)
203             echo >&2 "$helpmessage"
204             echo >&2
205             exit 1
206             ;;
207         --skip)
208             skipwhat="$1"; shift
209             skip[$skipwhat]=1
210             ;;
211         --only)
212             only="$1"; skip[$1]=""; shift
213             ;;
214         --skip-install)
215             skip_install=1
216             ;;
217         --leave-temp)
218             leave_temp[VENVDIR]=1
219             leave_temp[GOPATH]=1
220             leave_temp[GEMHOME]=1
221             ;;
222         --retry)
223             retry=1
224             ;;
225         *_test=*)
226             suite="${arg%%_test=*}"
227             args="${arg#*=}"
228             testargs["$suite"]="$args"
229             ;;
230         *=*)
231             eval export $(echo $arg | cut -d= -f1)=\"$(echo $arg | cut -d= -f2-)\"
232             ;;
233         *)
234             echo >&2 "$0: Unrecognized option: '$arg'. Try: $0 --help"
235             exit 1
236             ;;
237     esac
238 done
239
240 start_api() {
241     echo 'Starting API server...'
242     cd "$WORKSPACE" \
243         && eval $(python sdk/python/tests/run_test_server.py start --auth admin) \
244         && export ARVADOS_TEST_API_HOST="$ARVADOS_API_HOST" \
245         && export ARVADOS_TEST_API_INSTALLED="$$" \
246         && (env | egrep ^ARVADOS)
247 }
248
249 start_nginx_proxy_services() {
250     echo 'Starting keepproxy, arv-git-httpd, and nginx ssl proxy...'
251     cd "$WORKSPACE" \
252         && python sdk/python/tests/run_test_server.py start_keep_proxy \
253         && python sdk/python/tests/run_test_server.py start_arv-git-httpd \
254         && python sdk/python/tests/run_test_server.py start_nginx \
255         && export ARVADOS_TEST_PROXY_SERVICES=1
256 }
257
258 stop_services() {
259     if [[ -n "$ARVADOS_TEST_PROXY_SERVICES" ]]; then
260         unset ARVADOS_TEST_PROXY_SERVICES
261         cd "$WORKSPACE" \
262             && python sdk/python/tests/run_test_server.py stop_nginx \
263             && python sdk/python/tests/run_test_server.py stop_arv-git-httpd \
264             && python sdk/python/tests/run_test_server.py stop_keep_proxy
265     fi
266     if [[ -n "$ARVADOS_TEST_API_HOST" ]]; then
267         unset ARVADOS_TEST_API_HOST
268         cd "$WORKSPACE" \
269             && python sdk/python/tests/run_test_server.py stop
270     fi
271 }
272
273 interrupt() {
274     failures+=("($(basename $0) interrupted)")
275     exit_cleanly
276 }
277 trap interrupt INT
278
279 sanity_checks
280
281 echo "WORKSPACE=$WORKSPACE"
282
283 if [[ -z "$CONFIGSRC" ]] && [[ -d "$HOME/arvados-api-server" ]]; then
284     # Jenkins expects us to use this by default.
285     CONFIGSRC="$HOME/arvados-api-server"
286 fi
287
288 # Clean up .pyc files that may exist in the workspace
289 cd "$WORKSPACE"
290 find -name '*.pyc' -delete
291
292 # Set up temporary install dirs (unless existing dirs were supplied)
293 for tmpdir in VENVDIR GOPATH GEMHOME
294 do
295     if [[ -n "${!tmpdir}" ]]; then
296         leave_temp[$tmpdir]=1
297     else
298         eval $tmpdir=$(mktemp -d)
299     fi
300 done
301
302 setup_ruby_environment() {
303     if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
304       source "$HOME/.rvm/scripts/rvm"
305       using_rvm=true
306     elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
307       source "/usr/local/rvm/scripts/rvm"
308       using_rvm=true
309     else
310       using_rvm=false
311     fi
312
313     if [[ "$using_rvm" == true ]]; then
314         # If rvm is in use, we can't just put separate "dependencies"
315         # and "gems-under-test" paths to GEM_PATH: passenger resets
316         # the environment to the "current gemset", which would lose
317         # our GEM_PATH and prevent our test suites from running ruby
318         # programs (for example, the Workbench test suite could not
319         # boot an API server or run arv). Instead, we have to make an
320         # rvm gemset and use it for everything.
321
322         [[ `type rvm | head -n1` == "rvm is a function" ]] \
323             || fatal 'rvm check'
324
325         # Put rvm's favorite path back in first place (overriding
326         # virtualenv, which just put itself there). Ignore rvm's
327         # complaint about not being in first place already.
328         rvm use @default 2>/dev/null
329
330         # Create (if needed) and switch to an @arvados-tests
331         # gemset. (Leave the choice of ruby to the caller.)
332         rvm use @arvados-tests --create \
333             || fatal 'rvm gemset setup'
334
335         rvm env
336     else
337         # When our "bundle install"s need to install new gems to
338         # satisfy dependencies, we want them to go where "gem install
339         # --user-install" would put them. (However, if the caller has
340         # already set GEM_HOME, we assume that's where dependencies
341         # should be installed, and we should leave it alone.)
342
343         if [ -z "$GEM_HOME" ]; then
344             user_gempath="$(gem env gempath)"
345             export GEM_HOME="${user_gempath%%:*}"
346         fi
347         PATH="$(gem env gemdir)/bin:$PATH"
348
349         # When we build and install our own gems, we install them in our
350         # $GEMHOME tmpdir, and we want them to be at the front of GEM_PATH and
351         # PATH so integration tests prefer them over other versions that
352         # happen to be installed in $user_gempath, system dirs, etc.
353
354         tmpdir_gem_home="$(env - PATH="$PATH" HOME="$GEMHOME" gem env gempath | cut -f1 -d:)"
355         PATH="$tmpdir_gem_home/bin:$PATH"
356         export GEM_PATH="$tmpdir_gem_home:$(gem env gempath)"
357
358         echo "Will install dependencies to $(gem env gemdir)"
359         echo "Will install arvados gems to $tmpdir_gem_home"
360         echo "Gem search path is GEM_PATH=$GEM_PATH"
361     fi
362 }
363
364 with_test_gemset() {
365     if [[ "$using_rvm" == true ]]; then
366         "$@"
367     else
368         GEM_HOME="$tmpdir_gem_home" "$@"
369     fi
370 }
371
372 export GOPATH
373 mkdir -p "$GOPATH/src/git.curoverse.com"
374 ln -sfn "$WORKSPACE" "$GOPATH/src/git.curoverse.com/arvados.git" \
375     || fatal "symlink failed"
376
377 virtualenv --setuptools "$VENVDIR" || fatal "virtualenv $VENVDIR failed"
378 . "$VENVDIR/bin/activate"
379
380 # Note: this must be the last time we change PATH, otherwise rvm will
381 # whine a lot.
382 setup_ruby_environment
383
384 echo "PATH is $PATH"
385
386 if ! which bundler >/dev/null
387 then
388     gem install --user-install bundler || fatal 'Could not install bundler'
389 fi
390
391 # Needed for run_test_server.py which is used by certain (non-Python) tests.
392 echo "pip install -q PyYAML"
393 pip install -q PyYAML || fatal "pip install PyYAML failed"
394
395 checkexit() {
396     if [[ "$1" != "0" ]]; then
397         title "!!!!!! $2 FAILED !!!!!!"
398         failures+=("$2 (`timer`)")
399     else
400         successes+=("$2 (`timer`)")
401     fi
402 }
403
404 timer_reset() {
405     t0=$SECONDS
406 }
407
408 timer() {
409     echo -n "$(($SECONDS - $t0))s"
410 }
411
412 do_test() {
413     while ! do_test_once ${@} && [[ "$retry" == 1 ]]
414     do
415         read -p 'Try again? [Y/n] ' x
416         if [[ "$x" != "y" ]] && [[ "$x" != "" ]]
417         then
418             break
419         fi
420     done
421 }
422
423 do_test_once() {
424     if [[ -z "${skip[$1]}" ]] && ( [[ -z "$only" ]] || [[ "$only" == "$1" ]] )
425     then
426         title "Running $1 tests"
427         timer_reset
428         if [[ "$2" == "go" ]]
429         then
430             if [[ -n "${testargs[$1]}" ]]
431             then
432                 # "go test -check.vv giturl" doesn't work, but this
433                 # does:
434                 cd "$WORKSPACE/$1" && go test ${testargs[$1]}
435             else
436                 # The above form gets verbose even when testargs is
437                 # empty, so use this form in such cases:
438                 go test "git.curoverse.com/arvados.git/$1"
439             fi
440         elif [[ "$2" == "pip" ]]
441         then
442            cd "$WORKSPACE/$1" \
443                 && python setup.py test ${testargs[$1]}
444         elif [[ "$2" != "" ]]
445         then
446             "test_$2"
447         else
448             "test_$1"
449         fi
450         result="$?"
451         checkexit $result "$1 tests"
452         title "End of $1 tests (`timer`)"
453         return $result
454     else
455         title "Skipping $1 tests"
456     fi
457 }
458
459 do_install() {
460     if [[ -z "$skip_install" ]]
461     then
462         title "Running $1 install"
463         timer_reset
464         if [[ "$2" == "go" ]]
465         then
466             go get -t "git.curoverse.com/arvados.git/$1"
467         elif [[ "$2" == "pip" ]]
468         then
469             cd "$WORKSPACE/$1" \
470                 && python setup.py sdist rotate --keep=1 --match .tar.gz \
471                 && pip install -q --upgrade dist/*.tar.gz
472         elif [[ "$2" != "" ]]
473         then
474             "install_$2"
475         else
476             "install_$1"
477         fi
478         checkexit $? "$1 install"
479         title "End of $1 install (`timer`)"
480     else
481         title "Skipping $1 install"
482     fi
483 }
484
485 title () {
486     txt="********** $1 **********"
487     printf "\n%*s%s\n\n" $((($COLUMNS-${#txt})/2)) "" "$txt"
488 }
489
490 bundle_install_trylocal() {
491     (
492         set -e
493         echo "(Running bundle install --local. 'could not find package' messages are OK.)"
494         if ! bundle install --local --no-deployment; then
495             echo "(Running bundle install again, without --local.)"
496             bundle install --no-deployment
497         fi
498         bundle package --all
499     )
500 }
501
502 install_doc() {
503     cd "$WORKSPACE/doc" \
504         && bundle_install_trylocal \
505         && rm -rf .site
506 }
507 do_install doc
508
509 install_ruby_sdk() {
510     with_test_gemset gem uninstall --force --all --executables arvados \
511         && cd "$WORKSPACE/sdk/ruby" \
512         && bundle_install_trylocal \
513         && gem build arvados.gemspec \
514         && with_test_gemset gem install --no-ri --no-rdoc `ls -t arvados-*.gem|head -n1`
515 }
516 do_install sdk/ruby ruby_sdk
517
518 install_cli() {
519     with_test_gemset gem uninstall --force --all --executables arvados-cli \
520         && cd "$WORKSPACE/sdk/cli" \
521         && bundle_install_trylocal \
522         && gem build arvados-cli.gemspec \
523         && with_test_gemset gem install --no-ri --no-rdoc `ls -t arvados-cli-*.gem|head -n1`
524 }
525 do_install sdk/cli cli
526
527 # Install the Python SDK early. Various other test suites (like
528 # keepproxy) bring up run_test_server.py, which imports the arvados
529 # module. We can't actually *test* the Python SDK yet though, because
530 # its own test suite brings up some of those other programs (like
531 # keepproxy).
532 declare -a pythonstuff
533 pythonstuff=(
534     sdk/python
535     services/fuse
536     services/nodemanager
537     )
538 for p in "${pythonstuff[@]}"
539 do
540     do_install "$p" pip
541 done
542
543 install_apiserver() {
544     cd "$WORKSPACE/services/api" \
545         && RAILS_ENV=test bundle_install_trylocal
546
547     rm -f config/environments/test.rb
548     cp config/environments/test.rb.example config/environments/test.rb
549
550     if [ -n "$CONFIGSRC" ]
551     then
552         for f in database.yml application.yml
553         do
554             cp "$CONFIGSRC/$f" config/ || fatal "$f"
555         done
556     fi
557
558     # Fill in a random secret_token and blob_signing_key for testing
559     SECRET_TOKEN=`echo 'puts rand(2**512).to_s(36)' |ruby`
560     BLOB_SIGNING_KEY=`echo 'puts rand(2**512).to_s(36)' |ruby`
561
562     sed -i'' -e "s:SECRET_TOKEN:$SECRET_TOKEN:" config/application.yml
563     sed -i'' -e "s:BLOB_SIGNING_KEY:$BLOB_SIGNING_KEY:" config/application.yml
564
565     # Set up empty git repo (for git tests)
566     GITDIR=$(mktemp -d)
567     sed -i'' -e "s:/var/cache/git:$GITDIR:" config/application.default.yml
568
569     rm -rf $GITDIR
570     mkdir -p $GITDIR/test
571     cd $GITDIR/test \
572         && git init \
573         && git config user.email "jenkins@ci.curoverse.com" \
574         && git config user.name "Jenkins, CI" \
575         && touch tmp \
576         && git add tmp \
577         && git commit -m 'initial commit'
578
579     # Clear out any lingering postgresql connections to the test
580     # database, so that we can drop it. This assumes the current user
581     # is a postgresql superuser.
582     test_database=$(python -c "import yaml; print yaml.load(file('config/database.yml'))['test']['database']")
583     psql "$test_database" -c "SELECT pg_terminate_backend (pg_stat_activity.procpid::int) FROM pg_stat_activity WHERE pg_stat_activity.datname = '$test_database';" 2>/dev/null
584
585     cd "$WORKSPACE/services/api" \
586         && RAILS_ENV=test bundle exec rake db:drop \
587         && RAILS_ENV=test bundle exec rake db:setup \
588         && RAILS_ENV=test bundle exec rake db:fixtures:load
589 }
590 do_install services/api apiserver
591
592 declare -a gostuff
593 gostuff=(
594     services/arv-git-httpd
595     services/crunchstat
596     services/keepstore
597     services/keepproxy
598     sdk/go/arvadosclient
599     sdk/go/keepclient
600     sdk/go/streamer
601     )
602 for g in "${gostuff[@]}"
603 do
604     do_install "$g" go
605 done
606
607 install_workbench() {
608     cd "$WORKSPACE/apps/workbench" \
609         && mkdir -p tmp/cache \
610         && RAILS_ENV=test bundle_install_trylocal
611 }
612 do_install apps/workbench workbench
613
614 test_doclinkchecker() {
615     (
616         set -e
617         cd "$WORKSPACE/doc"
618         ARVADOS_API_HOST=qr1hi.arvadosapi.com
619         # Make sure python-epydoc is installed or the next line won't
620         # do much good!
621         PYTHONPATH=$WORKSPACE/sdk/python/ bundle exec rake linkchecker baseurl=file://$WORKSPACE/doc/.site/ arvados_workbench_host=workbench.$ARVADOS_API_HOST arvados_api_host=$ARVADOS_API_HOST
622     )
623 }
624 do_test doc doclinkchecker
625
626 stop_services
627
628 test_apiserver() {
629     cd "$WORKSPACE/services/api" \
630         && RAILS_ENV=test bundle exec rake test TESTOPTS=-v ${testargs[services/api]}
631 }
632 do_test services/api apiserver
633
634 # Shortcut for when we're only running apiserver tests. This saves a bit of time,
635 # because we don't need to start up the api server for subsequent tests.
636 if [ ! -z "$only" ] && [ "$only" == "services/api" ]; then
637   rotate_logfile "$WORKSPACE/services/api/log/" "test.log"
638   exit_cleanly
639 fi
640
641 start_api
642
643 test_ruby_sdk() {
644     cd "$WORKSPACE/sdk/ruby" \
645         && bundle exec rake test TESTOPTS=-v ${testargs[sdk/ruby]}
646 }
647 do_test sdk/ruby ruby_sdk
648
649 test_cli() {
650     cd "$WORKSPACE/sdk/cli" \
651         && mkdir -p /tmp/keep \
652         && KEEP_LOCAL_STORE=/tmp/keep bundle exec rake test TESTOPTS=-v ${testargs[sdk/cli]}
653 }
654 do_test sdk/cli cli
655
656 for p in "${pythonstuff[@]}"
657 do
658     do_test "$p" pip
659 done
660
661 for g in "${gostuff[@]}"
662 do
663     do_test "$g" go
664 done
665
666 test_workbench() {
667     start_nginx_proxy_services \
668         && cd "$WORKSPACE/apps/workbench" \
669         && RAILS_ENV=test bundle exec rake test TESTOPTS=-v ${testargs[apps/workbench]}
670 }
671 do_test apps/workbench workbench
672
673 test_workbench_benchmark() {
674     start_nginx_proxy_services \
675         && cd "$WORKSPACE/apps/workbench" \
676         && RAILS_ENV=test bundle exec rake test:benchmark ${testargs[apps/workbench_benchmark]}
677 }
678 do_test apps/workbench_benchmark workbench_benchmark
679
680 test_workbench_profile() {
681     start_nginx_proxy_services \
682         && cd "$WORKSPACE/apps/workbench" \
683         && RAILS_ENV=test bundle exec rake test:profile ${testargs[apps/workbench_profile]}
684 }
685 do_test apps/workbench_profile workbench_profile
686
687 exit_cleanly