Jenkinsfile 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. def retryWithDelay(int maxRetries, int delay, Closure body) {
  2. for (int i = 0; i < maxRetries; i++) {
  3. try {
  4. return body()
  5. } catch (Exception e) {
  6. sleep(delay)
  7. }
  8. }
  9. throw Exception("Failed after ${maxRetries} retries")
  10. }
  11. def device(String ip, String step_label, String cmd) {
  12. withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
  13. def ssh_cmd = """
  14. ssh -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' exec /usr/bin/bash <<'END'
  15. set -e
  16. export TERM=xterm-256color
  17. shopt -s huponexit # kill all child processes when the shell exits
  18. export CI=1
  19. export PYTHONWARNINGS=error
  20. export LOGPRINT=debug
  21. export TEST_DIR=${env.TEST_DIR}
  22. export SOURCE_DIR=${env.SOURCE_DIR}
  23. export GIT_BRANCH=${env.GIT_BRANCH}
  24. export GIT_COMMIT=${env.GIT_COMMIT}
  25. export CI_ARTIFACTS_TOKEN=${env.CI_ARTIFACTS_TOKEN}
  26. export GITHUB_COMMENTS_TOKEN=${env.GITHUB_COMMENTS_TOKEN}
  27. export AZURE_TOKEN='${env.AZURE_TOKEN}'
  28. # only use 1 thread for tici tests since most require HIL
  29. export PYTEST_ADDOPTS="-n 0"
  30. export GIT_SSH_COMMAND="ssh -i /data/gitkey"
  31. source ~/.bash_profile
  32. if [ -f /TICI ]; then
  33. source /etc/profile
  34. rm -rf /tmp/tmp*
  35. rm -rf ~/.commacache
  36. rm -rf /dev/shm/*
  37. rm -rf /dev/tmp/tmp*
  38. if ! systemctl is-active --quiet systemd-resolved; then
  39. echo "restarting resolved"
  40. sudo systemctl start systemd-resolved
  41. sleep 3
  42. fi
  43. # restart aux USB
  44. if [ -e /sys/bus/usb/drivers/hub/3-0:1.0 ]; then
  45. echo "restarting aux usb"
  46. echo "3-0:1.0" | sudo tee /sys/bus/usb/drivers/hub/unbind
  47. sleep 0.5
  48. echo "3-0:1.0" | sudo tee /sys/bus/usb/drivers/hub/bind
  49. fi
  50. fi
  51. if [ -f /data/openpilot/launch_env.sh ]; then
  52. source /data/openpilot/launch_env.sh
  53. fi
  54. ln -snf ${env.TEST_DIR} /data/pythonpath
  55. cd ${env.TEST_DIR} || true
  56. ${cmd}
  57. END"""
  58. sh script: ssh_cmd, label: step_label
  59. }
  60. }
  61. def deviceStage(String stageName, String deviceType, List extra_env, def steps) {
  62. stage(stageName) {
  63. if (currentBuild.result != null) {
  64. return
  65. }
  66. def extra = extra_env.collect { "export ${it}" }.join('\n');
  67. def branch = env.BRANCH_NAME ?: 'master';
  68. lock(resource: "", label: deviceType, inversePrecedence: true, variable: 'device_ip', quantity: 1, resourceSelectStrategy: 'random') {
  69. docker.image('ghcr.io/commaai/alpine-ssh').inside('--user=root') {
  70. timeout(time: 35, unit: 'MINUTES') {
  71. retry (3) {
  72. def date = sh(script: 'date', returnStdout: true).trim();
  73. device(device_ip, "set time", "date -s '" + date + "'")
  74. device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh"))
  75. }
  76. steps.each { item ->
  77. if (branch != "master" && item.size() == 3 && !hasPathChanged(item[2])) {
  78. println "Skipping ${item[0]}: no changes in ${item[2]}."
  79. return;
  80. } else {
  81. device(device_ip, item[0], item[1])
  82. }
  83. }
  84. }
  85. }
  86. }
  87. }
  88. }
  89. @NonCPS
  90. def hasPathChanged(List<String> paths) {
  91. changedFiles = []
  92. for (changeLogSet in currentBuild.changeSets) {
  93. for (entry in changeLogSet.getItems()) {
  94. for (file in entry.getAffectedFiles()) {
  95. changedFiles.add(file.getPath())
  96. }
  97. }
  98. }
  99. env.CHANGED_FILES = changedFiles.join(" ")
  100. if (currentBuild.number > 1) {
  101. env.CHANGED_FILES += currentBuild.previousBuild.getBuildVariables().get("CHANGED_FILES")
  102. }
  103. for (path in paths) {
  104. if (env.CHANGED_FILES.contains(path)) {
  105. return true;
  106. }
  107. }
  108. return false;
  109. }
  110. def setupCredentials() {
  111. withCredentials([
  112. string(credentialsId: 'azure_token', variable: 'AZURE_TOKEN'),
  113. ]) {
  114. env.AZURE_TOKEN = "${AZURE_TOKEN}"
  115. }
  116. withCredentials([
  117. string(credentialsId: 'ci_artifacts_pat', variable: 'CI_ARTIFACTS_TOKEN'),
  118. ]) {
  119. env.CI_ARTIFACTS_TOKEN = "${CI_ARTIFACTS_TOKEN}"
  120. }
  121. withCredentials([
  122. string(credentialsId: 'post_comments_github_pat', variable: 'GITHUB_COMMENTS_TOKEN'),
  123. ]) {
  124. env.GITHUB_COMMENTS_TOKEN = "${GITHUB_COMMENTS_TOKEN}"
  125. }
  126. }
  127. node {
  128. env.CI = "1"
  129. env.PYTHONWARNINGS = "error"
  130. env.TEST_DIR = "/data/openpilot"
  131. env.SOURCE_DIR = "/data/openpilot_source/"
  132. setupCredentials()
  133. env.GIT_BRANCH = checkout(scm).GIT_BRANCH
  134. env.GIT_COMMIT = checkout(scm).GIT_COMMIT
  135. def excludeBranches = ['master-ci', 'devel', 'devel-staging', 'release3', 'release3-staging',
  136. 'testing-closet*', 'hotfix-*']
  137. def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
  138. if (env.BRANCH_NAME != 'master') {
  139. properties([
  140. disableConcurrentBuilds(abortPrevious: true)
  141. ])
  142. }
  143. try {
  144. if (env.BRANCH_NAME == 'devel-staging') {
  145. deviceStage("build release3-staging", "tici-needs-can", [], [
  146. ["build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"],
  147. ])
  148. }
  149. if (env.BRANCH_NAME == 'master-ci') {
  150. deviceStage("build nightly", "tici-needs-can", [], [
  151. ["build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"],
  152. ])
  153. }
  154. if (!env.BRANCH_NAME.matches(excludeRegex)) {
  155. parallel (
  156. // tici tests
  157. 'onroad tests': {
  158. deviceStage("onroad", "tici-needs-can", [], [
  159. // TODO: ideally, this test runs in master-ci, but it takes 5+m to build it
  160. //["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"],
  161. ["build openpilot", "cd system/manager && ./build.py"],
  162. ["check dirty", "release/check-dirty.sh"],
  163. ["onroad tests", "pytest selfdrive/test/test_onroad.py -s"],
  164. //["time to onroad", "pytest selfdrive/test/test_time_to_onroad.py"],
  165. ])
  166. },
  167. 'HW + Unit Tests': {
  168. deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [
  169. ["build", "cd system/manager && ./build.py"],
  170. ["test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", ["panda/", "selfdrive/pandad/"]],
  171. ["test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"],
  172. ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"],
  173. ["test pigeond", "pytest system/ubloxd/tests/test_pigeond.py"],
  174. ["test manager", "pytest system/manager/test/test_manager.py"],
  175. ])
  176. },
  177. 'loopback': {
  178. deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [
  179. ["build openpilot", "cd system/manager && ./build.py"],
  180. ["test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"],
  181. ])
  182. },
  183. 'camerad': {
  184. deviceStage("AR0231", "tici-ar0231", ["UNSAFE=1"], [
  185. ["build", "cd system/manager && ./build.py"],
  186. ["test camerad", "pytest system/camerad/test/test_camerad.py"],
  187. ["test exposure", "pytest system/camerad/test/test_exposure.py"],
  188. ])
  189. deviceStage("OX03C10", "tici-ox03c10", ["UNSAFE=1"], [
  190. ["build", "cd system/manager && ./build.py"],
  191. ["test camerad", "pytest system/camerad/test/test_camerad.py"],
  192. ["test exposure", "pytest system/camerad/test/test_exposure.py"],
  193. ])
  194. },
  195. 'sensord': {
  196. deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [
  197. ["build", "cd system/manager && ./build.py"],
  198. ["test sensord", "pytest system/sensord/tests/test_sensord.py"],
  199. ])
  200. deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [
  201. ["build", "cd system/manager && ./build.py"],
  202. ["test sensord", "pytest system/sensord/tests/test_sensord.py"],
  203. ])
  204. },
  205. 'replay': {
  206. deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [
  207. ["build", "cd system/manager && ./build.py", ["selfdrive/modeld/"]],
  208. ["model replay", "selfdrive/test/process_replay/model_replay.py", ["selfdrive/modeld/"]],
  209. ])
  210. },
  211. 'tizi': {
  212. deviceStage("tizi", "tizi", ["UNSAFE=1"], [
  213. ["build openpilot", "cd system/manager && ./build.py"],
  214. ["test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"],
  215. ["test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"],
  216. ["test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", ["panda/", "selfdrive/pandad/"]],
  217. ["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"],
  218. ["test hw", "pytest system/hardware/tici/tests/test_hardware.py"],
  219. ["test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py"],
  220. ])
  221. },
  222. )
  223. }
  224. } catch (Exception e) {
  225. currentBuild.result = 'FAILED'
  226. throw e
  227. }
  228. }