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