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