Cerin
Cerin

Reputation: 64719

How to capture output from Python smtp debug server

I'm trying to write unittests for some custom Django email backends, and to test it against a "real" smtp server, I'm trying to use Python's built-in smtpd debugging server by running:

python -m smtpd -n -c DebuggingServer localhost:1025

My unittest basically looks like:

class Tests(TestCase):

    @override_settings(EMAIL_BACKEND='mycustombackend')
    @override_settings(EMAIL_HOST='localhost')
    @override_settings(EMAIL_PORT='1025')
    def test_backend(self):
        from django.core import mail
        mail.send_mail(
                subject='Subject here',
                message='Here is the message.',
                from_email='[email protected]',
                recipient_list=['[email protected]'],
                fail_silently=False,
            )

and when I run this, the smtpd process outputs the email content correctly.

However, when I try and capture that so I can confirm it in my unittest, I get nothing. I've tried using the subprocess package, to launch the process and read the output via pipes, but it never receives any output.

I thought I was using subprocess incorrectly, so as a last resort, I tried launching the process with:

python -m smtpd -n -c DebuggingServer localhost:1025 > /tmp/smtpd.log

and reading the log file. However, even with that, no output is ever written to the file.

What's going on here?

Upvotes: 6

Views: 7491

Answers (4)

ExoWanderer
ExoWanderer

Reputation: 74

When working with Flask, I set up a similar SMPTHandler for app.logger.

I found that the output of python -m smptd goes to the stderr (&2), instead of stdout (&1). Although there may be more elegant solutions, this worked for me:

python -m smtpd -n -c DebuggingServer 127.0.0.1:1025 2>&1 >> smtp_error.log

The command sends the stderr to stdout and then logs all output to the smpt_error.log.

Upvotes: 0

Frerich Raabe
Frerich Raabe

Reputation: 94299

I noticed the same symptom - for me, it helped to pass the -u argument to the Python interpreter, as in:

python -u -m smtpd -n -c DebuggingServer localhost:1025

The documentation says:

Force stdin, stdout and stderr to be totally unbuffered. On systems where it matters, also put stdin, stdout and stderr in binary mode.

Upvotes: 3

sudoqux
sudoqux

Reputation: 2568

According to this answer, output buffering is turned on when:

process STDOUT is redirected to something other than a terminal

The suggested solution would in this case be:

stdbuf -oL python -m smtpd -n -c DebuggingServer localhost:1025 > mail.log

Upvotes: 0

rodentrabies
rodentrabies

Reputation: 51

I had the same problem and spent 2 days trying to figure out what's going on. I tried running both

python -m smtpd -n -c DebuggingServer localhost:1025

and

python -m smtpd -n -c DebuggingServer localhost:1025 > mail.log

from one of my integration tests with subprocess, but it didn't work. While experimenting with it through REPL I noticed that first read from the pipe that is opened for us by subprocess hangs. After I kill it, next read actually returns the data. So I started investigating what's in the stream. But as I had no luck in 2 hours, I ended up rolling my own wrapper around SMTPServer that writes to the file, and got myself up and running.

Here is the wrapper class (process_message is an abstract method that is required of smtpd.SMTPServer's child class to be runnable by smtpd module):

# test_smtpd.py

import smtpd

SMTP_DUMPFILE = '/tmp/mail.log'

class SMTPTestServer(smtpd.SMTPServer):
    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
        with open(SMTP_DUMPFILE, 'w') as f:
            f.write(data)

I run it with

python -m smtpd -n -c test_smtpd.SMTPTestServer localhost:1025

While this does not directly answer your question, it is an easy workaround, so I hope this helps.

Upvotes: 3

Related Questions