From 18d09d65a65350e2e49c8ecfe8c1a1fa9a0a99bc Mon Sep 17 00:00:00 2001 From: nre Date: Wed, 10 Oct 2018 13:58:40 +0200 Subject: [PATCH 1/2] Introduce new class: Pipenv This class is designed to facilitate running a Pipenv command against multiple versions of Python, which is something that Pipenv cannot do by itself very gracefully. We do this by using the `--python` argument to Pipenv, which assumes that the `Pipfile` does not contain a `requirements` section which may cause Pipenv to throw an error. --- src/com/ableton/Pipenv.groovy | 52 +++++++++++++++++++ test/com/ableton/PipenvTest.groovy | 81 ++++++++++++++++++++++++++++++ vars/pipenv.groovy | 13 +++++ 3 files changed, 146 insertions(+) create mode 100644 src/com/ableton/Pipenv.groovy create mode 100644 test/com/ableton/PipenvTest.groovy create mode 100644 vars/pipenv.groovy diff --git a/src/com/ableton/Pipenv.groovy b/src/com/ableton/Pipenv.groovy new file mode 100644 index 0000000..e5743ec --- /dev/null +++ b/src/com/ableton/Pipenv.groovy @@ -0,0 +1,52 @@ +package com.ableton + + +/** + * Provides an easy way to run a command with Pipenv using multiple Python versions. + */ +class Pipenv implements Serializable { + /** + * Script context. + * Required value, may not be null! + */ + @SuppressWarnings('FieldTypeRequired') + def script + + /** + * Run a closure with Pipenv using multiple versions of Python. Because the virtualenv + * created by Pipenv must be wiped out between runs, this function cannot be + * parallelized and therefore the commands are run serially for each Python version. + * + * This function also installs the development packages for each Python version (in + * other words, it runs {@code pipenv install --dev}. Also, it removes the virtualenv + * after the last Python version has been run. + * + * @param pythonVersions List of Python versions to run the command with. This argument + * is passed to Pipenv via {@code pipenv --python}. See + * {@code pipenv --help} for supported syntax. + * @param body Closure body to execute. The closure body is passed the Python version + * as an argument. + * @return Map of return values. The keys in the map correspond to the Python versions + * given in {@code args.pythonVersions}, and the values are the results of + * executing the closure body for each version. + */ + Map runWith(List pythonVersions, Closure body) { + assert script + assert pythonVersions + + Map results = [:] + + try { + pythonVersions.each { python -> + script.sh "pipenv install --dev --python ${python}" + results[python] = body(python) + } + } finally { + try { + script.sh 'pipenv --rm' + } catch (ignored) {} + } + + return results + } +} diff --git a/test/com/ableton/PipenvTest.groovy b/test/com/ableton/PipenvTest.groovy new file mode 100644 index 0000000..8ca99a1 --- /dev/null +++ b/test/com/ableton/PipenvTest.groovy @@ -0,0 +1,81 @@ +package com.ableton + +import static com.lesfurets.jenkins.unit.MethodCall.callArgsToString +import static org.junit.Assert.assertEquals +import static org.junit.Assert.assertNotNull +import static org.junit.Assert.assertTrue + +import com.lesfurets.jenkins.unit.BasePipelineTest +import org.junit.After +import org.junit.Before +import org.junit.Test + + +class PipenvTest extends BasePipelineTest { + @SuppressWarnings('FieldTypeRequired') + def script + + @Override + @Before + void setUp() throws Exception { + super.setUp() + + this.script = loadScript('test/resources/EmptyPipeline.groovy') + assertNotNull(script) + helper.registerAllowedMethod('sh', [String], JenkinsMocks.sh) + + JenkinsMocks.addShMock('pipenv --rm', '', 0) + } + + @After + void tearDown() { + JenkinsMocks.clearStaticData() + } + + @Test + void runWith() throws Exception { + List pythonVersions = ['2.7', '3.5'] + pythonVersions.each { python -> + JenkinsMocks.addShMock("pipenv install --dev --python ${python}", '', 0) + } + + int numCalls = 0 + Map result = new Pipenv(script: script).runWith(pythonVersions) { p -> + numCalls++ + return p + } + + // Ensure that pipenv install was called for each Python version + pythonVersions.each { python -> + assertTrue(helper.callStack.findAll { call -> + call.methodName == 'sh' + }.any { call -> + callArgsToString(call).contains("pipenv install --dev --python ${python}") + }) + } + + // Ensure that the closure body was evaluated for each Python version + assertEquals(pythonVersions.size(), numCalls) + pythonVersions.each { python -> + assert result[python] + assertEquals(python, result[python]) + } + + // Ensure that pipenv --rm was called + assertTrue(helper.callStack.findAll { call -> + call.methodName == 'sh' + }.any { call -> + callArgsToString(call).contains('pipenv --rm') + }) + } + + @Test(expected = AssertionError) + void runWithNoScript() throws Exception { + new Pipenv().runWith(['2.7']) {} + } + + @Test(expected = AssertionError) + void runWithEmptyPythonVersions() throws Exception { + new Pipenv(script: script).runWith([]) {} + } +} diff --git a/vars/pipenv.groovy b/vars/pipenv.groovy new file mode 100644 index 0000000..b3742de --- /dev/null +++ b/vars/pipenv.groovy @@ -0,0 +1,13 @@ +import com.ableton.Pipenv + + +/** + * Run a closure with Pipenv using multiple versions of Python. + * @param pythonVersions List of Python versions. + * @param body Closure body to execute. + * @return Map with return output or values for each Python version. + * @see com.ableton.Pipenv#runWith(List, Closure) + */ +Map runWith(List pythonVersions, Closure body) { + return new Pipenv(script: this).runWith(pythonVersions, body) +} From 3751f423445f83cd8f27c0199d04458a5005264f Mon Sep 17 00:00:00 2001 From: nre Date: Tue, 27 Nov 2018 08:38:44 +0100 Subject: [PATCH 2/2] Version 0.9.3 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 2003b63..965065d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.2 +0.9.3