Skip to content

Commit

Permalink
Implement local testing (iliana#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
Christopher MacGown committed Oct 20, 2017
1 parent efab795 commit be328b1
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 0 deletions.
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
PY2=python2.7
PY3=python3.6
PY3SETUP=yum install -q -y python36
IMAGE=amazonlinux:latest
DEST=/code

echo:
$(eval ENVFLAG := -e EXAMPLE=echo)

ec2-region:
$(eval ENVFLAG := -e EXAMPLE=ec2-region)

python2:
docker run -it $(ENVFLAG) -v $(shell pwd):$(DEST):ro $(IMAGE) $(PY2) $(DEST)/test_liblambda.py
python3:
docker run -it $(ENVFLAG) -v $(shell pwd):$(DEST):ro $(IMAGE) /bin/bash -c "$(PY3SETUP); $(PY3) $(DEST)/test_liblambda.py"

test-echo: echo python2 python3
test-ec2: ec2-region python2 python3


.PHONY: echo ec2-region test-echo test-ec2 python2 python3
194 changes: 194 additions & 0 deletions test_liblambda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Christopher MacGown
#
# Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
# http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
# http://opensource.org/licenses/MIT>, at your option. This file may not be
# copied, modified, or distributed except according to those terms.
#
#
# pylint: disable=missing-docstring

import contextlib
import fcntl
import os
import sys
import re
import time
import unittest

ERROR = None

try:
import liblambda
except ImportError as err:
ERROR = err
liblambda = None


@contextlib.contextmanager
def capture_stdout():
'''capture_stdout()
Store the stdout file descriptor and redirect it to the write-side of
the created pipe. We put the read-side into non-blocking mode so that
we can capture the output later.
This is only necessary because Rust writes directly to the stdout fd
and doing the typical sys.stdout song and dance in Python doesn't
capture things coming out of `liblambda`.
This is a context-manager and it yields the read-side of the pipe, so
it needs to be read and closed (for good housekeeping).
'''

# Setup pipe
read, write = os.pipe()
read, write = os.fdopen(read, 'rb'), os.fdopen(write, 'wb')

fcntl.fcntl(read, fcntl.F_SETFL, os.O_NONBLOCK)

fd = sys.stdout.fileno() # pylint: disable=invalid-name
with os.fdopen(os.dup(fd), 'wb') as copied:
sys.stdout.flush()
os.dup2(write.fileno(), fd) # redirect STDOUT -> the write FD.

try:
yield read
finally:
sys.stdout.flush()
os.dup2(copied.fileno(), fd) # redirect back.
write.close()


def now_in_millis():
return int(round(time.time() * 1000.0))


class FakeContext(object): # pylint: disable=too-few-public-methods,too-many-instance-attributes
def __init__(self, timeout=3000):
self._start = now_in_millis()
self._timeout = timeout

self.function_name = 'fake_function'
self.function_version = '$LATEST'
self.invoked_function_arn = 'arn:aws:lambda:XX-TEST-1:999999999999:function:fake_function' # pylint: disable=line-too-long
self.memory_limit_in_mb = '128'
self.aws_request_id = '1f8958d8-b20b-4a3c-b8fb-78896d10a9e5'
self.log_group_name = '/aws/lambda/fake_function'
# Date and coordinates of the Battle of Hastings - just for test data.
self.log_stream_name = '1066/10/14/[$LATEST]00000000000000000000505443002915' # pylint: disable=line-too-long

def get_remaining_time_in_millis(self):
return self._timeout - (now_in_millis() - self._start)


def consume(reader):
try:
return reader.read().decode(sys.stdout.encoding)
finally:
reader.close()

class TestCrowbar(unittest.TestCase):
def setUp(self):
self.context = FakeContext(timeout=100)

if not getattr(self, 'assertRegex', None):
# assertRegexpMatches is deprecated in 3.6, so make sure python2.7
# calls the method the same thing.
setattr(self, 'assertRegex', self.assertRegexpMatches)

def test_00_import_liblambda(self):
# This makes the import failure a little friendlier.
self.assertIsNotNone(liblambda, "Could not import liblambda: {}".format(ERROR))

@unittest.skipIf(liblambda is None, "Could not import liblambda")
@unittest.skipUnless(os.environ["EXAMPLE"] == "echo", "DISABLED")
def test_01_echo_short_timeout(self):
expectation = re.compile(r'hello cloudwatch logs from (?P<name>\w+) '
r'version (?P<version>\${0,1}\w+),'
r'.(?P<time>\d+) ms.*')

time.sleep(0.01)

with capture_stdout() as stdout:
self.assertEqual(liblambda.handler("echo", self.context), "echo")

output = consume(stdout)
matches = re.match(expectation, output)

self.assertRegex(output, expectation, "")
self.assertIn(int(matches.group('time')),
[88, 89, 90],
"{} not in [88, 89, 90]".format(matches.group('time')))

@unittest.skipIf(liblambda is None, "Could not import liblambda")
@unittest.skipUnless(os.environ["EXAMPLE"] == "ec2_regions", "DISABLED")
def test_01_ec2_regions_short_timeout(self): # pylint: disable=invalid-name
with capture_stdout() as stdout:
self.assertEqual(liblambda.handler("list-regions", self.context),
["US East (Ohio)",
"US East (N. Virginia)",
"US West (N. California)",
"US West (Oregon)",
"Asia Pacific (Mumbai)",
"Asia Pacific (Seoul)",
"Asia Pacific (Singapore)",
"Asia Pacific (Sydney)",
"Asia Pacific (Tokyo)",
"Canada (Central)",
"EU (Frankfurt)",
"EU (Ireland)",
"EU (London)",
"South America (São Paulo)",])
output = consume(stdout)
self.assertEqual(output, "", "Unexpected STDOUT output")

@unittest.skipIf(liblambda is None, "Could not import liblamba")
@unittest.skipUnless(os.environ["EXAMPLE"] == "echo", "DISABLED")
def test_02_echo_long_timeout(self):
# This test is a duplicate of test_01_echo, but with a longer deadline. Not necessarily
# the most exhaustive method of testing, but I wanted to show that.
expectation = re.compile(r'hello cloudwatch logs from (?P<name>\w+) '
r'version (?P<version>\${0,1}\w+),'
r'.(?P<time>\d+) ms.*')

context = FakeContext() # 3 seconds.
time.sleep(0.001)
with capture_stdout() as stdout:
self.assertEqual(liblambda.handler("echo", context), "echo")

output = consume(stdout)
matches = re.match(expectation, output)

self.assertRegex(output, expectation, "unexpected")
self.assertIn(int(matches.group('time')),
[2998, 2999, 3000],
"{} not in [2998, 2999, 3000]".format(matches.group('time')))

@unittest.skipIf(liblambda is None, "Could not import liblambda")
@unittest.skipUnless(os.environ["EXAMPLE"] == "ec2_regions", "DISABLED")
def test_01_ec2_regions_short_timeout(self): # pylint: disable=invalid-name
context = FakeContext() # 3 seconds.
time.sleep(0.001)
with capture_stdout() as stdout:
self.assertEqual(liblambda.handler("list-regions", context),
["US East (Ohio)",
"US East (N. Virginia)",
"US West (N. California)",
"US West (Oregon)",
"Asia Pacific (Mumbai)",
"Asia Pacific (Seoul)",
"Asia Pacific (Singapore)",
"Asia Pacific (Sydney)",
"Asia Pacific (Tokyo)",
"Canada (Central)",
"EU (Frankfurt)",
"EU (Ireland)",
"EU (London)",
"South America (São Paulo)",])
output = consume(stdout)
self.assertEqual(output, "", "Unexpected STDOUT output")

if __name__ == '__main__':
unittest.main()

0 comments on commit be328b1

Please sign in to comment.