For one of my apps I'd been manually testing some basic functions in a bunch of environments, and it was a huge pain. So I decided to automatize it. As a simplest solution I chose to run an environment in Docker and interact with them through pexpect.
First of all I tried to use docker-py, but it's almost impossible to interact with app run in Docker container, started from docker-py with pexpect. So I just used Docker binary:
fromcontextlibimportcontextmanagerimportsubprocessimportshutilfromtempfileimportmkdtempfrompathlibimportPathimportsysimportpexpect# Absolute path to your source root:root=str(Path(__file__).parent.parent.parent.resolve())def_build_container(tag,dockerfile):"""Creates a temporary folder with Dockerfile, builds an image and removes the folder."""tmpdir=mkdtemp()withPath(tmpdir).joinpath('Dockerfile').open('w')asfile:file.write(dockerfile)ifsubprocess.call(['docker','build','--tag={}'.format(tag),tmpdir],cwd=root)!=0:raiseException("Can't build a container")shutil.rmtree(tmpdir)@contextmanagerdefspawn(tag,dockerfile,cmd):"""Yields spawn object for `cmd` ran inside a Docker container with an image build with `tag` and `dockerfile`. Source root is available in `/src`."""_build_container(tag,dockerfile)proc=pexpect.spawnu('docker run --volume {}:/src --tty=true ''--interactive=true {} {}'.format(root,tag,cmd))proc.logfile=sys.stdouttry:yieldprocfinally:proc.terminate()
_build_container
is a bit tricky, but it's because Docker binary can build an image
only for file named Dockerfile
.
This code can be used for running something inside a Docker container very simple, code for printing content of your source root inside the container will be:
withspawn(u'ubuntu-test',u'FROM ubuntu:latest',u'bash')asproc:proc.sendline(u'ls /src')
Back to testing, if we want to test that some application can print version, you can easily write py.test test like this:
container=(u'ubuntu-python',u'''FROM ubuntu:latestRUN apt-get updateRUN apt-get install -yy python''')deftest_version():"""Ensure that app can print current version."""tag,dockerfile=containerwithspawn(tag,dockerfile,u'bash')asproc:proc.sendline(u'cd /src')proc.sendline(u'pip install .')proc.sendline(u'app --version')# Checks that `version:` is in the output:assertproc.expect([pexpect.TIMEOUT,u'version:'])
You can notice the strange assert proc.expect([pexpect.TIMEOUT, u'version:'])
construction,
it works very simple, if there's version:
in output, expect
returns 1
, if timeout
came first - 0
.
Also you can notice that all strings are in unicode (u''
), it's for compatibility
with Python 2. If you use only Python 3, you can remove all u''
.