테스트가 실패하자 정리 step까지 건너뛰어 컨테이너가 그대로 남아 있던 이유
문제 발생
self-hosted 러너에서 E2E 테스트를 돌리는 job이었습니다. 테스트용 컨테이너를 띄우고, 테스트를 돌리고, 마지막에 내리는 단순한 구조였습니다.
steps:
- uses: actions/checkout@v4
- run: docker compose -f e2e.yml up -d
- run: pnpm test:e2e
- name: 정리
run: docker compose -f e2e.yml down -v테스트가 한 번 실패한 뒤로 다음 실행부터는 port is already allocated로 시작조차 못 했습니다. 실행 기록을 열어 보니 정리 step 옆에 건너뜀 표시가 붙어 있었습니다. 컨테이너가 러너에 그대로 살아서 포트를 쥐고 있었던 겁니다.
원인 분석
if를 안 적은 step에도 조건은 걸려 있습니다. GitHub 문서의 상태 확인 함수 설명에 따르면, 이 함수들 중 하나를 넣지 않으면 기본으로 success()가 적용됩니다. success()는 이전 step이 모두 성공했을 때만 참이라, 테스트가 실패한 순간 그 뒤의 step은 전부 건너뜁니다. 정리 step도 예외가 아니었습니다.
그렇다고 아무 데나 always()를 붙이면 되는 것도 아니었습니다. 문서에 따르면 always()는 실행이 취소됐을 때도 step을 돌게 만듭니다. 그래서 문서는 소스 받기처럼 치명적으로 실패할 수 있는 작업에는 always를 쓰지 말라고, 그러면 워크플로가 타임아웃까지 멈춰 있을 수 있다고 경고합니다. 성공·실패와 상관없이 돌리고 싶을 때 권하는 대안은 if: ${{ !cancelled() }}입니다.
결국 step마다 "언제 돌아야 하는가"를 직접 적어 줘야 했던 겁니다.
해결 방안
- 조건 네 가지를 구분해서 씁니다. 문서의 설명을 기준으로 정리하면 이렇습니다.
- 아무것도 안 적음(
success()): 이전 step이 모두 성공했을 때 failure(): 이전 step 중 하나라도 실패했을 때!cancelled(): 취소되지만 않았다면 성공이든 실패든always(): 취소됐을 때까지 포함해서 무조건
- 러너에 흔적을 남기는 정리 작업은
always()로 둡니다. 취소된 실행에서도 컨테이너는 남으니까요. 대신 이 step이 멈추면 끝없이 기다리게 되니timeout-minutes를 같이 겁니다.
- name: 정리
if: ${{ always() }}
timeout-minutes: 3
run: docker compose -f e2e.yml down -v- 실패 원인을 남기는 step은
failure()로 둡니다. 성공한 실행에서까지 로그를 쌓을 필요는 없습니다.
- name: 실패 로그 저장
if: ${{ failure() }}
run: docker compose -f e2e.yml logs > e2e.log-
테스트 리포트 업로드는
!cancelled()로 둡니다. 실패한 실행의 리포트야말로 가장 필요하지만, 사람이 취소한 실행의 리포트는 의미가 없습니다. 문서가 권하는 형태 그대로입니다. -
시작할 때 한 번 더 치웁니다. 러너 자체가 죽어 버리면 어떤 조건도 소용이 없습니다. 띄우기 전에
docker compose -f e2e.yml down -v를 먼저 돌려 지난 실행이 남긴 것을 정리하고 시작합니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.