summaryrefslogtreecommitdiff
path: root/cbuildbot/run_tests
blob: 1a5e2b3d53b4f9ee5ec1c99ff1a37079920360d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#!/bin/bash

# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

CHROMITE_PATH=$(realpath "$(dirname "$0")/..")
IN_CHROOT="cros_sdk"
TIMEOUT="timeout -k 5m 20m"
CHROOT_CHROMITE=../../chromite

set -eu

# List all exceptions, with a token describing what's odd here.
# inside - inside the chroot
# skip - don't run this test (please comment on why)
declare -A special_tests
special_tests=(
  # Tests that need to run inside the chroot.
  ['cros/commands/cros_build_unittest.py']=inside
  ['lib/upgrade_table_unittest.py']=inside
  ['scripts/cros_list_modified_packages_unittest.py']=inside
  ['scripts/cros_mark_as_stable_unittest.py']=inside
  ['scripts/cros_mark_chrome_as_stable_unittest.py']=inside
  ['scripts/sync_package_status_unittest.py']=inside
  ['scripts/cros_portage_upgrade_unittest.py']=inside
  ['scripts/upload_package_status_unittest.py']=inside

  # Tests that need to run outside the chroot.
  ['lib/cgroups_unittest.py']=outside

  # Tests that are presently broken.
  ['lib/gdata_lib_unittest.py']=skip
  ['scripts/chrome_set_ver_unittest.py']=skip
  ['scripts/check_gdata_token_unittest.py']=skip
  ['scripts/merge_package_status_unittest.py']=skip
  ['scripts/upload_package_status_unittest.py']=skip
  # TODO(akeshet): skip only test004GetLatestSHA1ForBranch
  # crbug.com/352297
  ['lib/gerrit_unittest.py']=skip

  # Tests that take >2 minutes to run.  All the slow tests are
  # disabled atm though ...
  #['scripts/cros_portage_upgrade_unittest.py']=skip
)

skip_quick_tests() {
  # Tests that require network can be really slow.
  special_tests['cbuildbot/manifest_version_unittest.py']=skip
  special_tests['cbuildbot/repository_unittest.py']=skip
  special_tests['cbuildbot/remote_try_unittest.py']=skip
  special_tests['lib/cros_build_lib_unittest.py']=skip
  special_tests['lib/gerrit_unittest.py']=skip
  special_tests['lib/patch_unittest.py']=skip

  # cgroups_unittest runs cros_sdk a lot, so is slow.
  special_tests['lib/cgroups_unittest.py']=skip
}

# Helper function to add failed logs/tests to be printed out later.
# $1 test that failed.
# $2 log file where we stored the output of the failed test.
append_failed_test() {
  echo "ERROR: Unittest $1 failed.  Log will be output at end of run!!!"

  cat - "$2" <<EOF >>"${LOGFILE}.err.${BASHPID}"

FAIL: Unittest $1 failed output:

EOF
}

# Wrapper to run unittest.  Hides output unless test fails.
# $1 test to run.  Must be in chromite/cbuildbot.
# $2 Is this a dry run?
run_test() {
  local test=$1 dryrun=$2
  local log_file="${LOGFILE}.tmp.${BASHPID}"
  local special="${special_tests[${test}]:-}"
  local starttime="$(date +%s%N)"

  if [[ "${special}" == "skip" ]]; then
    echo "Skipping unittest ${test}"
    return
  elif [[ "${special}" == "outside" && -f /etc/cros_chroot_version ]]; then
    echo "Skipping unittest ${test} because it must run outside the chroot"
    return
  elif [[ "${special}" == "inside" && ! -f /etc/cros_chroot_version ]]; then
    if ${SKIP_CHROOT_TESTS}; then
      echo "Skipping unittest ${test} because it must run inside the chroot"
      return
    else
      echo "Starting unittest ${test} inside the chroot"
      if ! ${dryrun}; then
        ${TIMEOUT} ${IN_CHROOT} -- python "${CHROOT_CHROMITE}/${test}" -v \
          &> "${log_file}" || append_failed_test "${test}" "${log_file}"
      fi
    fi
  else
    echo "Starting unittest ${test}"
    if ! ${dryrun}; then
      ${TIMEOUT} python "${CHROMITE_PATH}/${test}" -v &> "${log_file}" ||
        append_failed_test "${test}" "${log_file}"
    fi
  fi

  if ! ${dryrun}; then
    local endtime="$(date +%s%N)"
    local duration=$(( (endtime - starttime) / 1000000 ))

    echo "Finished unittest ${test} (${duration} ms)"
  fi
  rm -f "${log_file}"
}

cleanup() {
  delayed_kill() {
    sleep 5
    kill -9 ${children[*]} &> /dev/null
  }

  echo "Cleaning up backgrounded jobs."
  # Graceful exit.
  kill -INT ${children[*]} &> /dev/null
  # Set of a hard kill timer after a while.
  delayed_kill &
  wait ${children[*]}
  show_logs
}

show_logs() {
  cat "${LOGFILE}".err.* > "${LOGFILE}" 2>/dev/null || :

  rm -f "${LOGFILE}".*
  if [[ -s ${LOGFILE} ]]; then
    cat "${LOGFILE}"
    echo
    echo
    echo "FAIL: The following tests failed:"
    sed -nre '/^FAIL:/s/^FAIL: Unittest (.*) failed output:/\1/p' "${LOGFILE}"
    rm -f "${LOGFILE}"
    exit 1
  fi

  rm -f "${LOGFILE}"
}

usage() {
  cat <<EOF
Usage: run_tests [options] [tests]

Run the specified tests.  If none are specified, we'll scan the
tree looking for tests to run and then only run the semi-fast ones.

You can add a .testignore file to a dir to disable scanning it.

Options:
  -q, --quick     Only run the really quick tests
  -n, --dry-run   Do everything but actually run the test
  -l, --list      List all the available tests
  -h, --help      This screen
EOF

  if [[ $# -gt 0 ]]; then
    printf '\nerror: %s\n' "$*" >&2
    exit 1
  else
    exit 0
  fi
}

main() {
  # Parse args from the user first.
  local list=false
  local dryrun=false
  local user_tests=()
  while [[ $# -gt 0 ]]; do
    case $1 in
    -h|--help)     usage;;
    -q|--quick)    skip_quick_tests;;
    -n|--dry-run)  dryrun=true;;
    -l|--list)     list=true;;
    -*)            usage "unknown option $1";;
    *)             user_tests+=( "$1" );;
    esac
    shift
  done
  if [[ ${#user_tests[@]} -gt 0 ]]; then
    set -- "${user_tests[@]}"
  fi

  # For some versions of 'sudo' (notably, the 'sudo' in the chroot at
  # the time of this writing), sudo -v will ask for a password whether
  # or not it's needed.  'sudo true' will do what we want.
  sudo true

  # Switch to CHROMITE_PATH, in case cwd is outside of the repo. This ensures
  # that "repo list" looks at the right repo, and sets up a consistent test
  # environment.
  cd "${CHROMITE_PATH}"

  SKIP_CHROOT_TESTS=false
  if ! repo list 2>/dev/null | grep -q chromiumos-overlay; then
    echo "chromiumos-overlay is not present. Skipping chroot tests..."
    SKIP_CHROOT_TESTS=true

    # cgroups_unittest requires cros_sdk, so it doesn't work.
    special_tests['lib/cgroups_unittest.py']=skip
  fi

  # Default to running all tests.
  if [[ $# -eq 0 ]]; then
    # List all unit test scripts that match the given pattern.
    local prune_tests=(
      $(find "${CHROMITE_PATH}" -name .testignore \
          -printf '-path %h -prune -o ')
    )
    local all_tests=(
      $(find "${CHROMITE_PATH}" \
          ${prune_tests[*]} \
          -name '*_unittest.py' -printf '%P ')
    )
    set -- "${all_tests[@]}"
  fi

  if ${list}; then
    printf '%s\n' "$@" | sort
    exit 0
  fi

  # Now do the real code.
  LOGFILE="$(mktemp -t cbuildbot.run_tests.XXXXXX)"
  trap cleanup INT TERM

  local children=()
  for test in "$@"; do
    run_test ${test} ${dryrun} &
    children+=( $! )
  done

  wait ${children[*]}
  trap - INT TERM

  show_logs
  if ! ${dryrun}; then
    echo "All tests succeeded!"
  fi
}

main "$@"