diff --git a/.appveyor.yml b/.appveyor.yml index 6fd6f1dbe..6d70a76fa 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -2,6 +2,8 @@ version: 1.0.{build} image: Visual Studio 2017 environment: + GOPATH: c:\gopath + GOVERSION: 1.11 matrix: @@ -24,6 +26,15 @@ install: - "gem install bundler -v 1.17.3 --no-ri --no-rdoc" - "bundler --version" +# setup go +- rmdir c:\go /s /q +- "choco install golang" +- "choco install bzr" +- "choco install dep" +- setx PATH "C:\go\bin;C:\gopath\bin;C:\Program Files (x86)\Bazaar\;C:\Program Files\Mercurial;%PATH%;" +- "go version" +- "go env" + test_script: - "%PYTHON%\\python.exe -m pytest --cov aws_lambda_builders --cov-report term-missing tests/unit tests/functional" - "%PYTHON%\\python.exe -m pytest tests/integration" diff --git a/.gitignore b/.gitignore index 7fdec006e..130accaf0 100644 --- a/.gitignore +++ b/.gitignore @@ -383,4 +383,6 @@ $RECYCLE.BIN/ /Dockerfile -# End of https://www.gitignore.io/api/osx,node,macos,linux,python,windows,pycharm,intellij,sublimetext,visualstudiocode +tests/integration/workflows/go_dep/data/src/*/vendor/* + +# End of https://www.gitignore.io/api/osx,node,macos,linux,python,windows,pycharm,intellij,sublimetext,visualstudiocode \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index c22f0aebc..3eb314b9d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,12 @@ install: - nvm install 8.10.0 - nvm use 8.10.0 + # Go workflow integ tests require Go 1.11+ + - eval "$(gimme 1.11.2)" + - go version + + - go get -u github.com/golang/dep/cmd/dep + # Install the code requirements - make init script: diff --git a/aws_lambda_builders/workflows/__init__.py b/aws_lambda_builders/workflows/__init__.py index c531a1bc2..8e8425446 100644 --- a/aws_lambda_builders/workflows/__init__.py +++ b/aws_lambda_builders/workflows/__init__.py @@ -5,3 +5,4 @@ import aws_lambda_builders.workflows.python_pip import aws_lambda_builders.workflows.nodejs_npm import aws_lambda_builders.workflows.ruby_bundler +import aws_lambda_builders.workflows.go_dep diff --git a/aws_lambda_builders/workflows/go_dep/__init__.py b/aws_lambda_builders/workflows/go_dep/__init__.py new file mode 100644 index 000000000..1cd6ddb8f --- /dev/null +++ b/aws_lambda_builders/workflows/go_dep/__init__.py @@ -0,0 +1,5 @@ +""" +Builds Go Lambda functions using the `dep` dependency manager +""" + +from .workflow import GoDepWorkflow diff --git a/aws_lambda_builders/workflows/go_dep/actions.py b/aws_lambda_builders/workflows/go_dep/actions.py new file mode 100644 index 000000000..067aac58f --- /dev/null +++ b/aws_lambda_builders/workflows/go_dep/actions.py @@ -0,0 +1,66 @@ +""" +Actions for Go dependency resolution with dep +""" + +import logging +import os + +from aws_lambda_builders.actions import BaseAction, Purpose, ActionFailedError + +from .subproc_exec import ExecutionError + + +LOG = logging.getLogger(__name__) + +class DepEnsureAction(BaseAction): + + """ + A Lambda Builder Action which runs dep to install dependencies from Gopkg.toml + """ + + NAME = "DepEnsure" + DESCRIPTION = "Ensures all dependencies are installed for a project" + PURPOSE = Purpose.RESOLVE_DEPENDENCIES + + def __init__(self, base_dir, subprocess_dep): + super(DepEnsureAction, self).__init__() + + self.base_dir = base_dir + self.subprocess_dep = subprocess_dep + + def execute(self): + try: + self.subprocess_dep.run(["ensure"], + cwd=self.base_dir) + except ExecutionError as ex: + raise ActionFailedError(str(ex)) + +class GoBuildAction(BaseAction): + + """ + A Lambda Builder Action which runs `go build` to create a binary + """ + + NAME = "GoBuild" + DESCRIPTION = "Builds final binary" + PURPOSE = Purpose.COMPILE_SOURCE + + def __init__(self, base_dir, source_path, output_path, subprocess_go, env=None): + super(GoBuildAction, self).__init__() + + self.base_dir = base_dir + self.source_path = source_path + self.output_path = output_path + + self.subprocess_go = subprocess_go + self.env = env if not env is None else {} + + def execute(self): + env = self.env + env.update({"GOOS": "linux", "GOARCH": "amd64"}) + + try: + self.subprocess_go.run(["build", "-o", self.output_path, self.source_path], + cwd=self.source_path, env=env) + except ExecutionError as ex: + raise ActionFailedError(str(ex)) diff --git a/aws_lambda_builders/workflows/go_dep/subproc_exec.py b/aws_lambda_builders/workflows/go_dep/subproc_exec.py new file mode 100644 index 000000000..1bb9f9746 --- /dev/null +++ b/aws_lambda_builders/workflows/go_dep/subproc_exec.py @@ -0,0 +1,93 @@ +""" +Wrapper around calling dep through a subprocess. +""" + +import logging + +LOG = logging.getLogger(__name__) + + +class ExecutionError(Exception): + """ + Exception raised in case binary execution fails. + It will pass on the standard error output from the binary console. + """ + + MESSAGE = "Exec Failed: {}" + + def __init__(self, message): + raw_message = message + if isinstance(message, bytes): + message = message.decode('utf-8') + + try: + Exception.__init__(self, self.MESSAGE.format(message.strip())) + except UnicodeError: + Exception.__init__(self, self.MESSAGE.format(raw_message.strip())) + +class SubprocessExec(object): + + """ + Wrapper around the Dep command line utility, making it + easy to consume execution results. + """ + + def __init__(self, osutils, binary=None): + """ + :type osutils: aws_lambda_builders.workflows.go_dep.utils.OSUtils + :param osutils: An instance of OS Utilities for file manipulation + + :type binary: str + :param binary: Path to the binary. If not set, + the default executable path will be used + """ + self.osutils = osutils + + self.binary = binary + + + def run(self, args, cwd=None, env=None): + + """ + Runs the action. + + :type args: list + :param args: Command line arguments to pass to the binary + + :type cwd: str + :param cwd: Directory where to execute the command (defaults to current dir) + + :rtype: str + :return: text of the standard output from the command + + :raises aws_lambda_builders.workflows.go_dep.dep.ExecutionError: + when the command executes with a non-zero return code. The exception will + contain the text of the standard error output from the command. + + :raises ValueError: if arguments are not provided, or not a list + """ + + if not isinstance(args, list): + raise ValueError("args must be a list") + + if not args: + raise ValueError("requires at least one arg") + + invoke_bin = [self.binary] + args + + LOG.debug("executing binary: %s", invoke_bin) + + p = self.osutils.popen(invoke_bin, + stdout=self.osutils.pipe, + stderr=self.osutils.pipe, + cwd=cwd, + env=env) + + out, err = p.communicate() + + if p.returncode != 0: + raise ExecutionError(message=err) + + out = out.decode('utf-8') if isinstance(out, bytes) else out + + return out.strip() diff --git a/aws_lambda_builders/workflows/go_dep/utils.py b/aws_lambda_builders/workflows/go_dep/utils.py new file mode 100644 index 000000000..7c0d0052e --- /dev/null +++ b/aws_lambda_builders/workflows/go_dep/utils.py @@ -0,0 +1,42 @@ +""" +Commonly used utilities +""" + +import os +import platform +import tarfile +import subprocess + + +class OSUtils(object): + + """ + Wrapper around file system functions, to make it easy to + unit test actions in memory + + TODO: move to somewhere generic + """ + + def joinpath(self, *args): + return os.path.join(*args) + + def popen(self, command, stdout=None, stderr=None, env=None, cwd=None): + p = subprocess.Popen(command, stdout=stdout, stderr=stderr, env=env, cwd=cwd) + return p + + @property + def pipe(self): + return subprocess.PIPE + + @property + def environ(self): + return os.environ.copy() + + def dirname(self, path): + return os.path.dirname(path) + + def abspath(self, path): + return os.path.abspath(path) + + def is_windows(self): + return platform.system().lower() == 'windows' diff --git a/aws_lambda_builders/workflows/go_dep/workflow.py b/aws_lambda_builders/workflows/go_dep/workflow.py new file mode 100644 index 000000000..690f3548f --- /dev/null +++ b/aws_lambda_builders/workflows/go_dep/workflow.py @@ -0,0 +1,63 @@ +""" +Go Dep Workflow +""" + +import logging +import os + +from aws_lambda_builders.actions import CopySourceAction +from aws_lambda_builders.workflow import BaseWorkflow, Capability + +from .actions import DepEnsureAction, GoBuildAction +from .utils import OSUtils +from .subproc_exec import SubprocessExec + +LOG = logging.getLogger(__name__) + +class GoDepWorkflow(BaseWorkflow): + """ + A Lambda builder workflow that knows how to build + Go projects using `dep` + """ + + NAME = "GoDepBuilder" + + CAPABILITY = Capability(language="go", + dependency_manager="dep", + application_framework=None) + + EXCLUDED_FILES = (".aws-sam") + + def __init__(self, + source_dir, + artifacts_dir, + scratch_dir, + manifest_path, + runtime=None, + osutils=None, + **kwargs): + + super(GoDepWorkflow, self).__init__(source_dir, + artifacts_dir, + scratch_dir, + manifest_path, + runtime=runtime, + **kwargs) + + options = kwargs["options"] if "options" in kwargs else {} + handler = options.get("handler", None) + + if osutils is None: + osutils = OSUtils() + + # project base name, where the Gopkg.toml and vendor dir are. + base_dir = osutils.abspath(osutils.dirname(manifest_path)) + output_path = osutils.joinpath(osutils.abspath(artifacts_dir), handler) + + subprocess_dep = SubprocessExec(osutils, "dep") + subprocess_go = SubprocessExec(osutils, "go") + + self.actions = [ + DepEnsureAction(base_dir, subprocess_dep), + GoBuildAction(base_dir, osutils.abspath(source_dir), output_path, subprocess_go, env=osutils.environ) + ] diff --git a/tests/functional/workflows/go_dep/test_godep_utils.py b/tests/functional/workflows/go_dep/test_godep_utils.py new file mode 100644 index 000000000..e2d610ed6 --- /dev/null +++ b/tests/functional/workflows/go_dep/test_godep_utils.py @@ -0,0 +1,63 @@ +import os +import shutil +import sys +import tempfile + +from unittest import TestCase + +from aws_lambda_builders.workflows.go_dep import utils + + +class TestGoDepOSUtils(TestCase): + + def setUp(self): + + self.osutils = utils.OSUtils() + + def test_dirname_returns_directory_for_path(self): + dirname = self.osutils.dirname(sys.executable) + + self.assertEqual(dirname, os.path.dirname(sys.executable)) + + def test_abspath_returns_absolute_path(self): + + result = self.osutils.abspath('.') + + self.assertTrue(os.path.isabs(result)) + + self.assertEqual(result, os.path.abspath('.')) + + def test_joinpath_joins_path_components(self): + + result = self.osutils.joinpath('a', 'b', 'c') + + self.assertEqual(result, os.path.join('a', 'b', 'c')) + + def test_popen_runs_a_process_and_returns_outcome(self): + + cwd_py = os.path.join(os.path.dirname(__file__), '..', '..', 'testdata', 'cwd.py') + + p = self.osutils.popen([sys.executable, cwd_py], + stdout=self.osutils.pipe, + stderr=self.osutils.pipe) + + out, err = p.communicate() + + self.assertEqual(p.returncode, 0) + + self.assertEqual(out.decode('utf8').strip(), os.getcwd()) + + def test_popen_can_accept_cwd(self): + + testdata_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'testdata') + + p = self.osutils.popen([sys.executable, 'cwd.py'], + stdout=self.osutils.pipe, + stderr=self.osutils.pipe, + cwd=testdata_dir) + + out, err = p.communicate() + + self.assertEqual(p.returncode, 0) + + self.assertEqual(out.decode('utf8').strip(), os.path.abspath(testdata_dir)) diff --git a/tests/integration/workflows/go_dep/data/src/failed-remote/Gopkg.lock b/tests/integration/workflows/go_dep/data/src/failed-remote/Gopkg.lock new file mode 100644 index 000000000..5f571753d --- /dev/null +++ b/tests/integration/workflows/go_dep/data/src/failed-remote/Gopkg.lock @@ -0,0 +1,17 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:69b1cc331fca23d702bd72f860c6a647afd0aa9fcbc1d0659b1365e26546dd70" + name = "not-really-a-git-repo.com/pkg/log" + packages = ["."] + pruneopts = "UT" + revision = "bcd833dfe83d3cebad139e4a29ed79cb2318bf95" + version = "v1.0.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = ["not-really-a-git-repo.com/pkg/log"] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/tests/integration/workflows/go_dep/data/src/failed-remote/Gopkg.toml b/tests/integration/workflows/go_dep/data/src/failed-remote/Gopkg.toml new file mode 100644 index 000000000..ec6996018 --- /dev/null +++ b/tests/integration/workflows/go_dep/data/src/failed-remote/Gopkg.toml @@ -0,0 +1,7 @@ +[[constraint]] + name = "not-really-a-git-repo.com/pkg/log" + version = "1.0.0" + +[prune] + go-tests = true + unused-packages = true diff --git a/tests/integration/workflows/go_dep/data/src/failed-remote/main.go b/tests/integration/workflows/go_dep/data/src/failed-remote/main.go new file mode 100644 index 000000000..8a07b424c --- /dev/null +++ b/tests/integration/workflows/go_dep/data/src/failed-remote/main.go @@ -0,0 +1,7 @@ +package main + +import "not-really-a-git-repo.com/pkg/log" + +func main() { + log.Info("hello, world") +} diff --git a/tests/integration/workflows/go_dep/data/src/no-gopkg/main.go b/tests/integration/workflows/go_dep/data/src/no-gopkg/main.go new file mode 100644 index 000000000..635db7ae6 --- /dev/null +++ b/tests/integration/workflows/go_dep/data/src/no-gopkg/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("hello, world") +} diff --git a/tests/integration/workflows/go_dep/data/src/nodeps/Gopkg.lock b/tests/integration/workflows/go_dep/data/src/nodeps/Gopkg.lock new file mode 100644 index 000000000..10ef81118 --- /dev/null +++ b/tests/integration/workflows/go_dep/data/src/nodeps/Gopkg.lock @@ -0,0 +1,9 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/tests/integration/workflows/go_dep/data/src/nodeps/Gopkg.toml b/tests/integration/workflows/go_dep/data/src/nodeps/Gopkg.toml new file mode 100644 index 000000000..5c879c7de --- /dev/null +++ b/tests/integration/workflows/go_dep/data/src/nodeps/Gopkg.toml @@ -0,0 +1,3 @@ +[prune] + go-tests = true + unused-packages = true diff --git a/tests/integration/workflows/go_dep/data/src/nodeps/main.go b/tests/integration/workflows/go_dep/data/src/nodeps/main.go new file mode 100644 index 000000000..635db7ae6 --- /dev/null +++ b/tests/integration/workflows/go_dep/data/src/nodeps/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("hello, world") +} diff --git a/tests/integration/workflows/go_dep/data/src/remote-deps/Gopkg.lock b/tests/integration/workflows/go_dep/data/src/remote-deps/Gopkg.lock new file mode 100644 index 000000000..7cc815ae3 --- /dev/null +++ b/tests/integration/workflows/go_dep/data/src/remote-deps/Gopkg.lock @@ -0,0 +1,44 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:0a69a1c0db3591fcefb47f115b224592c8dfa4368b7ba9fae509d5e16cdc95c8" + name = "github.com/konsorten/go-windows-terminal-sequences" + packages = ["."] + pruneopts = "UT" + revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" + version = "v1.0.1" + +[[projects]] + digest = "1:69b1cc331fca23d702bd72f860c6a647afd0aa9fcbc1d0659b1365e26546dd70" + name = "github.com/sirupsen/logrus" + packages = ["."] + pruneopts = "UT" + revision = "bcd833dfe83d3cebad139e4a29ed79cb2318bf95" + version = "v1.2.0" + +[[projects]] + branch = "master" + digest = "1:38f553aff0273ad6f367cb0a0f8b6eecbaef8dc6cb8b50e57b6a81c1d5b1e332" + name = "golang.org/x/crypto" + packages = ["ssh/terminal"] + pruneopts = "UT" + revision = "505ab145d0a99da450461ae2c1a9f6cd10d1f447" + +[[projects]] + branch = "master" + digest = "1:10405139b45e3a97a3842c93984710e30466eb933545f219ad3f5e45246973b4" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows", + ] + pruneopts = "UT" + revision = "9a3f9b0469bbc6b8802087ae5c0af9f61502de01" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = ["github.com/sirupsen/logrus"] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/tests/integration/workflows/go_dep/data/src/remote-deps/Gopkg.toml b/tests/integration/workflows/go_dep/data/src/remote-deps/Gopkg.toml new file mode 100644 index 000000000..2dac9c39f --- /dev/null +++ b/tests/integration/workflows/go_dep/data/src/remote-deps/Gopkg.toml @@ -0,0 +1,7 @@ +[[constraint]] + name = "github.com/sirupsen/logrus" + version = "1.2.0" + +[prune] + go-tests = true + unused-packages = true diff --git a/tests/integration/workflows/go_dep/data/src/remote-deps/main.go b/tests/integration/workflows/go_dep/data/src/remote-deps/main.go new file mode 100644 index 000000000..3a65fc4e5 --- /dev/null +++ b/tests/integration/workflows/go_dep/data/src/remote-deps/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/sirupsen/logrus" + +func main() { + logrus.Info("hello, world") +} diff --git a/tests/integration/workflows/go_dep/test_go_dep.py b/tests/integration/workflows/go_dep/test_go_dep.py new file mode 100644 index 000000000..a379b3d58 --- /dev/null +++ b/tests/integration/workflows/go_dep/test_go_dep.py @@ -0,0 +1,82 @@ +import os +import shutil +import tempfile + +from unittest import TestCase + +from aws_lambda_builders.builder import LambdaBuilder +from aws_lambda_builders.exceptions import WorkflowFailedError + + +class TestGoDep(TestCase): + TEST_DATA_FOLDER = os.path.join(os.path.dirname(__file__), "data") + + def setUp(self): + self.artifacts_dir = tempfile.mkdtemp() + self.scratch_dir = tempfile.mkdtemp() + + os.environ["GOPATH"] = self.TEST_DATA_FOLDER + + self.no_deps = os.path.join(self.TEST_DATA_FOLDER, "src", "nodeps") + + self.builder = LambdaBuilder(language="go", + dependency_manager="dep", + application_framework=None) + + self.runtime = "go1.x" + + def tearDown(self): + shutil.rmtree(self.artifacts_dir) + shutil.rmtree(self.scratch_dir) + + def test_builds_project_with_no_deps(self): + source_dir = os.path.join(self.TEST_DATA_FOLDER, "src", "nodeps") + + self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir, + os.path.join(source_dir, "Gopkg.toml"), + runtime=self.runtime, + options={"handler": "main"}) + + expected_files = {"main"} + output_files = set(os.listdir(self.artifacts_dir)) + + self.assertEquals(expected_files, output_files) + + def test_builds_project_with_no_gopkg_file(self): + source_dir = os.path.join(self.TEST_DATA_FOLDER, "src", "no-gopkg") + + with self.assertRaises(WorkflowFailedError) as ex: + self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir, + os.path.join(source_dir, "Gopkg.toml"), + runtime=self.runtime, + options={"handler": "main"}) + + self.assertEquals( + "GoDepBuilder:DepEnsure - Exec Failed: could not find project Gopkg.toml," + + " use dep init to initiate a manifest", + str(ex.exception)) + + def test_builds_project_with_remote_deps(self): + source_dir = os.path.join(self.TEST_DATA_FOLDER, "src", "remote-deps") + + self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir, + os.path.join(source_dir, "Gopkg.toml"), + runtime=self.runtime, + options={"handler": "main"}) + + expected_files = {"main"} + output_files = set(os.listdir(self.artifacts_dir)) + + self.assertEquals(expected_files, output_files) + + def test_builds_project_with_failed_remote_deps(self): + source_dir = os.path.join(self.TEST_DATA_FOLDER, "src", "failed-remote") + + with self.assertRaises(WorkflowFailedError) as ex: + self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir, + os.path.join(source_dir, "Gopkg.toml"), + runtime=self.runtime, + options={"handler": "main"}) + + # The full message is super long, so part of it is fine. + self.assertNotEqual(str(ex.exception).find('unable to deduce repository and source type for'), -1) diff --git a/tests/unit/workflows/go_dep/__init__.py b/tests/unit/workflows/go_dep/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/workflows/go_dep/test_actions.py b/tests/unit/workflows/go_dep/test_actions.py new file mode 100644 index 000000000..fbefa8c18 --- /dev/null +++ b/tests/unit/workflows/go_dep/test_actions.py @@ -0,0 +1,69 @@ +from unittest import TestCase +from mock import patch + +from aws_lambda_builders.actions import ActionFailedError + +from aws_lambda_builders.workflows.go_dep.actions import DepEnsureAction, GoBuildAction +from aws_lambda_builders.workflows.go_dep.subproc_exec import ExecutionError + + +class TestDepEnsureAction(TestCase): + @patch("aws_lambda_builders.workflows.go_dep.subproc_exec.SubprocessExec") + def test_runs_dep_ensure(self, SubProcMock): + """ + tests the happy path of running `dep ensure` + """ + + sub_proc_dep = SubProcMock.return_value + action = DepEnsureAction("base", sub_proc_dep) + + action.execute() + + sub_proc_dep.run.assert_called_with(["ensure"], cwd="base") + + @patch("aws_lambda_builders.workflows.go_dep.subproc_exec.SubprocessExec") + def test_fails_dep_ensure(self, SubProcMock): + """ + tests failure, something being returned on stderr + """ + + sub_proc_dep = SubProcMock.return_value + sub_proc_dep.run.side_effect = ExecutionError(message="boom!") + action = DepEnsureAction("base", sub_proc_dep) + + with self.assertRaises(ActionFailedError) as raised: + action.execute() + + self.assertEqual(raised.exception.args[0], "Exec Failed: boom!") + + +class TestGoBuildAction(TestCase): + @patch("aws_lambda_builders.workflows.go_dep.subproc_exec.SubprocessExec") + def test_runs_go_build(self, SubProcMock): + """ + tests the happy path of running `dep ensure` + """ + + sub_proc_go = SubProcMock.return_value + action = GoBuildAction("base", "source", "output", sub_proc_go, env={}) + + action.execute() + + sub_proc_go.run.assert_called_with(["build", "-o", "output", "source"], + cwd="source", + env={"GOOS": "linux", "GOARCH": "amd64"}) + + @patch("aws_lambda_builders.workflows.go_dep.subproc_exec.SubprocessExec") + def test_fails_go_build(self, SubProcMock): + """ + tests failure, something being returned on stderr + """ + + sub_proc_go = SubProcMock.return_value + sub_proc_go.run.side_effect = ExecutionError(message="boom!") + action = GoBuildAction("base", "source", "output", sub_proc_go, env={}) + + with self.assertRaises(ActionFailedError) as raised: + action.execute() + + self.assertEqual(raised.exception.args[0], "Exec Failed: boom!") diff --git a/tests/unit/workflows/go_dep/test_exec.py b/tests/unit/workflows/go_dep/test_exec.py new file mode 100644 index 000000000..13a20f8aa --- /dev/null +++ b/tests/unit/workflows/go_dep/test_exec.py @@ -0,0 +1,72 @@ +from unittest import TestCase +from mock import patch + +from aws_lambda_builders.workflows.go_dep.subproc_exec import SubprocessExec, ExecutionError + + +class FakePopen: + def __init__(self, out=b"out", err=b"err", retcode=0): + self.out = out + self.err = err + self.returncode = retcode + + def communicate(self): + return self.out, self.err + + +class TestSubprocessExec(TestCase): + + @patch("aws_lambda_builders.workflows.go_dep.utils.OSUtils") + def setUp(self, OSUtilMock): + self.osutils = OSUtilMock.return_value + self.osutils.pipe = "PIPE" + self.popen = FakePopen() + self.osutils.popen.side_effect = [self.popen] + self.under_test = SubprocessExec(self.osutils, "bin") + + def test_run_executes_bin_on_nixes(self): + self.osutils.is_windows.side_effect = [False] + + self.under_test.run(["did", "thing"]) + + self.osutils.popen.assert_called_with(["bin", "did", "thing"], cwd=None, env=None, stderr="PIPE", stdout="PIPE") + + def test_uses_cwd_if_supplied(self): + self.under_test.run(["did", "thing"], cwd="/a/cwd") + + self.osutils.popen.assert_called_with(["bin", "did", "thing"], + cwd="/a/cwd", env=None, stderr="PIPE", stdout="PIPE") + + def test_uses_env_if_supplied(self): + self.under_test.run(["did", "thing"], env={"foo": "bar"}) + + self.osutils.popen.assert_called_with(["bin", "did", "thing"], + cwd=None, env={"foo": "bar"}, stderr="PIPE", stdout="PIPE") + + def test_returns_popen_out_decoded_if_retcode_is_0(self): + self.popen.out = "some encoded text\n\n" + + result = self.under_test.run(["did"]) + + self.assertEqual(result, "some encoded text") + + def test_raises_ExecutionError_with_err_text_if_retcode_is_not_0(self): + self.popen.returncode = 1 + self.popen.err = "some error text\n\n" + + with self.assertRaises(ExecutionError) as raised: + self.under_test.run(["did"]) + + self.assertEqual(raised.exception.args[0], "Exec Failed: some error text") + + def test_raises_ValueError_if_args_not_a_list(self): + with self.assertRaises(ValueError) as raised: + self.under_test.run(("pack")) + + self.assertEqual(raised.exception.args[0], "args must be a list") + + def test_raises_ValueError_if_args_empty(self): + with self.assertRaises(ValueError) as raised: + self.under_test.run([]) + + self.assertEqual(raised.exception.args[0], "requires at least one arg") diff --git a/tests/unit/workflows/go_dep/test_workflow.py b/tests/unit/workflows/go_dep/test_workflow.py new file mode 100644 index 000000000..3b5fcbddc --- /dev/null +++ b/tests/unit/workflows/go_dep/test_workflow.py @@ -0,0 +1,17 @@ +from unittest import TestCase + +from aws_lambda_builders.workflows.go_dep.workflow import GoDepWorkflow +from aws_lambda_builders.workflows.go_dep.actions import DepEnsureAction, GoBuildAction + + +class TestGoDepWorkflow(TestCase): + """ + The workflow requires an external tool, dep to run. It will need to be tested with integration + tests. These are just tests to provide quick feedback if anything breaks + """ + + def test_workflow_sets_up_workflow(self): + workflow = GoDepWorkflow("source", "artifacts", "scratch", "manifest", options={"handler": "foo"}) + self.assertEqual(len(workflow.actions), 2) + self.assertIsInstance(workflow.actions[0], DepEnsureAction) + self.assertIsInstance(workflow.actions[1], GoBuildAction)