Skip to content

Commit

Permalink
Introduce new class: Pipenv
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nre-ableton committed Oct 26, 2018
1 parent 95f1f8c commit 78cebe4
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 0 deletions.
52 changes: 52 additions & 0 deletions src/com/ableton/Pipenv.groovy
Original file line number Diff line number Diff line change
@@ -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.
* <strong>Required value, may not be null!</strong>
*/
@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 result of
* executing the closure body.
*/
Map runWith(List pythonVersions, Closure body) {
assert script
assert pythonVersions

Map result = [:]

try {
pythonVersions.each { python ->
script.sh "pipenv install --dev --python ${python}"
result[python] = body(python)
}
} finally {
try {
script.sh 'pipenv --rm'
} catch (ignored) {}
}

return result
}
}
81 changes: 81 additions & 0 deletions test/com/ableton/PipenvTest.groovy
Original file line number Diff line number Diff line change
@@ -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([]) {}
}
}
13 changes: 13 additions & 0 deletions vars/pipenv.groovy
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit 78cebe4

Please sign in to comment.