5416: Run keepproxy, arv-git-httpd, and SSL proxy services during Workbench test...
[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_services
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 start_nginx_proxy_services() {
247     echo 'Starting keepproxy, arv-git-httpd, and nginx ssl proxy...'
248     cd "$WORKSPACE" \
249         && python sdk/python/tests/run_test_server.py start_keep_proxy \
250         && python sdk/python/tests/run_test_server.py start_arv-git-httpd \
251         && python sdk/python/tests/run_test_server.py start_nginx \
252         && export ARVADOS_TEST_PROXY_SERVICES=1
253 }
254
255 stop_services() {
256     if [[ -n "$ARVADOS_TEST_PROXY_SERVICES" ]]; then
257         unset ARVADOS_TEST_PROXY_SERVICES
258         cd "$WORKSPACE" \
259             && python sdk/python/tests/run_test_server.py stop_nginx \
260             && python sdk/python/tests/run_test_server.py stop_arv-git-httpd \
261             && python sdk/python/tests/run_test_server.py stop_keep_proxy
262     fi
263     if [[ -n "$ARVADOS_TEST_API_HOST" ]]; then
264         unset ARVADOS_TEST_API_HOST
265         cd "$WORKSPACE" \
266             && python sdk/python/tests/run_test_server.py stop
267     fi
268 }
269
270 interrupt() {
271     failures+=("($(basename $0) interrupted)")
272     exit_cleanly
273 }
274 trap interrupt INT
275
276 sanity_checks
277
278 echo "WORKSPACE=$WORKSPACE"
279
280 if [[ -z "$CONFIGSRC" ]] && [[ -d "$HOME/arvados-api-server" ]]; then
281     # Jenkins expects us to use this by default.
282     CONFIGSRC="$HOME/arvados-api-server"
283 fi
284
285 # Clean up .pyc files that may exist in the workspace
286 cd "$WORKSPACE"
287 find -name '*.pyc' -delete
288
289 # Set up temporary install dirs (unless existing dirs were supplied)
290 for tmpdir in VENVDIR GOPATH GEMHOME
291 do
292     if [[ -n "${!tmpdir}" ]]; then
293         leave_temp[$tmpdir]=1
294     else
295         eval $tmpdir=$(mktemp -d)
296     fi
297 done
298
299 setup_ruby_environment() {
300     if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
301       source "$HOME/.rvm/scripts/rvm"
302       using_rvm=true
303     elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
304       source "/usr/local/rvm/scripts/rvm"
305       using_rvm=true
306     else
307       using_rvm=false
308     fi
309
310     if [[ "$using_rvm" == true ]]; then
311         # If rvm is in use, we can't just put separate "dependencies"
312         # and "gems-under-test" paths to GEM_PATH: passenger resets
313         # the environment to the "current gemset", which would lose
314         # our GEM_PATH and prevent our test suites from running ruby
315         # programs (for example, the Workbench test suite could not
316         # boot an API server or run arv). Instead, we have to make an
317         # rvm gemset and use it for everything.
318
319         [[ `type rvm | head -n1` == "rvm is a function" ]] \
320             || fatal 'rvm check'
321
322         # Put rvm's favorite path back in first place (overriding
323         # virtualenv, which just put itself there). Ignore rvm's
324         # complaint about not being in first place already.
325         rvm use @default 2>/dev/null
326
327         # Create (if needed) and switch to an @arvados-tests
328         # gemset. (Leave the choice of ruby to the caller.)
329         rvm use @arvados-tests --create \
330             || fatal 'rvm gemset setup'
331
332         rvm env
333     else
334         # When our "bundle install"s need to install new gems to
335         # satisfy dependencies, we want them to go where "gem install
336         # --user-install" would put them. (However, if the caller has
337         # already set GEM_HOME, we assume that's where dependencies
338         # should be installed, and we should leave it alone.)
339
340         if [ -z "$GEM_HOME" ]; then
341             user_gempath="$(gem env gempath)"
342             export GEM_HOME="${user_gempath%%:*}"
343         fi
344         PATH="$(gem env gemdir)/bin:$PATH"
345
346         # When we build and install our own gems, we install them in our
347         # $GEMHOME tmpdir, and we want them to be at the front of GEM_PATH and
348         # PATH so integration tests prefer them over other versions that
349         # happen to be installed in $user_gempath, system dirs, etc.
350
351         tmpdir_gem_home="$(env - PATH="$PATH" HOME="$GEMHOME" gem env gempath | cut -f1 -d:)"
352         PATH="$tmpdir_gem_home/bin:$PATH"
353         export GEM_PATH="$tmpdir_gem_home:$(gem env gempath)"
354
355         echo "Will install dependencies to $(gem env gemdir)"
356         echo "Will install arvados gems to $tmpdir_gem_home"
357         echo "Gem search path is GEM_PATH=$GEM_PATH"
358     fi
359 }
360
361 with_test_gemset() {
362     if [[ "$using_rvm" == true ]]; then
363         "$@"
364     else
365         GEM_HOME="$tmpdir_gem_home" "$@"
366     fi
367 }
368
369 export GOPATH
370 mkdir -p "$GOPATH/src/git.curoverse.com"
371 ln -sfn "$WORKSPACE" "$GOPATH/src/git.curoverse.com/arvados.git" \
372     || fatal "symlink failed"
373
374 virtualenv --setuptools "$VENVDIR" || fatal "virtualenv $VENVDIR failed"
375 . "$VENVDIR/bin/activate"
376
377 # Note: this must be the last time we change PATH, otherwise rvm will
378 # whine a lot.
379 setup_ruby_environment
380
381 echo "PATH is $PATH"
382
383 if ! which bundler >/dev/null
384 then
385     gem install --user-install bundler || fatal 'Could not install bundler'
386 fi
387
388 # Needed for run_test_server.py which is used by certain (non-Python) tests.
389 echo "pip install -q PyYAML"
390 pip install -q PyYAML || fatal "pip install PyYAML failed"
391
392 checkexit() {
393     if [[ "$1" != "0" ]]; then
394         title "!!!!!! $2 FAILED !!!!!!"
395         failures+=("$2 (`timer`)")
396     else
397         successes+=("$2 (`timer`)")
398     fi
399 }
400
401 timer_reset() {
402     t0=$SECONDS
403 }
404
405 timer() {
406     echo -n "$(($SECONDS - $t0))s"
407 }
408
409 do_test() {
410     while ! do_test_once ${@} && [[ "$retry" == 1 ]]
411     do
412         read -p 'Try again? [Y/n] ' x
413         if [[ "$x" != "y" ]] && [[ "$x" != "" ]]
414         then
415             break
416         fi
417     done
418 }
419
420 do_test_once() {
421     if [[ -z "${skip[$1]}" ]] && ( [[ -z "$only" ]] || [[ "$only" == "$1" ]] )
422     then
423         title "Running $1 tests"
424         timer_reset
425         if [[ "$2" == "go" ]]
426         then
427             go test ${testargs[$1]} "git.curoverse.com/arvados.git/$1"
428         elif [[ "$2" == "pip" ]]
429         then
430            cd "$WORKSPACE/$1" \
431                 && python setup.py test ${testargs[$1]}
432         elif [[ "$2" != "" ]]
433         then
434             "test_$2"
435         else
436             "test_$1"
437         fi
438         result="$?"
439         checkexit $result "$1 tests"
440         title "End of $1 tests (`timer`)"
441         return $result
442     else
443         title "Skipping $1 tests"
444     fi
445 }
446
447 do_install() {
448     if [[ -z "$skip_install" ]]
449     then
450         title "Running $1 install"
451         timer_reset
452         if [[ "$2" == "go" ]]
453         then
454             go get -t "git.curoverse.com/arvados.git/$1"
455         elif [[ "$2" == "pip" ]]
456         then
457             cd "$WORKSPACE/$1" \
458                 && python setup.py sdist rotate --keep=1 --match .tar.gz \
459                 && pip install -q --upgrade dist/*.tar.gz
460         elif [[ "$2" != "" ]]
461         then
462             "install_$2"
463         else
464             "install_$1"
465         fi
466         checkexit $? "$1 install"
467         title "End of $1 install (`timer`)"
468     else
469         title "Skipping $1 install"
470     fi
471 }
472
473 title () {
474     txt="********** $1 **********"
475     printf "\n%*s%s\n\n" $((($COLUMNS-${#txt})/2)) "" "$txt"
476 }
477
478 bundle_install_trylocal() {
479     (
480         set -e
481         echo "(Running bundle install --local. 'could not find package' messages are OK.)"
482         if ! bundle install --local --no-deployment; then
483             echo "(Running bundle install again, without --local.)"
484             bundle install --no-deployment
485         fi
486         bundle package --all
487     )
488 }
489
490 install_doc() {
491     cd "$WORKSPACE/doc" \
492         && bundle_install_trylocal \
493         && rm -rf .site
494 }
495 do_install doc
496
497 install_ruby_sdk() {
498     with_test_gemset gem uninstall --force --all --executables arvados \
499         && cd "$WORKSPACE/sdk/ruby" \
500         && bundle_install_trylocal \
501         && gem build arvados.gemspec \
502         && with_test_gemset gem install --no-ri --no-rdoc `ls -t arvados-*.gem|head -n1`
503 }
504 do_install sdk/ruby ruby_sdk
505
506 install_cli() {
507     with_test_gemset gem uninstall --force --all --executables arvados-cli \
508         && cd "$WORKSPACE/sdk/cli" \
509         && bundle_install_trylocal \
510         && gem build arvados-cli.gemspec \
511         && with_test_gemset gem install --no-ri --no-rdoc `ls -t arvados-cli-*.gem|head -n1`
512 }
513 do_install sdk/cli cli
514
515 # Install the Python SDK early. Various other test suites (like
516 # keepproxy) bring up run_test_server.py, which imports the arvados
517 # module. We can't actually *test* the Python SDK yet though, because
518 # its own test suite brings up some of those other programs (like
519 # keepproxy).
520 declare -a pythonstuff
521 pythonstuff=(
522     sdk/python
523     services/fuse
524     services/nodemanager
525     )
526 for p in "${pythonstuff[@]}"
527 do
528     do_install "$p" pip
529 done
530
531 install_apiserver() {
532     cd "$WORKSPACE/services/api" \
533         && RAILS_ENV=test bundle_install_trylocal
534
535     rm -f config/environments/test.rb
536     cp config/environments/test.rb.example config/environments/test.rb
537
538     if [ -n "$CONFIGSRC" ]
539     then
540         for f in database.yml application.yml
541         do
542             cp "$CONFIGSRC/$f" config/ || fatal "$f"
543         done
544     fi
545
546     # Fill in a random secret_token and blob_signing_key for testing
547     SECRET_TOKEN=`echo 'puts rand(2**512).to_s(36)' |ruby`
548     BLOB_SIGNING_KEY=`echo 'puts rand(2**512).to_s(36)' |ruby`
549
550     sed -i'' -e "s:SECRET_TOKEN:$SECRET_TOKEN:" config/application.yml
551     sed -i'' -e "s:BLOB_SIGNING_KEY:$BLOB_SIGNING_KEY:" config/application.yml
552
553     # Set up empty git repo (for git tests)
554     GITDIR=$(mktemp -d)
555     sed -i'' -e "s:/var/cache/git:$GITDIR:" config/application.default.yml
556
557     rm -rf $GITDIR
558     mkdir -p $GITDIR/test
559     cd $GITDIR/test \
560         && git init \
561         && git config user.email "jenkins@ci.curoverse.com" \
562         && git config user.name "Jenkins, CI" \
563         && touch tmp \
564         && git add tmp \
565         && git commit -m 'initial commit'
566
567     # Clear out any lingering postgresql connections to the test
568     # database, so that we can drop it. This assumes the current user
569     # is a postgresql superuser.
570     test_database=$(python -c "import yaml; print yaml.load(file('config/database.yml'))['test']['database']")
571     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
572
573     cd "$WORKSPACE/services/api" \
574         && RAILS_ENV=test bundle exec rake db:drop \
575         && RAILS_ENV=test bundle exec rake db:setup \
576         && RAILS_ENV=test bundle exec rake db:fixtures:load
577 }
578 do_install services/api apiserver
579
580 declare -a gostuff
581 gostuff=(
582     services/arv-git-httpd
583     services/crunchstat
584     services/keepstore
585     services/keepproxy
586     sdk/go/arvadosclient
587     sdk/go/keepclient
588     sdk/go/streamer
589     )
590 for g in "${gostuff[@]}"
591 do
592     do_install "$g" go
593 done
594
595 install_workbench() {
596     cd "$WORKSPACE/apps/workbench" \
597         && mkdir -p tmp/cache \
598         && RAILS_ENV=test bundle_install_trylocal
599 }
600 do_install apps/workbench workbench
601
602 test_doclinkchecker() {
603     (
604         set -e
605         cd "$WORKSPACE/doc"
606         ARVADOS_API_HOST=qr1hi.arvadosapi.com
607         # Make sure python-epydoc is installed or the next line won't
608         # do much good!
609         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
610     )
611 }
612 do_test doc doclinkchecker
613
614 stop_services
615
616 test_apiserver() {
617     cd "$WORKSPACE/services/api" \
618         && RAILS_ENV=test bundle exec rake test TESTOPTS=-v ${testargs[services/api]}
619 }
620 do_test services/api apiserver
621
622 # Shortcut for when we're only running apiserver tests. This saves a bit of time,
623 # because we don't need to start up the api server for subsequent tests.
624 if [ ! -z "$only" ] && [ "$only" == "services/api" ]; then
625   rotate_logfile "$WORKSPACE/services/api/log/" "test.log"
626   exit_cleanly
627 fi
628
629 start_api
630
631 test_ruby_sdk() {
632     cd "$WORKSPACE/sdk/ruby" \
633         && bundle exec rake test TESTOPTS=-v ${testargs[sdk/ruby]}
634 }
635 do_test sdk/ruby ruby_sdk
636
637 test_cli() {
638     cd "$WORKSPACE/sdk/cli" \
639         && mkdir -p /tmp/keep \
640         && KEEP_LOCAL_STORE=/tmp/keep bundle exec rake test TESTOPTS=-v ${testargs[sdk/cli]}
641 }
642 do_test sdk/cli cli
643
644 for p in "${pythonstuff[@]}"
645 do
646     do_test "$p" pip
647 done
648
649 for g in "${gostuff[@]}"
650 do
651     do_test "$g" go
652 done
653
654 test_workbench() {
655     start_nginx_proxy_services \
656         && cd "$WORKSPACE/apps/workbench" \
657         && RAILS_ENV=test bundle exec rake test TESTOPTS=-v ${testargs[apps/workbench]}
658 }
659 do_test apps/workbench workbench
660
661 test_workbench_benchmark() {
662     start_nginx_proxy_services \
663         && cd "$WORKSPACE/apps/workbench" \
664         && RAILS_ENV=test bundle exec rake test:benchmark ${testargs[apps/workbench_benchmark]}
665 }
666 do_test apps/workbench_benchmark workbench_benchmark
667
668 test_workbench_profile() {
669     start_nginx_proxy_services \
670         && cd "$WORKSPACE/apps/workbench" \
671         && RAILS_ENV=test bundle exec rake test:profile ${testargs[apps/workbench_profile]}
672 }
673 do_test apps/workbench_profile workbench_profile
674
675 exit_cleanly