Commit b9f53fc3 authored by Jérome Perrin's avatar Jérome Perrin

slapos-testing: use buildout to install eggs

Instead of having running test installing eggs with setuptools during `python setup.py test`, which fails for `caucase` because one of its dependency (namely `cryptography`) cannot be installed so easily, we want to install the dependencies via buildout, using the same installation methods than in the actual software profiles using our eggs.

What I initially believed would be a small change turned out to be a long journey, especially because of using a develop version of `slapos.recipe.cmmi` caused signature changes in the parts installed by this recipe ( `git`, `openssl` etc - some parts that takes a bit of time to install ) so I had to fight with the software being reinstalled each time. Even though the cases which leads to reinstallation are understood (this even involved fixing a bug in buildout slapos.buildout!14 ), this is still the case. Some solutions are proposed in the commit message of f4b6eeda , but this reached a state where we can consider first merging with this known problem or discuss ways of improving that before merging if it's considered as blocking. Current state is that it takes one hour to re-install what has to be reinstalled and run the test suite on test runner.

Despite this issue, this approach already improve things, because:
 * `caucase` tests are running (and passing)
 * some `slapos.cookbook` are improved so that they reuse eggs from the buildout and do not install again eggs when the are run.
 *  ... and some other small cleanups

This depends on erp5!619 on the `erp5.util` side.

/reviewed-on !309
parents d7a7b6a5 f8df048c
......@@ -99,7 +99,6 @@ setup(name=name,
'dropbear.add_authorized_key = slapos.recipe.dropbear:AddAuthorizedKey',
'dropbear.client = slapos.recipe.dropbear:Client',
'duplicity = slapos.recipe.duplicity:Recipe',
'egg_test = slapos.recipe.erp5_test:EggTestRecipe',
'equeue = slapos.recipe.equeue:Recipe',
'erp5.promise = slapos.recipe.erp5_promise:Recipe',
'erp5.test = slapos.recipe.erp5_test:Recipe',
......
......@@ -113,26 +113,3 @@ class CloudoooRecipe(GenericBaseRecipe):
return path_list
class EggTestRecipe(GenericBaseRecipe):
"""
Recipe used to create wrapper used to run test suite (python setup.py test)
off a list of Python eggs.
"""
def install(self):
test_list = self.options['test-list'].strip().replace('\n', ',')
common_dict = {}
if self.options.get('environment'):
environment_part = self.buildout.get(self.options['environment'])
if environment_part:
common_dict['environment'] = dict(environment_part)
if 'prepend-path' in self.options:
common_dict['prepend_path'] = self.options['prepend-path']
return self.createPythonScript(
self.options['run-test-suite'], __name__ + '.test.runTestSuite',
((self.options['run-test-suite-binary'],
"--source_code_path_list", test_list),
common_dict)
)
......@@ -4,8 +4,6 @@ import unittest
from mock import patch
from slapos.recipe import free_port
class SocketMock():
def __init__(self, *args, **kw):
self.args = args
......@@ -28,31 +26,16 @@ class FreePortTest(unittest.TestCase):
SocketMock.bind = SocketMock.close = SocketMock.nothing_happen
def new_recipe(self, **kw):
buildout = {
'buildout': {
'bin-directory': '',
'find-links': '',
'allow-hosts': '',
'develop-eggs-directory': '',
'eggs-directory': '',
'python': 'testpython',
'installed': '.installed.cfg',
},
'testpython': {
'executable': sys.executable,
},
'slap-connection': {
'computer-id': '',
'partition-id': '',
'server-url': '',
'software-release-url': '',
}
}
from slapos.recipe import free_port
from slapos.test.utils import makeRecipe
options = {
'ip': '127.0.0.1',
}
options.update(kw)
return free_port.Recipe(buildout=buildout, name='free_port', options=options)
return makeRecipe(
free_port.Recipe,
options=options,
name='free_port')
@useMock
def test_ifNoBusyPortThenMinPortIsAlwaysReturned(self):
......
......@@ -4,30 +4,15 @@ import unittest
from tempfile import mkdtemp
from shutil import rmtree
from slapos.recipe import generic_cloudooo
class TestGenericCloudooo(unittest.TestCase):
def new_recipe(self, options):
buildout = {
'buildout': {
'bin-directory': '',
'find-links': '',
'allow-hosts': '',
'develop-eggs-directory': '',
'eggs-directory': '',
'python': 'testpython',
},
'testpython': {
'executable': sys.executable,
},
'slap-connection': {
'computer-id': '',
'partition-id': '',
'server-url': '',
'software-release-url': '',
}
}
return generic_cloudooo.Recipe(buildout=buildout, name='generic_cloudooo', options=options)
from slapos.recipe import generic_cloudooo
from slapos.test.utils import makeRecipe
return makeRecipe(
generic_cloudooo.Recipe,
options=options,
name='generic_cloudooo')
def setUp(self):
self.test_dir = mkdtemp()
......
......@@ -5,37 +5,19 @@ import sys
import tempfile
import unittest
from slapos.recipe import pbs
class PBSTest(unittest.TestCase):
def new_recipe(self):
buildout = {
'buildout': {
'bin-directory': '',
'find-links': '',
'allow-hosts': '',
'develop-eggs-directory': '',
'eggs-directory': '',
'python': 'testpython',
},
'testpython': {
'executable': sys.executable,
},
'slap-connection': {
'computer-id': '',
'partition-id': '',
'server-url': '',
'software-release-url': '',
}
}
from slapos.recipe import pbs
from slapos.test.utils import makeRecipe
options = {
'rdiffbackup-binary': ''
}
return pbs.Recipe(buildout=buildout, name='pbs', options=options)
return makeRecipe(
pbs.Recipe,
options=options,
name='pbs')
def test_push(self):
recipe = self.new_recipe()
......
......@@ -6,8 +6,6 @@ import tempfile
import unittest
from slapos.slap.slap import NotFoundError, ConnectionError
from slapos.recipe import re6stnet
class Re6stnetTest(unittest.TestCase):
......@@ -47,31 +45,21 @@ class Re6stnetTest(unittest.TestCase):
shutil.rmtree(path)
def new_recipe(self):
buildout = {
'buildout': {
'bin-directory': '',
'find-links': '',
'allow-hosts': '',
'develop-eggs-directory': '',
'eggs-directory': '',
'python': 'testpython',
},
'testpython': {
'executable': sys.executable,
},
'slap-connection': {
from slapos.recipe import re6stnet
from slapos.test.utils import makeRecipe
return makeRecipe(
re6stnet.Recipe,
options=self.options,
slap_connection={
'computer-id': 'comp-test',
'partition-id': 'slappart0',
'server-url': 'http://server.com',
'software-release-url': 'http://software.com',
'key-file': '/path/to/key',
'cert-file': '/path/to/cert'
}
}
options = self.options
},
name='re6stnet')
return re6stnet.Recipe(buildout=buildout, name='re6stnet', options=options)
def checkWrapper(self, path):
self.assertTrue(os.path.exists(path))
......
"""Test helpers
"""
import sys
import os.path
from ConfigParser import ConfigParser
import logging
def makeRecipe(recipe_class, options, name='test', slap_connection=None):
"""Instanciate a recipe of `recipe_class` with `options` with a buildout
mapping containing a python and an empty `slapos-connection` mapping, unless
provided as `slap_connection`.
If running tests in a buildout folder, the test recipe will reuse the
`eggs-directory` and `develop-eggs-directory` from this buildout so that the
test recipe does not need to install eggs again when using working set.
To prevent test accidentally writing to the buildout's eggs repositories, we
set `newest` to false and `offline` to true in this case.
"""
buildout = {
'buildout': {
'bin-directory': '',
'find-links': '',
'allow-hosts': '',
'develop-eggs-directory': '',
'eggs-directory': '',
'python': 'testpython',
},
'testpython': {
'executable': sys.executable,
},
'slap-connection': {
'computer-id': '',
'partition-id': '',
'server-url': '',
'software-release-url': '',
}
}
if slap_connection is not None:
buildout['slap-connection'] = slap_connection
# are we in buildout folder ?
# the usual layout is
# ${buildout:directory}/parts/slapos-repository/slapos/test/utils.py , so try
# to find a buildout relative to this file.
buildout_cfg = os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', 'buildout.cfg')
if os.path.exists(buildout_cfg):
parser = ConfigParser()
parser.readfp(open(buildout_cfg))
eggs_directory = parser.get('buildout', 'eggs-directory')
develop_eggs_directory = parser.get('buildout', 'develop-eggs-directory')
logging.getLogger(__name__).info(
'Using eggs-directory (%s) and develop-eggs-directory (%s) from buildout at %s',
eggs_directory,
develop_eggs_directory,
buildout_cfg)
buildout['buildout']['eggs-directory'] = eggs_directory
buildout['buildout']['develop-eggs-directory'] = develop_eggs_directory
buildout['buildout']['newest'] = False
buildout['buildout']['offline'] = True
return recipe_class(buildout=buildout, name=name, options=options)
# Slapos egg tests
This software release is used to run unit test of slapos eggs.
The approach is to use setuptools' integrated test runner, `python setup.py test`, to run tests.
The `python` used in this command will be a `zc.recipe.egg` interpreter with
all eggs pre-installed by this software release.
Nexedi staff can see the results of this test from the test suite
`SLAPOS-EGG-TEST` in test result module.
Here's an example session of how a developer could use this software release in
slaprunner to develop a slapos egg, in the example `slapos.core`, to make
changes to the code, run tests and publish changes.
```bash
# install this software release
SR=https://lab.nexedi.com/nexedi/slapos/raw/master/software/slapos-testing/software.cfg
COMP=slaprunner
INSTANCE_NAME=$COMP
slapos supply $SR $COMP
slapos node software
slapos request --node=node=$COMP $INSTANCE_NAME $SR
slapos node instance
# The source code is a git clone working copy on the instance
cd ~/srv/runner/instance/slappart0/parts/slapos.core/
# make some changes to the code
vim slapos/tests/client.py
# run tests, using bundled python intepreter with pre-installed eggs dependencies
~/srv/runner/instance/slappart0/software_release/bin/python_for_test setup.py build
# when satified, commit changes
git add -p && git commit
# add developer's fork remote (this is only needed the first time)
git remote add my_remote https://lab.nexedi.com/your_username/slapos.core.git/
# push the changes
git push my_remote HEAD:feature_branch_name
# then submit merge request
```
# THIS IS NOT A BUILDOUT FILE, despite purposedly using a compatible syntax.
# The only allowed lines here are (regexes):
# - "^#" comments, copied verbatim
# - "^[" section beginings, copied verbatim
# - lines containing an "=" sign which must fit in the following categorie.
# - "^\s*filename\s*=\s*path\s*$" where "path" is relative to this file
# But avoid directories, they are not portable.
# Copied verbatim.
# - "^\s*hashtype\s*=.*" where "hashtype" is one of the values supported
# by the re-generation script.
# Re-generated.
# - other lines are copied verbatim
# Substitution (${...:...}), extension ([buildout] extends = ...) and
# section inheritance (< = ...) are NOT supported (but you should really
# not need these here).
[template]
filename = instance.cfg
md5sum = 9dece9d12dc94bf5c35d307cc8aa4d6b
[buildout]
parts =
slapos.core-setup
erp5.util-setup
phantomjs-wrapper
slapos-test-runner
sh-environment
eggs-directory = ${buildout:eggs-directory}
develop-eggs-directory = ${buildout:develop-eggs-directory}
......@@ -24,33 +21,26 @@ bin = $${buildout:directory}/bin
etc = $${buildout:directory}/etc
services = $${:etc}/run
srv = $${buildout:directory}/srv
source-code = $${:srv}/eggs-source-code
[download-source]
recipe = slapos.recipe.build:gitclone
git-executable = ${git:location}/bin/git
# Local development
[slapos.core]
<= download-source
repository = ${slapos.core-repository:location}
[slapos.core-setup]
recipe = plone.recipe.command
command = echo "Updating setup...";cd $${slapos.core:location}; export PATH="$${slapos-test-runner:prepend-path}:$PATH"; export CPPFLAGS="$${environment:CPPFLAGS}"; export LDFLAGS="$${environment:LDFLAGS}"; export PYTHONPATH="$${environment:PYTHONPATH}"; export LOCAL_IPV4="$${environment:LOCAL_IPV4}"; ${python2.7:location}/bin/python setup.py test -n; ${python2.7:location}/bin/python setup.py test -n; ${python2.7:location}/bin/python setup.py test -n; ${python2.7:location}/bin/python setup.py test -n
update-command = $${:command}
[caucase]
<= download-source
repository = ${caucase-repository:location}
[erp5.util]
<= download-source
repository = ${erp5.util-repository:location}
[slapos.cookbook]
<= download-source
repository = ${slapos.cookbook-repository:location}
[slapos.recipe.template]
[slapos.core]
<= download-source
repository = ${slapos.recipe.template-repository:location}
repository = ${slapos.core-repository:location}
[slapos.recipe.build]
<= download-source
......@@ -60,57 +50,33 @@ repository = ${slapos.recipe.build-repository:location}
<= download-source
repository = ${slapos.recipe.cmmi-repository:location}
[slapos.toolbox]
[slapos.recipe.template]
<= download-source
repository = ${slapos.toolbox-repository:location}
repository = ${slapos.recipe.template-repository:location}
[erp5-util]
[slapos.toolbox]
<= download-source
repository = ${erp5-util-repository:location}
repository = ${slapos.toolbox-repository:location}
[erp5.util-setup]
recipe = plone.recipe.command
command = echo "Updating setup...";cd $${erp5-util:location}; export PATH="$${slapos-test-runner:prepend-path}:$PATH"; export CPPFLAGS="$${environment:CPPFLAGS}"; export LDFLAGS="$${environment:LDFLAGS}"; export PYTHONPATH="$${environment:PYTHONPATH}"; ${python2.7:location}/bin/python setup.py test -n; ${python2.7:location}/bin/python setup.py test -n; ${python2.7:location}/bin/python setup.py test -n; ${python2.7:location}/bin/python setup.py test -n; ${python2.7:location}/bin/python setup.py test -n
update-command = $${:command}
[slapos-test-runner]
recipe = slapos.cookbook:egg_test
run-test-suite = $${create-directory:bin}/runTestSuite
run-test-suite-binary = ${buildout:bin-directory}/runTestSuite
# The list of executables should be defined here and a combination
# of tests should dynamically generated.
#python-list = $${}
test-list =
$${slapos.cookbook:location}
$${slapos.core:location}
$${slapos.recipe.template:location}
$${slapos.recipe.build:location}
$${slapos.recipe.cmmi:location}
$${slapos.toolbox:location}
$${erp5-util:location}
$${caucase:location}
prepend-path = ${curl:location}/bin:${openssl:location}/bin:${git:location}/bin:${libxslt:location}/bin:${python2.7:location}/bin
environment = environment
[environment]
CPPFLAGS = -I${python2.7:location}/include/python2.7 -I${libxml2:location}/include -I${libxslt:location}/include
LDFLAGS = -L${python2.7:location}/lib -L${libxml2:location}/lib -L${libxslt:location}/lib -L${libxslt:location}/lib -L${zlib:location}/lib -Wl,-rpath=${zlib:location}/lib -Wl,-rpath=${python2.7:location}/lib -Wl,-rpath=${libxml2:location}/lib -Wl,-rpath=${libxslt:location}/lib -Wl,-rpath=${zlib:location}/lib
LD_LIBRARY_PATH = ${python2.7:location}/lib:${libxml2:location}/lib:${libxslt:location}/lib:${libxslt:location}/lib:${zlib:location}/lib
PYTHONPATH = ${python-setuptools:pythonpath}:${buildout:eggs-directory}:${buildout:develop-eggs-directory}
LOCAL_IPV4 = $${slap-configuration:ipv4-random}
[sh-environment]
# Section exposes testing default environment as sh file. It is thus easy
# to directly develop and test the egg inside of this instance.
recipe = collective.recipe.template
input = inline:
export PATH="$${slapos-test-runner:prepend-path}:$PATH"
export CPPFLAGS="$${environment:CPPFLAGS}"
export LDFLAGS="$${environment:LDFLAGS}"
export PYTHONPATH="$${environment:PYTHONPATH}"
export PS1="[slapos-testing env Active] $PS1"
output = $${create-directory:bin}/environment.sh
mode = 755
recipe = slapos.cookbook:wrapper
wrapper-path = $${create-directory:bin}/runTestSuite
command-line =
${buildout:bin-directory}/runTestSuite
--python_interpreter=${buildout:bin-directory}/${eggs:interpreter}
--source_code_path_list=$${caucase:location},$${erp5.util:location},$${slapos.cookbook:location},$${slapos.core:location},$${slapos.recipe.build:location},$${slapos.recipe.cmmi:location},$${slapos.recipe.template:location},$${slapos.toolbox:location}
# Notes about environment:
# * slapos.cookbook:wrapper does not seem to allow "extending" PATH. Tests
# needs ping, which is a setuid binary that cannot be installed via slapos
# way of building software without root access, so we keep "standard"
# /usr/bin and /bin in $PATH
# * LOCAL_IPV4 is needed for some slapos.core tests
environment =
PATH=${coreutils:location}/bin:${curl:location}/bin:${openssl:location}/bin:${git:location}/bin:${libxslt:location}/bin:/usr/bin/:/bin/
LOCAL_IPV4=$${slap-configuration:ipv4-random}
[phantomjs-wrapper]
recipe = slapos.cookbook:wrapper
......
......@@ -6,26 +6,78 @@ extends =
../../component/libxml2/buildout.cfg
../../component/libxslt/buildout.cfg
../../component/bcrypt/buildout.cfg
../../component/python-2.7/buildout.cfg
../../component/python-setuptools/buildout.cfg
../../component/zlib/buildout.cfg
../../component/phantomjs/buildout.cfg
../../component/pycurl/buildout.cfg
../../component/coreutils/buildout.cfg
../../stack/slapos.cfg
./buildout.hash.cfg
parts =
caucase-repository
slapos.cookbook-repository
slapos.core-repository
slapos.recipe.template-repository
slapos.recipe.build-repository
slapos.recipe.cmmi-repository
slapos.toolbox-repository
erp5-util-repository
bootstrap-slapos.recipe.cmmi
eggs
phantomjs
template
[bootstrap-slapos.recipe.cmmi]
# install our develop version of slapos.recipe.cmmi before anything else,
# otherwise it will be installed from pypi by dependencies.
recipe = zc.recipe.egg
eggs = ${slapos.recipe.cmmi-setup:egg}
[setup-develop-egg]
recipe = zc.recipe.egg:develop
[caucase-setup]
<= setup-develop-egg
egg = caucase
setup = ${caucase-repository:location}
[erp5.util-setup]
<= setup-develop-egg
# XXX erp5.util does not have `test` extra require, but has a `testnode` extra require with same dependencies
egg = erp5.util[testnode]
setup = ${erp5.util-repository:location}
depends = ${slapos.core-setup:egg}
[slapos.cookbook-setup]
<= setup-develop-egg
# XXX slapos.cookbook does not have `test` extra require, `mock` is only listed in `tests_require` and is listed explicitly
egg = slapos.cookbook
setup = ${slapos.cookbook-repository:location}
depends = ${slapos.core-setup:egg}
[slapos.core-setup]
<= setup-develop-egg
# XXX slapos.cookbook does not have `test` extra require, `mock`, `pyflakes` and `httmock` are only listed in `tests_require` and are listed explicitly
egg = slapos.core
setup = ${slapos.core-repository:location}
[slapos.recipe.build-setup]
<= setup-develop-egg
egg = slapos.recipe.build[test]
setup = ${slapos.recipe.build-repository:location}
[slapos.recipe.cmmi-setup]
<= setup-develop-egg
egg = slapos.recipe.cmmi[test]
setup = ${slapos.recipe.cmmi-repository:location}
depends = ${slapos.recipe.build-setup:egg}
[slapos.recipe.template-setup]
<= setup-develop-egg
# XXX slapos.recipe.template does not have `test` extra require, `zope.testing` is only listed in `tests_require` and is listed explicitly
egg = slapos.recipe.template
setup = ${slapos.recipe.template-repository:location}
[slapos.toolbox-setup]
<= setup-develop-egg
# XXX slapos.toolbox does not have `test` extra require, `mock` and `pycurl` are only listed in `tests_require` and are listed explicitly
egg = slapos.toolbox
setup = ${slapos.toolbox-repository:location}
depends = ${slapos.core-setup:egg}
[eggs]
recipe = zc.recipe.egg
eggs =
......@@ -35,18 +87,24 @@ eggs =
${bcrypt:egg}
dnspython
Jinja2
caucase
erp5.util
slapos.cookbook
collective.recipe.template
plone.recipe.command
slapos.recipe.template
slapos.recipe.cmmi
slapos.toolbox
${caucase-setup:egg}
${erp5.util-setup:egg}
${slapos.cookbook-setup:egg}
${slapos.core-setup:egg}
${slapos.recipe.build-setup:egg}
${slapos.recipe.cmmi-setup:egg}
${slapos.recipe.template-setup:egg}
${slapos.toolbox-setup:egg}
mock
zope.testing
httmock
pyflakes
entry-points =
runTestSuite=erp5.util.testsuite:runTestSuite
scripts =
runTestSuite
interpreter=
python_for_test
[git-clone-repository]
recipe = slapos.recipe.build:gitclone
......@@ -58,6 +116,10 @@ branch = master
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/caucase.git
[erp5.util-repository]
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/erp5.git
[slapos.cookbook-repository]
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/slapos.git
......@@ -73,28 +135,38 @@ repository = https://lab.nexedi.com/nexedi/slapos.recipe.template.git
[slapos.recipe.build-repository]
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/slapos.recipe.build.git
# We use the system git and not slapos provided one, because
# slapos.recipe.build is a dependency of slapos.recipe.cmmi
#git-executable = git
[slapos.recipe.cmmi-repository]
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/slapos.recipe.cmmi.git
# We use the system git and not slapos provided one, because slapos git needs
# slapos.recipe.cmmi to be installed. This circular dependency cause parts to
# be reinstalled everytime buildout is run because signatures are not stable.
#git-executable = git
[slapos.toolbox-repository]
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/slapos.toolbox.git
[erp5-util-repository]
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/erp5.git
[template]
recipe = slapos.recipe.template
url = ${:_profile_base_location_}/instance.cfg
md5sum = 6626794c9dbb2530bb8ba3d331e27542
url = ${:_profile_base_location_}/${:filename}
output = ${buildout:directory}/template.cfg
mode = 640
[versions]
Pygments = 2.1.3
collective.recipe.template = 1.10
plone.recipe.command = 1.1
slapos.recipe.template = 4.3
# clear the version of tested eggs, to make sure we installed the developped ones
caucase =
erp5.util =
slapos.cookbook =
slapos.core =
slapos.recipe.build =
slapos.recipe.cmmi =
slapos.recipe.template =
slapos.toolbox =
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment