Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get uwsgi to exit with return code of any failed sub-process

I'm writing some integration tests that involve a Python application running under uwsgi.
To test an aspect of this, I am running an uwsgi spooler, which requires that the master process is running.
If pytest has a failed test, it returns a non-zero exit code, which is great.
Without the master process, the entire uwsgi process also returns this exit code, and so our continuous integration server responds appropriately.
However, when the master process is running, it always exits with a zero exit code - regardless of failed tests.

I need it to pass on the first non-zero exit code of a subprocess if there is one.

Note: I'm not really interested in mocking this out - I need to test this working.

I've created a Dockerized Minimal, Complete, and Verifiable Example that illustrates my issue:

Dockerfile:

FROM python:3.6.4-slim-stretch

WORKDIR /srv

RUN apt-get update \
    && apt-get install -y build-essential \
    && pip install uwsgi pytest

COPY test_app.py /srv/

CMD ['/bin/bash']

test_app.py:

import pytest

def test_this():
    assert 1==0

Given the above 2 files in a directory, the following shows the return code if I run this failing test under uwsgi without the master process:

$ docker build -t=test .
$ docker run test uwsgi --chdir /srv --pyrun /usr/local/bin/pytest
...
============================= test session starts ==============================
platform linux -- Python 3.6.4, pytest-3.3.2, py-1.5.2, pluggy-0.6.0
rootdir: /srv, inifile:
collected 1 item

test_app.py F                                                            [100%]

=================================== FAILURES ===================================
__________________________________ test_this ___________________________________

    def test_this():
>       assert 1==0
E       assert 1 == 0

test_app.py:4: AssertionError
=========================== 1 failed in 0.05 seconds ===========================
$ echo $?
1

Note: you can see that the return code from this process (last line) is non-zero as required


Now, changing nothing other than running uwsgi with the master process, we get the following output:

$ docker run test uwsgi --set master=true --chdir /srv --pyrun /usr/local/bin/pytest
...
============================= test session starts ==============================
platform linux -- Python 3.6.4, pytest-3.3.2, py-1.5.2, pluggy-0.6.0
rootdir: /srv, inifile:
collected 1 item

test_app.py F                                                            [100%]

=================================== FAILURES ===================================
__________________________________ test_this ___________________________________

    def test_this():
>       assert 1==0
E       assert 1 == 0

test_app.py:4: AssertionError
=========================== 1 failed in 0.05 seconds ===========================
worker 1 buried after 0 seconds
goodbye to uWSGI.
$ echo $?
0

Note: this time the return code from this process (last line) is zero - even though the test failed

How can I get uwsgi to forward the exit code from a failing process to the master?

like image 482
Gerrat Avatar asked Jan 12 '18 20:01

Gerrat


1 Answers

This works, but feels a little hacky. I'll happily accept a better answer if one comes along.

I've made this work with the addition of two additional files (and a small update to the Dockerfile):

Dockerfile:

FROM python:3.6.4-slim-stretch

WORKDIR /srv

RUN apt-get update \
    && apt-get install -y build-essential \
    && pip install uwsgi pytest

COPY test_app.py test run_tests.py /srv/

CMD ['/bin/bash']

test:

#!/bin/bash
uwsgi --set master=true --chdir /srv --pyrun /srv/run_tests.py
exit $(cat /tmp/test_results)

run_tests.py:

#!/usr/bin/python

import re
import subprocess
import sys

from pytest import main


def write_result(retcode):

    path = r'/tmp/test_results'
    with open(path, 'w') as f:
        f.write(str(retcode))


def run():

    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    retcode = 1
    try:
        retcode = main()
    finally:
        write_result(retcode)

    sys.exit(retcode)


if __name__ == '__main__':
    run()

The way it works is that I've copied and tweaked the pytest program into run_tests.py, where it writes out the return code of the tests to a temporary file. The tests are run via a bash script: test, that runs uwsgi, which runs the tests, then exits the script with the return code from the tests.

Results now look like:

$ docker build -t=test .
$ docker run test /srv/test
...
============================= test session starts ==============================
platform linux -- Python 3.6.4, pytest-3.3.2, py-1.5.2, pluggy-0.6.0
rootdir: /srv, inifile:
collected 1 item

test_app.py F                                                            [100%]

=================================== FAILURES ===================================
__________________________________ test_this ___________________________________

    def test_this():
>       assert 1==0
E       assert 1 == 0

test_app.py:4: AssertionError
=========================== 1 failed in 0.05 seconds ===========================
worker 1 buried after 0 seconds
goodbye to uWSGI.
$ echo $?
1
like image 137
Gerrat Avatar answered Nov 15 '22 06:11

Gerrat