diff --git a/.gitignore b/.gitignore index 4cde4c896c..0ed16fc576 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ tags *.log heat-test.db .venv +AUTHORS +ChangeLog diff --git a/MANIFEST.in b/MANIFEST.in index 81de932222..2a506f0b9d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,5 @@ +include AUTHORS +include ChangeLog include HACKING.rst include LICENSE include README.rst diff --git a/heat/api/v1/__init__.py b/heat/api/v1/__init__.py index 3737229ff5..3cf5a81443 100644 --- a/heat/api/v1/__init__.py +++ b/heat/api/v1/__init__.py @@ -15,6 +15,9 @@ import logging import routes +import gettext + +gettext.install('heat', unicode=1) from heat.api.v1 import stacks from heat.common import wsgi @@ -61,7 +64,7 @@ class API(wsgi.Router): for action in self._actions: mapper.connect("/", controller=stacks_resource, action=action, - conditions=conditions(action)) + conditions=conditions(action)) mapper.connect("/", controller=stacks_resource, action="index") diff --git a/heat/openstack/common/setup.py b/heat/openstack/common/setup.py new file mode 100644 index 0000000000..40178903c8 --- /dev/null +++ b/heat/openstack/common/setup.py @@ -0,0 +1,183 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Utilities with minimum-depends for use in setup.py +""" + +import os +import re +import subprocess + +from setuptools.command import sdist + + +def parse_mailmap(mailmap='.mailmap'): + mapping = {} + if os.path.exists(mailmap): + fp = open(mailmap, 'r') + for l in fp: + l = l.strip() + if not l.startswith('#') and ' ' in l: + canonical_email, alias = [x for x in l.split(' ') \ + if x.startswith('<')] + mapping[alias] = canonical_email + return mapping + + +def canonicalize_emails(changelog, mapping): + """Takes in a string and an email alias mapping and replaces all + instances of the aliases in the string with their real email. + """ + for alias, email in mapping.iteritems(): + changelog = changelog.replace(alias, email) + return changelog + + +# Get requirements from the first file that exists +def get_reqs_from_files(requirements_files): + reqs_in = [] + for requirements_file in requirements_files: + if os.path.exists(requirements_file): + return open(requirements_file, 'r').read().split('\n') + return [] + + +def parse_requirements(requirements_files=['requirements.txt', + 'tools/pip-requires']): + requirements = [] + for line in get_reqs_from_files(requirements_files): + if re.match(r'\s*-e\s+', line): + requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1', + line)) + elif re.match(r'\s*-f\s+', line): + pass + else: + requirements.append(line) + + return requirements + + +def parse_dependency_links(requirements_files=['requirements.txt', + 'tools/pip-requires']): + dependency_links = [] + for line in get_reqs_from_files(requirements_files): + if re.match(r'(\s*#)|(\s*$)', line): + continue + if re.match(r'\s*-[ef]\s+', line): + dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line)) + return dependency_links + + +def write_requirements(): + venv = os.environ.get('VIRTUAL_ENV', None) + if venv is not None: + with open("requirements.txt", "w") as req_file: + output = subprocess.Popen(["pip", "-E", venv, "freeze", "-l"], + stdout=subprocess.PIPE) + requirements = output.communicate()[0].strip() + req_file.write(requirements) + + +def _run_shell_command(cmd): + output = subprocess.Popen(["/bin/sh", "-c", cmd], + stdout=subprocess.PIPE) + return output.communicate()[0].strip() + + +def write_vcsversion(location): + """Produce a vcsversion dict that mimics the old one produced by bzr. + """ + if os.path.isdir('.git'): + branch_nick_cmd = 'git branch | grep -Ei "\* (.*)" | cut -f2 -d" "' + branch_nick = _run_shell_command(branch_nick_cmd) + revid_cmd = "git rev-parse HEAD" + revid = _run_shell_command(revid_cmd).split()[0] + revno_cmd = "git log --oneline | wc -l" + revno = _run_shell_command(revno_cmd) + with open(location, 'w') as version_file: + version_file.write(""" +# This file is automatically generated by setup.py, So don't edit it. :) +version_info = { + 'branch_nick': '%s', + 'revision_id': '%s', + 'revno': %s +} +""" % (branch_nick, revid, revno)) + + +def write_git_changelog(): + """Write a changelog based on the git changelog.""" + if os.path.isdir('.git'): + git_log_cmd = 'git log --stat' + changelog = _run_shell_command(git_log_cmd) + mailmap = parse_mailmap() + with open("ChangeLog", "w") as changelog_file: + changelog_file.write(canonicalize_emails(changelog, mailmap)) + + +def generate_authors(): + """Create AUTHORS file using git commits.""" + jenkins_email = 'jenkins@review.openstack.org' + old_authors = 'AUTHORS.in' + new_authors = 'AUTHORS' + if os.path.isdir('.git'): + # don't include jenkins email address in AUTHORS file + git_log_cmd = "git log --format='%aN <%aE>' | sort -u | " \ + "grep -v " + jenkins_email + changelog = _run_shell_command(git_log_cmd) + mailmap = parse_mailmap() + with open(new_authors, 'w') as new_authors_fh: + new_authors_fh.write(canonicalize_emails(changelog, mailmap)) + if os.path.exists(old_authors): + with open(old_authors, "r") as old_authors_fh: + new_authors_fh.write('\n' + old_authors_fh.read()) + + +def get_cmdclass(): + """Return dict of commands to run from setup.py.""" + + cmdclass = dict() + + class LocalSDist(sdist.sdist): + """Builds the ChangeLog and Authors files from VC first.""" + + def run(self): + write_git_changelog() + generate_authors() + # sdist.sdist is an old style class, can't use super() + sdist.sdist.run(self) + + cmdclass['sdist'] = LocalSDist + + # If Sphinx is installed on the box running setup.py, + # enable setup.py to build the documentation, otherwise, + # just ignore it + try: + from sphinx.setup_command import BuildDoc + + class LocalBuildDoc(BuildDoc): + def run(self): + for builder in ['html', 'man']: + self.builder = builder + self.finalize_options() + BuildDoc.run(self) + cmdclass['build_sphinx'] = LocalBuildDoc + except ImportError: + pass + + return cmdclass diff --git a/heat/testing/fake/__init__.py b/heat/testing/fake/__init__.py index 5cdad4717e..e69de29bb2 100644 --- a/heat/testing/fake/__init__.py +++ b/heat/testing/fake/__init__.py @@ -1 +0,0 @@ -import rabbit diff --git a/heat/tests/test_resources.py b/heat/tests/test_resources.py index dc5f7f10bd..4e285774e1 100644 --- a/heat/tests/test_resources.py +++ b/heat/tests/test_resources.py @@ -28,7 +28,7 @@ class instancesTest(unittest.TestCase): print "instancesTest teardown complete" def test_instance_create(self): - f = open('../../templates/WordPress_Single_Instance_gold.template') + f = open('templates/WordPress_Single_Instance_gold.template') t = json.loads(f.read()) f.close() @@ -78,7 +78,7 @@ class instancesTest(unittest.TestCase): assert(instance.id > 0) def test_instance_create_delete(self): - f = open('../../templates/WordPress_Single_Instance_gold.template') + f = open('templates/WordPress_Single_Instance_gold.template') t = json.loads(f.read()) f.close() diff --git a/heat/tests/test_stacks.py b/heat/tests/test_stacks.py index 8ebdd541ac..e78a0c0e2e 100644 --- a/heat/tests/test_stacks.py +++ b/heat/tests/test_stacks.py @@ -29,7 +29,7 @@ class stacksTest(unittest.TestCase): # We use this in a number of tests so it's factored out here. def start_wordpress_stack(self, stack_name): - f = open('../../templates/WordPress_Single_Instance_gold.template') + f = open('templates/WordPress_Single_Instance_gold.template') t = json.loads(f.read()) f.close() params = {} @@ -146,7 +146,7 @@ class stacksTest(unittest.TestCase): self.m.ReplayAll() stack.create_blocking() - f = open('../../templates/WordPress_Single_Instance_gold.template') + f = open('templates/WordPress_Single_Instance_gold.template') t = json.loads(f.read()) params = {} parameters = {} diff --git a/openstack-common.conf b/openstack-common.conf index 7a0da8891c..7a059c8865 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=cfg,local,iniparser,utils,exception,timeutils,importutils +modules=cfg,local,iniparser,utils,exception,timeutils,importutils,setup # The base module to hold the copy of openstack.common base=heat diff --git a/setup.cfg b/setup.cfg index 6a7725f0c1..d3062c4b94 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,3 +22,6 @@ keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = heat/locale/heat.pot +[nosetests] +verbosity=2 +detailed-errors=1 diff --git a/setup.py b/setup.py index bfcbce1dd8..58801cf7ae 100755 --- a/setup.py +++ b/setup.py @@ -17,58 +17,19 @@ import gettext import os import subprocess -from setuptools import setup, find_packages -from setuptools.command.sdist import sdist +import setuptools -gettext.install('heat', unicode=1) +from heat.openstack.common import setup +setup.write_vcsversion('heat/vcsversion.py') + +# import this after write_vcsversion because version imports vcsversion from heat import version +requires = setup.parse_requirements() +depend_links = setup.parse_dependency_links() -def run_git_command(cmd): - output = subprocess.Popen(["/bin/sh", "-c", cmd], - stdout=subprocess.PIPE) - return output.communicate()[0].strip() - - -if os.path.isdir('.git'): - branch_nick_cmd = 'git branch | grep -Ei "\* (.*)" | cut -f2 -d" "' - branch_nick = run_git_command(branch_nick_cmd) - revid_cmd = "git --no-pager log --max-count=1 | cut -f2 -d' ' | head -1" - revid = run_git_command(revid_cmd) - revno_cmd = "git --no-pager log --oneline | wc -l" - revno = run_git_command(revno_cmd) - with open("heat/vcsversion.py", 'w') as version_file: - version_file.write(""" -# This file is automatically generated by setup.py, So don't edit it. :) -version_info = { - 'branch_nick': '%s', - 'revision_id': '%s', - 'revno': %s -} -""" % (branch_nick, revid, revno)) - -cmdclass = {} - -# If Sphinx is installed on the box running setup.py, -# enable setup.py to build the documentation, otherwise, -# just ignore it -try: - from sphinx.setup_command import BuildDoc - - class local_BuildDoc(BuildDoc): - def run(self): - for builder in ['html', 'man']: - self.builder = builder - self.finalize_options() - BuildDoc.run(self) - cmdclass['build_sphinx'] = local_BuildDoc - -except: - pass - - -setup( +setuptools.setup( name='heat', version=version.canonical_version_string(), description='The heat project provides services for provisioning ' @@ -77,8 +38,10 @@ setup( author='Heat API Developers', author_email='discuss@heat-api.org', url='http://heat.openstack.org/', - packages=find_packages(exclude=['bin']), - cmdclass=cmdclass, + packages=setuptools.find_packages(exclude=['bin']), + cmdclass=setup.get_cmdclass(), + install_requires=requires, + dependency_links=depend_links, include_package_data=True, classifiers=[ 'Development Status :: 4 - Beta', @@ -92,15 +55,4 @@ setup( 'bin/heat-metadata', 'bin/heat-engine', 'bin/heat-db-setup'], - data_files=[('/etc/heat', ['etc/heat-api.conf', - 'etc/heat-api-paste.ini', - 'etc/heat-metadata.conf', - 'etc/heat-metadata-paste.ini', - 'etc/heat-engine.conf', - 'etc/heat-engine-paste.ini']), - ('/etc/bash_completion.d', ['etc/bash_completion.d/heat']), - ('/var/log/heat', ['var/log/heat/api.log', - 'var/log/heat/metadata.log', - 'var/log/heat/engine.log']), - ('/var/lib/heat', ['var/lib/heat/.dummy'])], py_modules=[]) diff --git a/tools/test-requires b/tools/test-requires index 6f14508c3e..2befbbb386 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -2,8 +2,9 @@ distribute>=0.6.24 coverage +mox nose nosexcover openstack.nose_plugin -pep8==0.6.1 -mox +pep8>=1.0 +sphinx diff --git a/tox.ini b/tox.ini index 66a87194f5..96afb18b32 100644 --- a/tox.ini +++ b/tox.ini @@ -1,34 +1,42 @@ [tox] -envlist = py27,pep8 +envlist = py26,py27,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} + NOSE_WITH_OPENSTACK=1 + NOSE_OPENSTACK_COLOR=1 + NOSE_OPENSTACK_RED=0.05 + NOSE_OPENSTACK_YELLOW=0.025 + NOSE_OPENSTACK_SHOW_ELAPSED=1 deps = -r{toxinidir}/tools/pip-requires -r{toxinidir}/tools/test-requires -commands = {toxinidir}/run_tests.sh -N -P - -[tox:jenkins] -sitepackages = True -downloadcache = ~/cache/pip +commands = nosetests [testenv:pep8] deps = pep8 -commands = /bin/bash run_tests.sh -N --pep8 - -[testenv:coverage] -commands = /bin/bash run_tests.sh -N -P --coverage +commands = pep8 --repeat --show-source heat setup.py [testenv:venv] commands = {posargs} +[testenv:cover] +commands = nosetests --cover-erase --cover-package=heat --with-xcoverage + +[tox:jenkins] +downloadcache = ~/cache/pip + +[testenv:jenkins26] +basepython = python2.6 +setenv = NOSE_WITH_XUNIT=1 + [testenv:jenkins27] basepython = python2.7 -deps = file://{toxinidir}/.cache.bundle +setenv = NOSE_WITH_XUNIT=1 [testenv:jenkinscover] -deps = file://{toxinidir}/.cache.bundle -commands = /bin/bash run_tests.sh -N --coverage +setenv = NOSE_WITH_XUNIT=1 +commands = nosetests --cover-erase --cover-package=heat --with-xcoverage [testenv:jenkinsvenv] -deps = file://{toxinidir}/.cache.bundle +setenv = NOSE_WITH_XUNIT=1 commands = {posargs} diff --git a/var/lib/heat/.dummy b/var/lib/heat/.dummy deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/var/log/heat/api.log b/var/log/heat/api.log deleted file mode 100644 index dc269db1b6..0000000000 --- a/var/log/heat/api.log +++ /dev/null @@ -1 +0,0 @@ -#dummy file diff --git a/var/log/heat/engine.log b/var/log/heat/engine.log deleted file mode 100644 index dc269db1b6..0000000000 --- a/var/log/heat/engine.log +++ /dev/null @@ -1 +0,0 @@ -#dummy file diff --git a/var/log/heat/metadata.log b/var/log/heat/metadata.log deleted file mode 100644 index e69de29bb2..0000000000