Skip to content
This repository has been archived by the owner on Jun 13, 2019. It is now read-only.

Commit

Permalink
Implement local testing (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
Christopher MacGown committed Oct 25, 2017
1 parent 335a257 commit 148b3c3
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
target
Cargo.lock
*.swp
test/*.so
test/*.zip
5 changes: 5 additions & 0 deletions test/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include Makefile.local
include Makefile.docker

list:
@$(MAKE) -pRrq -f $(MAKEFILE_LIST) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make/ { if ($$1 !~ "^[#.]") { print $$1 }}' | sort
28 changes: 28 additions & 0 deletions test/Makefile.docker
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
PY2=python2.7
PY3=python3.6
PY3SETUP=yum install -q -y python36
IMAGE=amazonlinux:latest
DEST := /code
VOLUME := -v $(shell pwd):$(DEST):ro
DOCKER := docker run -it
BUILDER := ilianaw/crowbar-builder

%.zip:
docker run --rm -a stderr -a stdout -v $(shell pwd)/../:$(DEST):ro --entrypoint "/bin/bash" $(BUILDER) -c "cd examples/$*; build.sh" > $@

build-ec2-regions: ec2-regions.zip
@$(eval ENVFLAG := -e EXAMPLE=ec2_regions)
unzip $<
build-echo: echo.zip
@$(eval ENVFLAG := -e EXAMPLE=echo)
unzip $<

test-docker:
-$(DOCKER) $(ENVFLAG) $(VOLUME) $(IMAGE) $(PY2) $(DEST)/test_liblambda.py
-$(DOCKER) $(ENVFLAG) $(VOLUME) $(IMAGE) /bin/bash -c "$(PY3SETUP); $(PY3) $(DEST)/test_liblambda.py"
-rm liblambda.so

test-all-docker:
@for target in $(EXAMPLES); do $(MAKE) build-$$target test-docker; done

.PHONY: build-echo build-ec2-regions test-docker test-all-docker
42 changes: 42 additions & 0 deletions test/Makefile.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
LIBLAMBDA := liblambda.so
EXPECTED := so
EXAMPLES := echo ec2-regions

ifeq ($(shell uname -s),Darwin)
SUFFIX := dylib
else
SUFFIX := EXPECTED
endif

%.$(EXPECTED):
@cd ../examples/$(subst .$(EXPECTED),,$@) && cargo build --release
@cp ../examples/$(subst .$(EXPECTED),,$@)/target/release/liblambda.$(SUFFIX) $@

echo: echo.$(EXPECTED)
@echo "Testing the echo example:"
@echo "==============================================================================="
@echo
-@unlink $(LIBLAMBDA) 2> /dev/null || true
@$(eval ENVFLAG := EXAMPLE=echo)
@ln -s echo.$(EXPECTED) $(LIBLAMBDA)

ec2-regions: ec2-regions.$(EXPECTED)
@echo "Testing the ec2-region example:"
@echo "==============================================================================="
@echo
-@unlink $(LIBLAMBDA) 2> /dev/null || true
@$(eval ENVFLAG := EXAMPLE=ec2_regions)
@ln -s ec2-regions.$(EXPECTED) $(LIBLAMBDA)

test-local:
@$(ENVFLAG) python2 test_liblambda.py
@$(ENVFLAG) python3 test_liblambda.py
-@unlink $(LIBLAMBDA)
-@echo
-@echo
-@echo

test-all-local:
@for target in $(EXAMPLES); do $(MAKE) $$target test-local; done

.PHONY: echo ec2-regions test-local test-all-local
22 changes: 22 additions & 0 deletions test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# crowbar tests

In order to test the ec2-regions example, you'll need to export your AWS credentials as environment
variables or create a credentials file at `~/.aws/credentials` formatted like so:


[default]
aws_access_key_id=<YOUR AWS ACCESS ID>
aws_secret_access_key=<YOUR AWS ACCESS KEY SECRET>


The easiest way to test is to use the `test-all-local` or `test-all-docker` Makefile targets. However,
you can use any of the below to test one or more of the examples locally or built with the builder.


make echo test-local # Locally build and test the 'echo' example.
make ec2-regions test-local # Locally build and test the 'ec2-regions' example.
make test-all-local # Locally build and test both examples.

make build-echo test-docker # Build and test the 'echo' example against amazonlinux:latest
make build-ec2-regions test-docker # Build and test the 'ec2-regions' example against amazonlinux:latest
make test-all-docker # Build and test both examples against amazonlinux:latest
198 changes: 198 additions & 0 deletions test/test_liblambda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# -*- 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): # pylint: disable=no-self-use
# This makes the import failure a little friendlier.
if liblambda is None:
print("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')),
[87, 88, 89, 90],
"{} not in [87, 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
os.environ["AWS_DEFAULT_REGION"] = "us-east-1"

with capture_stdout() as stdout:
self.assertEqual(liblambda.handler("list-regions", self.context),
['ap-south-1',
'eu-west-2',
'eu-west-1',
'ap-northeast-2',
'ap-northeast-1',
'sa-east-1',
'ca-central-1',
'ap-southeast-1',
'ap-southeast-2',
'eu-central-1',
'us-east-1',
'us-east-2',
'us-west-1',
'us-west-2',])
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_02_ec2_regions_long_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),
['ap-south-1',
'eu-west-2',
'eu-west-1',
'ap-northeast-2',
'ap-northeast-1',
'sa-east-1',
'ca-central-1',
'ap-southeast-1',
'ap-southeast-2',
'eu-central-1',
'us-east-1',
'us-east-2',
'us-west-1',
'us-west-2',])
output = consume(stdout)
self.assertEqual(output, "", "Unexpected STDOUT output")

if __name__ == '__main__':

unittest.main()

0 comments on commit 148b3c3

Please sign in to comment.