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