Unittest print does not work in docker compose

Hi all,

Issue type: unittest print does not work in docker compose

OS Version/build:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.6 LTS
Release:        20.04
Codename:       focal

App version:

$ /usr/bin/docker --version
Docker version 24.0.6, build ed223bc

Steps to reproduce:

Dockerfile code to run unittest:

RUN python -m unittest scan_3306_port.py

unittest Python code:

import unittest
import nmap

class TestPortScan(unittest.TestCase):
    def test_scan(self):
        print("Scanning local network for hosts...")
        nm = nmap.PortScanner()
        nm.scan(hosts='172.20.0.0/16', arguments='-sn -n')
        hosts = [host for host in nm.all_hosts() if nm[host].state() == 'up']
        print(f"Found {len(hosts)} hosts on the local network: {', '.join(hosts)}")
        self.assertGreater(len(hosts), 0, 'No hosts found on the local network')

        print("Scanning each host for open 3306 ports...")
        for host in hosts:
            nm.scan(hosts=host, arguments='-p 3306')
            if 'tcp' in nm[host].all_protocols() and 3306 in nm[host]['tcp']:
                print(f"Host {host} has open 3306 port")
                return  # Return success code immediately after finding one host with open 3306 port
            else:
                print(f"Host {host} does not have open 3306 port")

        print("No hosts with open 3306 port found.")
        self.fail("No hosts with open 3306 port found.")  # Fail the test if no hosts with open 3306 port are found

if __name__ == '__main__':
    print("Running port scan test...")
    unittest.main(exit=False)  # Prevent unittest from exiting after running the test

docker compose command:

$ /usr/bin/docker compose up --force-recreate

output related to unittest:

=> [server stage-0 32/36] RUN python -m unittest scan_3306_port.py                                                                         1981.6s

Please advise.

If you are building with buildkit which is the default builder in recent Docker versions, you won’t see the output when the command is finished. You can try

docker build --progress plain ...

And replace ... with your actual parameters

OR

docker compose build --progress plain

OR

export BUILDKIT_PROGRESS=plain
docker compose up --force-recreate

I found a way to achieve what I need:

$ /usr/bin/docker compose -f docker-compose-custom1.yml up --build
CMD /bin/bash -c 'python3 -m unittest /app/tests/check_mysql_host_availability_ping.py && \ 
    python3 /app/src/manage.py runserver 0.0.0.0:82'
import os
import subprocess
import time

# Get the MySQL host from the environment variable
mysql_host="179.30.0.10"
#mysql_host="172.20.0.5"
#mysql_host = os.getenv("MYSQL_HOST")

if not mysql_host:
    print("\n\n\nError! MYSQL_HOST environment variable is not set.\n\n\n")
    exit(1)

def is_mysql_host_available(host, timeout_seconds=360):
    start_time = time.time()
    
    while time.time() - start_time < timeout_seconds:
        try:
            subprocess.check_call(["ping", "-c", "1", host])
            return True
        except subprocess.CalledProcessError:
            pass  # Ping failed, continue looping
        
        # Sleep for a short duration before the next ping attempt
        time.sleep(1)

    return False

if is_mysql_host_available(mysql_host):
    subprocess.call(["/bin/bash", "-c", "echo 'MySQL host ping success.'"])
    print(f"\n\n\nSuccess! MySQL host '{mysql_host}' is available based on ping result.\n\n\n")
    exit(0)
else:
    print(f"\n\n\nError! MySQL host '{mysql_host}' is not available based on ping result.\n\n\n")
    exit(1)
core-web-1      | PING 179.30.0.10 (179.30.0.10) 56(84) bytes of data.
core-web-1      | 64 bytes from 179.30.0.10: icmp_seq=1 ttl=64 time=0.128 ms
core-web-1      | 
core-web-1      | --- 179.30.0.10 ping statistics ---
core-web-1      | 1 packets transmitted, 1 received, 0% packet loss, time 0ms
core-web-1      | rtt min/avg/max/mdev = 0.128/0.128/0.128/0.000 ms
core-web-1      | MySQL host ping success.
core-web-1      | 
core-web-1      | Success! MySQL host '179.30.0.10' is available based on ping result.
core-web-1      | 
core-web-1      | 

@rimelek FYI

Great, thanky ou for your feedback. However, you should at least use the following syntax using the “exec” keyword:

CMD ["/bin/bash", "-c", "python3 -m unittest /app/tests/check_mysql_host_availability_ping.py && \ 
    exec python3 /app/src/manage.py runserver 0.0.0.0:82"]

So you don’t run a bash in another shell and execute a new child process in bash. When the value of the CMD is a string that is the SHELL form. So you could have done this too if the default shell is enough:

CMD "python3 -m unittest /app/tests/check_mysql_host_availability_ping.py && \ 
    exec python3 /app/src/manage.py runserver 0.0.0.0:82"

Of course, the cleanest solution would be creating a shell script and specifing that shell script as CMD.

Note that I haven’T tried any of the above commands, so it is possible that I made a mistake somewhere. My main point is using the exec keyword and usually avoiding the SHELL form in CMD so you can stop the container properly.

More detauls: Constructing commands to run in Docker containers - DEV Community

And finally: Are you sure you want to run the tests every time you start the container? If you are, then you indeed found a solution.