Retire Packaging Deb project repos

This commit is part of a series to retire the Packaging Deb
project. Step 2 is to remove all content from the project
repos, replacing it with a README notification where to find
ongoing work, and how to recover the repo if needed at some
future point (as in
https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project).

Change-Id: I6ca27a94cfb35256d71966ea300245365d210350
This commit is contained in:
Tony Breeds
2017-09-12 15:59:39 -06:00
parent 5fa587dfed
commit 37349c1ffa
47 changed files with 14 additions and 3157 deletions

33
.gitignore vendored
View File

@@ -1,33 +0,0 @@
# Compiled files
*.py[co]
*.a
*.o
*.so
# Sphinx
_build
# Packages/installer info
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
# Other
.testrepository
.tox
.*.swp
.coverage
cover
AUTHORS
ChangeLog
# Editors
*~

View File

@@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack-dev/hacking.git

View File

@@ -1,3 +0,0 @@
# Format is:
# Name <preferred e-mail> <other e-mail >
Joe Gordon <joe.gordon0@gmail.com> <jogo@cloudscaling.com>

View File

@@ -1,8 +0,0 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@@ -1,16 +0,0 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
Once those steps have been completed, changes to OpenStack
should be submitted for review via the Gerrit tool, following
the workflow documented at:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/hacking

View File

@@ -1,352 +0,0 @@
.. _StyleGuide:
OpenStack Style Guidelines
==========================
OpenStack has a set of style guidelines for clarity. OpenStack is a
very large code base (over 1 Million lines of python), spanning dozens
of git trees, with over a thousand developers contributing every 12
months. As such common style helps developers understand code in
reviews, move between projects smoothly, and overall make the code
more maintainable.
Step 0
------
- Step 1: Read `pep8`_
- Step 2: Read `pep8`_ again
- Step 3: Read on
.. _`pep8`: http://www.python.org/dev/peps/pep-0008/
General
-------
- [H903] Use only UNIX style newlines (``\n``), not Windows style (``\r\n``)
- It is preferred to wrap long lines in parentheses and not a backslash
for line continuation.
- [H201] Do not write ``except:``, use ``except Exception:`` at the very least.
When catching an exception you should be as specific so you don't mistakenly
catch unexpected exceptions.
- [H101] Include your name with TODOs as in ``# TODO(yourname)``. This makes
it easier to find out who the author of the comment was.
- [H105] Don't use author tags. We use version control instead.
- [H106] Don't put vim configuration in source files (off by default).
- [H904] Delay string interpolations at logging calls (off by default).
- Do not shadow a built-in or reserved word. Shadowing built -in or reserved
words makes the code harder to understand. Example::
def list():
return [1, 2, 3]
mylist = list() # BAD, shadows `list` built-in
class Foo(object):
def list(self):
return [1, 2, 3]
mylist = Foo().list() # OKAY, does not shadow built-in
Imports
-------
- Do not import objects, only modules (*)
- [H301] Do not import more than one module per line (*)
- [H303] Do not use wildcard ``*`` import (*)
- [H304] Do not make relative imports
- [H306] Alphabetically order your imports by the full module path.
Organize your imports according to the `Import order
template`_ and `Real-world Import Order Examples`_ below.
(*) exceptions are:
- imports from ``migrate`` package
- imports from ``sqlalchemy`` package
- function imports from ``i18n`` module
Import order template
^^^^^^^^^^^^^^^^^^^^^
::
{{stdlib imports in human alphabetical order}}
\n
{{third-party lib imports in human alphabetical order}}
\n
{{project imports in human alphabetical order}}
\n
\n
{{begin your code}}
Real-world Import Order Examples
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Example::
import httplib
import logging
import random
import StringIO
import time
import unittest
import eventlet
import webob.exc
import nova.api.ec2
from nova.api import manager
from nova.api import openstack
from nova.auth import users
from nova.endpoint import cloud
import nova.flags
from nova.i18n import _, _LC
from nova import test
Docstrings
----------
- [H401] Docstrings should not start with a space.
- [H403] Multi line docstrings should end on a new line.
- [H404] Multi line docstrings should start without a leading new line.
- [H405] Multi line docstrings should start with a one line summary followed
by an empty line.
Example::
"""A multi line docstring has a one-line summary, less than 80 characters.
Then a new paragraph after a newline that explains in more detail any
general information about the function, class or method. Example usages
are also great to have here if it is a complex class or function.
When writing the docstring for a class, an extra line should be placed
after the closing quotations. For more in-depth explanations for these
decisions see http://www.python.org/dev/peps/pep-0257/
If you are going to describe parameters and return values, use Sphinx, the
appropriate syntax is as follows.
:param foo: the foo parameter
:param bar: the bar parameter
:returns: return_type -- description of the return value
:returns: description of the return value
:raises: AttributeError, KeyError
"""
Dictionaries/Lists
------------------
If a dictionary (dict) or list object is longer than 80 characters, its items
should be split with newlines. Embedded iterables should have their items
indented. Additionally, the last item in the dictionary should have a trailing
comma. This increases readability and simplifies future diffs.
Example::
my_dictionary = {
"image": {
"name": "Just a Snapshot",
"size": 2749573,
"properties": {
"user_id": 12,
"arch": "x86_64",
},
"things": [
"thing_one",
"thing_two",
],
"status": "ACTIVE",
},
}
- [H501] Do not use ``locals()`` or ``self.__dict__`` for formatting strings,
it is not clear as using explicit dictionaries and can hide errors during
refactoring.
Calling Methods
---------------
Calls to methods 80 characters or longer should format each argument with
newlines. This is not a requirement, but a guideline::
unnecessarily_long_function_name('string one',
'string two',
kwarg1=constants.ACTIVE,
kwarg2=['a', 'b', 'c'])
Rather than constructing parameters inline, it is better to break things up::
list_of_strings = [
'what_a_long_string',
'not as long',
]
dict_of_numbers = {
'one': 1,
'two': 2,
'twenty four': 24,
}
object_one.call_a_method('string three',
'string four',
kwarg1=list_of_strings,
kwarg2=dict_of_numbers)
Internationalization (i18n) Strings
-----------------------------------
In order to support multiple languages, we have a mechanism to support
automatic translations of exception and log strings.
Example::
msg = _("An error occurred")
raise HTTPBadRequest(explanation=msg)
- [H702] If you have a variable to place within the string, first
internationalize the template string then do the replacement.
Example::
msg = _LE("Missing parameter: %s")
LOG.error(msg, "flavor")
- [H703] If you have multiple variables to place in the string, use keyword
parameters. This helps our translators reorder parameters when needed.
Example::
msg = _LE("The server with id %(s_id)s has no key %(m_key)s")
LOG.error(msg, {"s_id": "1234", "m_key": "imageId"})
.. seealso::
* `oslo.i18n Guidelines <https://docs.openstack.org/oslo.i18n/latest/user/guidelines.html>`__
Python 3.x compatibility
------------------------
OpenStack code should become Python 3.x compatible. That means all Python 2.x-only
constructs or dependencies should be avoided. In order to start making code
Python 3.x compatible before it can be fully Python 3.x compatible, we have checks for Python 2.x-only constructs:
- [H231] ``except``. Instead of::
except x,y:
Use::
except x as y:
- [H232] Python 3.x has become more strict regarding octal string
literals. Use ``0o755`` instead of ``0755``. Similarly, explicit use of long
literals (``01234L``) should be avoided.
- [H233] The ``print`` operator can be avoided by using::
from __future__ import print_function
at the top of your module.
- [H234] ``assertEquals()`` logs a DeprecationWarning in Python 3.x, use
``assertEqual()`` instead. The same goes for ``assertNotEquals()``.
- [H235] ``assert_()`` is deprecated in Python 3.x, use ``assertTrue()`` instead.
- [H236] Use ``six.add_metaclass`` instead of ``__metaclass__``.
Example::
import six
@six.add_metaclass(Meta)
class YourClass():
- [H237] Don't use modules that were removed in Python 3. Removed module list:
http://python3porting.com/stdlib.html#removed-modules
- [H238] Old style classes are deprecated and no longer available in Python 3
(they are converted to new style classes). In order to avoid any unwanted side
effects all classes should be declared using new style. See `the new-style
class documentation <https://www.python.org/doc/newstyle/>`_ for reference on
the differences.
Example::
class Foo(object):
pass
Creating Unit Tests
-------------------
For every new feature, unit tests should be created that both test and
(implicitly) document the usage of said feature. If submitting a patch for a
bug that had no unit test, a new passing unit test should be added. If a
submitted bug fix does have a unit test, be sure to add a new one that fails
without the patch and passes with the patch.
Unit Tests and assertRaises
---------------------------
A properly written test asserts that particular behavior occurs. This can
be a success condition or a failure condition, including an exception.
When asserting that a particular exception is raised, the most specific
exception possible should be used.
- [H202] Testing for ``Exception`` being raised is almost always a
mistake since it will match (almost) every exception, even those
unrelated to the exception intended to be tested.
This applies to catching exceptions manually with a try/except block,
or using ``assertRaises()``.
Example::
with self.assertRaises(exception.InstanceNotFound):
db.instance_get_by_uuid(elevated, instance_uuid)
- [H203] Use assertIs(Not)None to check for None (off by default)
Unit test assertions tend to give better messages for more specific
assertions. As a result, ``assertIsNone(...)`` is preferred over
``assertEqual(None, ...)`` and ``assertIs(None, ...)``, and
``assertIsNotNone(...)`` is preferred over ``assertNotEqual(None, ...)``
and ``assertIsNot(None, ...)``. Off by default.
OpenStack Trademark
-------------------
OpenStack is a registered trademark of the OpenStack Foundation, and uses the
following capitalization::
OpenStack
OpenStack Licensing
-------------------
- [H102 H103] Newly contributed Source Code should be licensed under the
Apache 2.0 license. All source files should have the following header::
# 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.
- [H104] Files with no code shouldn't contain any license header nor comments,
and must be left completely empty.
Commit Messages
---------------
Using a common format for commit messages will help keep our git history
readable.
For further information on constructing high quality commit messages,
and how to split up commits into a series of changes, consult the
project wiki:
https://wiki.openstack.org/GitCommitMessages

176
LICENSE
View File

@@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@@ -1,9 +0,0 @@
include AUTHORS
include ChangeLog
include README.rst
include requirements.txt
include test-requirements.txt
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

14
README Normal file
View File

@@ -0,0 +1,14 @@
This project is no longer maintained.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
For ongoing work on maintaining OpenStack packages in the Debian
distribution, please see the Debian OpenStack packaging team at
https://wiki.debian.org/OpenStack/.
For any further questions, please email
openstack-dev@lists.openstack.org or join #openstack-dev on
Freenode.

View File

@@ -1,131 +0,0 @@
Introduction
============
hacking is a set of flake8 plugins that test and enforce the :ref:`StyleGuide`.
Hacking pins its dependencies, as a new release of some dependency can break
hacking based gating jobs. This is because new versions of dependencies can
introduce new rules, or make existing rules stricter.
Installation
============
hacking is available from pypi, so just run::
pip install hacking
This will install specific versions of ``flake8`` with the ``hacking``,
``pep8``, ``mccabe`` and ``pyflakes`` plugins.
Origin
======
Hacking started its life out as a text file in Nova's first commit. It was
initially based on the `Google Python Style Guide`_, and over time more
OpenStack specific rules were added. Hacking serves several purposes:
1. Agree on a common style guide so reviews don't get bogged down on style
nit picks. (example: docstring guidelines)
2. Make code written by many different authors easier to read by making the
style more uniform. (example: unix vs windows newlines)
3. Call out dangerous patterns and avoid them. (example: shadowing built-in
or reserved words)
Initially the hacking style guide was enforced manually by reviewers, but this
was a big waste of time so hacking, the tool, was born to automate
the process and remove the extra burden from human reviewers.
.. _`Google Python Style Guide`: https://google.github.io/styleguide/pyguide.html
Versioning
==========
hacking uses the ``major.minor.maintenance`` release notation, where maintenance
releases cannot contain new checks. This way projects can gate on hacking
by pinning on the ``major.minor`` number while accepting maintenance updates
without being concerned that a new version will break the gate with a new
check.
For example a project can depend on ``hacking>=0.10.0,<0.11.0``, and can know
that ``0.10.1`` will not fail in places where ``0.10.0`` passed.
Adding additional checks
========================
Each check is a pep8 plugin so read
- https://github.com/jcrocholl/pep8/blob/master/docs/developer.rst#contribute
The focus of new or changed rules should be to do one of the following
- Substantially increase the reviewability of the code (eg: H301, H303)
as they make it easy to understand where symbols come from)
- Catch a common programming error that may arise in the future (H201)
- Prevent a situation that would 100% of the time be -1ed by
developers (H903)
But, as always, remember that these are Guidelines. Treat them as
such. There are always times for exceptions. All new rules should
support noqa.
If a check needs to be staged in, or it does not apply to every project or its
branch, it can be added as off by default.
Requirements
------------
- The check must already have community support. We do not want to dictate
style, only enforce it.
- The canonical source of the OpenStack Style Guidelines is :ref:`StyleGuide`,
and hacking just enforces
them; so when adding a new check, it must be in ``HACKING.rst``
- False negatives are ok, but false positives are not
- Cannot be project specific, project specific checks should be `Local Checks`_
- Include extensive tests
- Registered as entry_points in ``setup.cfg``
- Error code must be in the relevant ``Hxxx`` group
- The check should not attempt to import modules from the code being checked.
Importing random modules, has caused all kinds of trouble for us in the past.
Enabling off-by-default checks
==============================
Some of the available checks are disabled by default. These checks are:
- [H106] Don't put vim configuration in source files.
- [H203] Use assertIs(Not)None to check for None.
- [H904] Delay string interpolations at logging calls.
To enable these checks, edit the ``flake8`` section of the ``tox.ini`` file.
For example to enable H106 and H203:
.. code-block:: ini
[flake8]
enable-extensions = H106,H203
Local Checks
============
hacking supports having local changes in a source tree. They can be configured
to run in two different ways. They can be registered individually, or with
a factory function.
For individual registration, put a comma separated list of pep8 compatible
check functions into the hacking section of tox.ini. E.g.:
.. code-block:: ini
[hacking]
local-check = nova.tests.hacking.bad_code_is_terrible
Alternately, you can specify the location of a callable that will be called
at registration time and will be passed the registration function. The callable
should expect to call the passed in function on everything if wants to
register. Such as:
.. code-block:: ini
[hacking]
local-check-factory = nova.tests.hacking.factory

View File

@@ -1,61 +0,0 @@
# -*- coding: utf-8 -*-
import os
import sys
sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'openstackdocstheme']
# openstackdocstheme options
repository_name = 'openstack-dev/hacking'
bug_project = 'hacking'
bug_tag = ''
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'hacking'
copyright = u'2013, OpenStack Foundation'
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output --------------------------------------------------
html_theme = 'openstackdocs'
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index',
'%s.tex' % project,
u'%s Documentation' % project,
u'OpenStack Foundation', 'manual'),
]

View File

@@ -1,14 +0,0 @@
================================================
hacking: OpenStack Hacking Guideline Enforcement
================================================
hacking is a set of flake8 plugins that test and enforce the :ref:`StyleGuide`.
Hacking pins its dependencies, as a new release of some dependency can break
hacking based gating jobs. This is because new versions of dependencies can
introduce new rules, or make existing rules stricter.
.. toctree::
:maxdepth: 3
user/index

View File

@@ -1 +0,0 @@
.. include:: ../../../HACKING.rst

View File

@@ -1,9 +0,0 @@
==================
User Documentation
==================
.. toctree::
:maxdepth: 2
hacking
usage

View File

@@ -1,5 +0,0 @@
=============
Using hacking
=============
.. include:: ../../../README.rst

View File

View File

@@ -1,182 +0,0 @@
# 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.
import re
import tokenize
from hacking import core
AUTHOR_TAG_RE = (re.compile("^\s*#\s*@?(a|A)uthors?:"),
re.compile("^\.\.\s+moduleauthor::"))
@core.flake8ext
def hacking_todo_format(physical_line, tokens):
"""Check for 'TODO()'.
OpenStack HACKING guide recommendation for TODO:
Include your name with TODOs as in "# TODO(termie)"
Okay: # TODO(sdague)
H101: # TODO fail
H101: # TODO
H101: # TODO (jogo) fail
Okay: TODO = 5
"""
# TODO(jogo): make the following doctests pass:
# H101: #TODO(jogo fail
# H101: #TODO(jogo
# TODO(jogo): make this check docstrings as well (don't have to be at top
# of function)
for token_type, text, start_index, _, _ in tokens:
if token_type == tokenize.COMMENT:
pos = text.find('TODO')
pos1 = text.find('TODO(')
if (pos != pos1):
return pos + start_index[1], "H101: Use TODO(NAME)"
@core.flake8ext
def hacking_has_license(physical_line, filename, lines, line_number):
"""Check for Apache 2.0 license.
H102 license header not found
"""
# don't work about init files for now
# TODO(sdague): enforce license in init file if it's not empty of content
license_found = False
# skip files that are < 10 lines, which isn't enough for a license to fit
# this allows us to handle empty files, as well as not fail on the Okay
# doctests.
if line_number is 1 and len(lines) > 10 and _project_is_apache():
for idx, line in enumerate(lines):
# if it's more than 10 characters in, it's probably not in the
# header
if 0 <= line.find('Licensed under the Apache License') < 10:
license_found = True
if not license_found:
return (0, "H102: Apache 2.0 license header not found")
@core.flake8ext
def hacking_has_correct_license(physical_line, filename, lines, line_number):
"""Check for Apache 2.0 license.
H103 header does not match Apache 2.0 License notice
"""
# don't work about init files for now
# TODO(sdague): enforce license in init file if it's not empty of content
# skip files that are < 10 lines, which isn't enough for a license to fit
# this allows us to handle empty files, as well as not fail on the Okay
# doctests.
if line_number is 1 and len(lines) > 10 and _project_is_apache():
for idx, line in enumerate(lines):
column = line.find('Licensed under the Apache License')
if (0 < column < 10 and not
_check_for_exact_apache(idx, lines)):
return (column, "H103: Header does not match Apache 2.0 "
"License notice")
EMPTY_LINE_RE = re.compile("^\s*(#.*|$)")
@core.flake8ext
def hacking_has_only_comments(physical_line, filename, lines, line_number):
"""Check for empty files with only comments
H104 empty file with only comments
"""
if line_number == 1 and all(map(EMPTY_LINE_RE.match, lines)):
return (0, "H104: File contains nothing but comments")
_is_apache_cache = None
def _project_is_apache():
"""Determine if a project is Apache.
Look for a key string in a set of possible license files to figure out
if a project looks to be Apache. This is used as a precondition for
enforcing license headers.
"""
global _is_apache_cache
if _is_apache_cache is not None:
return _is_apache_cache
license_files = ["LICENSE"]
for filename in license_files:
try:
with open(filename, "r") as file:
for line in file:
if re.search('Apache License', line):
_is_apache_cache = True
return True
except IOError:
pass
_is_apache_cache = False
return False
def _check_for_exact_apache(start, lines):
"""Check for the Apache 2.0 license header.
We strip all the newlines and extra spaces so this license string
should work regardless of indentation in the file.
"""
APACHE2 = """
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."""
# out of all the formatting I've seen, a 12 line version seems to be the
# longest in the source tree. So just take the 12 lines starting with where
# the Apache starting words were found, strip all the '#' and collapse the
# spaces.
content = ''.join(lines[start:(start + 12)])
content = re.sub('\#', '', content)
content = re.sub('\s+', ' ', content).strip()
stripped_apache2 = re.sub('\s+', ' ', APACHE2).strip()
if stripped_apache2 in content:
return True
else:
print("<license>!=<apache2>:\n'%s' !=\n'%s'" %
(content, stripped_apache2))
return False
@core.flake8ext
def hacking_no_author_tags(physical_line):
"""Check that no author tags are used.
H105 don't use author tags
"""
for regex in AUTHOR_TAG_RE:
if regex.match(physical_line):
physical_line = physical_line.lower()
pos = physical_line.find('moduleauthor')
if pos < 0:
pos = physical_line.find('author')
return (pos, "H105: Don't use author tags")

View File

@@ -1,46 +0,0 @@
# 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.
import tokenize
from hacking import core
LOCALS_TEXT_MAP = {
'locals': 'locals()',
'self': 'self.__dict__'
}
@core.flake8ext
def hacking_no_locals(logical_line, tokens, noqa):
"""Do not use locals() or self.__dict__ for string formatting.
Okay: 'locals()'
Okay: 'locals'
Okay: locals()
Okay: print(locals())
H501: print("%(something)" % locals())
H501: LOG.info(_("%(something)") % self.__dict__)
Okay: print("%(something)" % locals()) # noqa
"""
if noqa:
return
for_formatting = False
for token_type, text, start, _, _ in tokens:
if text == "%" and token_type == tokenize.OP:
for_formatting = True
if for_formatting and token_type == tokenize.NAME:
for k, v in LOCALS_TEXT_MAP.items():
if text == k and v in logical_line:
yield (start[1],
"H501: Do not use %s for string formatting" % v)

View File

@@ -1,161 +0,0 @@
# 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.
import tokenize
from hacking import core
START_DOCSTRING_TRIPLE = ['u"""', 'r"""', '"""', "u'''", "r'''", "'''"]
END_DOCSTRING_TRIPLE = ['"""', "'''"]
@core.flake8ext
def hacking_docstring_start_space(physical_line, previous_logical, tokens):
r"""Check for docstring not starting with space.
OpenStack HACKING guide recommendation for docstring:
Docstring should not start with space
Okay: def foo():\n '''This is good.'''
Okay: def foo():\n r'''This is good.'''
Okay: def foo():\n a = ''' This is not a docstring.'''
Okay: def foo():\n pass\n ''' This is not.'''
H401: def foo():\n ''' This is not.'''
H401: def foo():\n r''' This is not.'''
"""
docstring = is_docstring(tokens, previous_logical)
if docstring:
start, start_triple = _find_first_of(docstring, START_DOCSTRING_TRIPLE)
if docstring[len(start_triple)] == ' ':
# docstrings get tokenized on the last line of the docstring, so
# we don't know the exact position.
return (0, "H401: docstring should not start with"
" a space")
@core.flake8ext
def hacking_docstring_multiline_end(physical_line, previous_logical, tokens):
r"""Check multi line docstring end.
OpenStack HACKING guide recommendation for docstring:
Docstring should end on a new line
Okay: '''foobar\nfoo\nbar\n'''
Okay: def foo():\n '''foobar\n\nfoo\nbar\n'''
Okay: class Foo(object):\n '''foobar\n\nfoo\nbar\n'''
Okay: def foo():\n a = '''not\na\ndocstring'''
Okay: def foo():\n a = '''not\na\ndocstring''' # blah
Okay: def foo():\n pass\n'''foobar\nfoo\nbar\n d'''
H403: def foo():\n '''foobar\nfoo\nbar\ndocstring'''
H403: def foo():\n '''foobar\nfoo\nbar\npretend raw: r'''
H403: class Foo(object):\n '''foobar\nfoo\nbar\ndocstring'''\n\n
"""
docstring = is_docstring(tokens, previous_logical)
if docstring:
if '\n' not in docstring:
# not a multi line
return
else:
last_line = docstring.split('\n')[-1]
pos = max(last_line.rfind(i) for i in END_DOCSTRING_TRIPLE)
if len(last_line[:pos].strip()) > 0:
# Something before the end docstring triple
return (pos,
"H403: multi line docstrings should end on a new line")
@core.flake8ext
def hacking_docstring_multiline_start(physical_line, previous_logical, tokens):
r"""Check multi line docstring starts immediately with summary.
OpenStack HACKING guide recommendation for docstring:
Docstring should start with a one-line summary, less than 80 characters.
Okay: '''foobar\n\nfoo\nbar\n'''
Okay: def foo():\n a = '''\nnot\na docstring\n'''
H404: def foo():\n '''\nfoo\nbar\n'''\n\n
H404: def foo():\n r'''\nfoo\nbar\n'''\n\n
"""
docstring = is_docstring(tokens, previous_logical)
if docstring:
if '\n' not in docstring:
# single line docstring
return
start, start_triple = _find_first_of(docstring, START_DOCSTRING_TRIPLE)
lines = docstring.split('\n')
if lines[0].strip() == start_triple:
# docstrings get tokenized on the last line of the docstring, so
# we don't know the exact position.
return (0, "H404: multi line docstring "
"should start without a leading new line")
@core.flake8ext
def hacking_docstring_summary(physical_line, previous_logical, tokens):
r"""Check multi line docstring summary is separated with empty line.
OpenStack HACKING guide recommendation for docstring:
Docstring should start with a one-line summary, less than 80 characters.
Okay: def foo():\n a = '''\nnot\na docstring\n'''
Okay: '''foobar\n\nfoo\nbar\n'''
H405: def foo():\n '''foobar\nfoo\nbar\n'''
H405: def foo():\n r'''foobar\nfoo\nbar\n'''
H405: def foo():\n '''foobar\n'''
"""
docstring = is_docstring(tokens, previous_logical)
if docstring:
if '\n' not in docstring:
# not a multi line docstring
return
lines = docstring.split('\n')
if len(lines) > 1 and len(lines[1].strip()) is not 0:
# docstrings get tokenized on the last line of the docstring, so
# we don't know the exact position.
return (0, "H405: multi line docstring "
"summary not separated with an empty line")
def is_docstring(tokens, previous_logical):
"""Return found docstring
'A docstring is a string literal that occurs as the first statement in a
module, function, class,'
http://www.python.org/dev/peps/pep-0257/#what-is-a-docstring
"""
for token_type, text, start, _, _ in tokens:
if token_type == tokenize.STRING:
break
elif token_type != tokenize.INDENT:
return False
else:
return False
line = text.lstrip()
start, start_triple = _find_first_of(line, START_DOCSTRING_TRIPLE)
if (previous_logical.startswith("def ") or
previous_logical.startswith("class ")):
if start == 0:
return text
def _find_first_of(line, substrings):
"""Find earliest occurrence of one of substrings in line.
Returns pair of index and found substring, or (-1, None)
if no occurrences of any of substrings were found in line.
"""
starts = ((line.find(i), i) for i in substrings)
found = [(i, sub) for i, sub in starts if i != -1]
if found:
return min(found)
else:
return -1, None

View File

@@ -1,224 +0,0 @@
# 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.
# module cannot be called except since that is a reserved word
import ast
import re
from six import PY2
from hacking import core
RE_ASSERT_RAISES_EXCEPTION = re.compile(r"self\.assertRaises\(Exception[,\)]")
@core.flake8ext
def hacking_except_format(logical_line, noqa):
r"""Check for 'except:'.
OpenStack HACKING guide recommends not using except:
Do not write "except:", use "except Exception:" at the very least
Okay: try:\n pass\nexcept Exception:\n pass
H201: try:\n pass\nexcept:\n pass
H201: except:
Okay: try:\n pass\nexcept: # noqa\n pass
"""
if noqa:
return
if logical_line.startswith("except:"):
yield 6, "H201: no 'except:' at least use 'except Exception:'"
@core.flake8ext
def hacking_except_format_assert(logical_line, noqa):
r"""Check for 'assertRaises(Exception'.
OpenStack HACKING guide recommends not using assertRaises(Exception...):
Do not use overly broad Exception type
Okay: self.assertRaises(NovaException, foo)
Okay: self.assertRaises(ExceptionStrangeNotation, foo)
H202: self.assertRaises(Exception, foo)
H202: self.assertRaises(Exception)
Okay: self.assertRaises(Exception) # noqa
Okay: self.assertRaises(Exception, foo) # noqa
"""
if noqa:
return
if RE_ASSERT_RAISES_EXCEPTION.search(logical_line):
yield 1, "H202: assertRaises Exception too broad"
def is_none(node):
'''Check whether an AST node corresponds to None.
In Python 2 None uses the same ast.Name class that variables etc. use,
but in Python 3 there is a new ast.NameConstant class.
'''
if PY2:
return isinstance(node, ast.Name) and node.id == 'None'
return isinstance(node, ast.NameConstant) and node.value is None
def _get_local_func_name(node):
if isinstance(node.func, ast.Attribute):
return node.func.attr
elif isinstance(node.func, ast.Name):
return node.func.id
else:
return None
class NoneArgChecker(ast.NodeVisitor):
'''NodeVisitor to check function calls for None arguments.
:param func_name: only check calls to functions with this name
:param num_args: number of arguments to check for None
self.none_found will be True if any None arguments were found.
'''
def __init__(self, func_name, num_args=2):
self.func_name = func_name
self.num_args = num_args
self.none_found = False
def visit_Call(self, node):
local_func_name = _get_local_func_name(node)
if local_func_name == self.func_name:
args_to_check = node.args[:self.num_args]
self.none_found |= any(is_none(x) for x in args_to_check)
self.generic_visit(node)
@core.flake8ext
@core.off_by_default
def hacking_assert_is_none(logical_line, noqa):
"""Use assertIs(Not)None to check for None in assertions.
Okay: self.assertEqual('foo', 'bar')
Okay: self.assertNotEqual('foo', {}.get('bar', None))
Okay: self.assertIs('foo', 'bar')
Okay: self.assertIsNot('foo', 'bar', None)
Okay: foo(self.assertIsNot('foo', 'bar'))
H203: self.assertEqual(None, 'foo')
H203: self.assertNotEqual('foo', None)
H203: self.assertIs(None, 'foo', 'bar')
H203: self.assertIsNot('foo', None, 'bar')
H203: foo(self.assertIsNot('foo', None, 'bar'))
Okay: self.assertEqual(None, 'foo') # noqa
Okay: self.assertIs(None, 'foo') # noqa
Okay: self.assertIsNone('foo')
"""
if noqa:
return
for func_name in ('assertEqual', 'assertIs', 'assertNotEqual',
'assertIsNot'):
try:
start = logical_line.index('.%s(' % func_name) + 1
except ValueError:
continue
checker = NoneArgChecker(func_name)
checker.visit(ast.parse(logical_line))
if checker.none_found:
yield start, "H203: Use assertIs(Not)None to check for None"
class AssertTrueFalseChecker(ast.NodeVisitor):
'''NodeVisitor to find "assert[True|False](some comparison)" statements.
:param method_names: methods to look for: assertTrue and/or assertFalse
:param ops: list of comparisons we want to look for (objects from the ast
module)
'''
def __init__(self, method_names, ops):
self.method_names = method_names
self.ops = tuple(ops)
self.error = False
def visit_Call(self, node):
# No need to keep visiting the AST if we already found something.
if self.error:
return
self.generic_visit(node)
local_func_name = _get_local_func_name(node)
if (local_func_name in self.method_names and
len(node.args) == 1 and
isinstance(node.args[0], ast.Compare) and
len(node.args[0].ops) == 1 and
isinstance(node.args[0].ops[0], self.ops)):
self.error = True
@core.flake8ext
@core.off_by_default
def hacking_assert_equal(logical_line, noqa):
r"""Check that self.assertEqual and self.assertNotEqual are used.
Okay: self.assertEqual(x, y)
Okay: self.assertNotEqual(x, y)
H204: self.assertTrue(x == y)
H204: self.assertTrue(x != y)
H204: self.assertFalse(x == y)
H204: self.assertFalse(x != y)
"""
if noqa:
return
methods = ['assertTrue', 'assertFalse']
for method in methods:
start = logical_line.find('.%s' % method) + 1
if start != 0:
break
else:
return
comparisons = [ast.Eq, ast.NotEq]
checker = AssertTrueFalseChecker(methods, comparisons)
checker.visit(ast.parse(logical_line))
if checker.error:
yield start, 'H204: Use assert(Not)Equal()'
@core.flake8ext
@core.off_by_default
def hacking_assert_greater_less(logical_line, noqa):
r"""Check that self.assert{Greater,Less}[Equal] are used.
Okay: self.assertGreater(x, y)
Okay: self.assertGreaterEqual(x, y)
Okay: self.assertLess(x, y)
Okay: self.assertLessEqual(x, y)
H205: self.assertTrue(x > y)
H205: self.assertTrue(x >= y)
H205: self.assertTrue(x < y)
H205: self.assertTrue(x <= y)
"""
if noqa:
return
methods = ['assertTrue', 'assertFalse']
for method in methods:
start = logical_line.find('.%s' % method) + 1
if start != 0:
break
else:
return
comparisons = [ast.Gt, ast.GtE, ast.Lt, ast.LtE]
checker = AssertTrueFalseChecker(methods, comparisons)
checker.visit(ast.parse(logical_line))
if checker.error:
yield start, 'H205: Use assert{Greater,Less}[Equal]'

View File

@@ -1,104 +0,0 @@
# 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.
import re
from hacking import core
RE_RELATIVE_IMPORT = re.compile('^from\s*[.]')
@core.flake8ext
def hacking_import_rules(logical_line, filename, noqa):
r"""Check for imports.
OpenStack HACKING guide recommends one import per line:
Do not import more than one module per line
Examples:
Okay: from nova.compute import api
H301: from nova.compute import api, utils
Do not use wildcard import
Do not make relative imports
Examples:
Okay: from os import path
Okay: from os import path as p
Okay: from os import (path as p)
Okay: import os.path
Okay: from nova.compute import rpcapi
Okay: from six.moves.urllib import parse
H303: from os.path import *
H304: from .compute import rpcapi
"""
# TODO(jogo): make the following doctests pass:
# H301: import os, sys
# TODO(mordred: We need to split this into different checks so that they
# can be disabled by command line switches properly
if noqa:
return
split_line = logical_line.split()
split_line_len = len(split_line)
if (split_line_len > 1 and split_line[0] in ('import', 'from') and
not core.is_import_exception(split_line[1])):
pos = logical_line.find(',')
if pos != -1:
if split_line[0] == 'from':
yield pos, "H301: one import per line"
pos = logical_line.find('*')
if pos != -1:
yield pos, "H303: No wildcard (*) import."
return
if split_line_len in (2, 4, 6) and split_line[1] != "__future__":
if 'from' == split_line[0] and split_line_len > 3:
mod = '.'.join((split_line[1], split_line[3]))
if core.is_import_exception(mod):
return
if RE_RELATIVE_IMPORT.search(logical_line):
yield logical_line.find('.'), (
"H304: No relative imports. '%s' is a relative import"
% logical_line)
return
@core.flake8ext
def hacking_import_alphabetical(logical_line, blank_before, previous_logical,
indent_level, previous_indent_level):
r"""Check for imports in alphabetical order.
OpenStack HACKING guide recommendation for imports:
imports in human alphabetical order
Okay: import os\nimport sys\n\nimport nova\nfrom nova import test
Okay: import os\nimport sys
H306: import sys\nimport os
Okay: import sys\n\n# foo\nimport six
"""
# handle import x
# use .lower since capitalization shouldn't dictate order
if blank_before < 1 and indent_level == previous_indent_level:
split_line = core.import_normalize(logical_line.
strip()).lower().split()
split_previous = core.import_normalize(previous_logical.
strip()).lower().split()
length = [2, 4]
if (len(split_line) in length and len(split_previous) in length and
split_line[0] == "import" and split_previous[0] == "import"):
if split_line[1] < split_previous[1]:
yield (0, "H306: imports not in alphabetical order (%s, %s)"
% (split_previous[1], split_line[1]))

View File

@@ -1,141 +0,0 @@
# 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.
import re
import tokenize
from hacking import core
FORMAT_RE = re.compile("%(?:"
"%|" # Ignore plain percents
"(\(\w+\))?" # mapping key
"([#0 +-]?" # flag
"(?:\d+|\*)?" # width
"(?:\.\d+)?" # precision
"[hlL]?" # length mod
"\w))") # type
class LocalizationError(Exception):
pass
def check_i18n():
"""Generator that checks token stream for localization errors.
Expects tokens to be ``send``ed one by one.
Raises LocalizationError if some error is found.
"""
while True:
try:
token_type, text, _, _, line = yield
except GeneratorExit:
return
if text == "def" and token_type == tokenize.NAME:
# explicitly ignore function definitions, as oslo defines these
return
if (token_type == tokenize.NAME and
text in ["_", "_LI", "_LW", "_LE", "_LC"]):
while True:
token_type, text, start, _, _ = yield
if token_type != tokenize.NL:
break
if token_type != tokenize.OP or text != "(":
continue # not a localization call
format_string = ''
while True:
token_type, text, start, _, _ = yield
if token_type == tokenize.STRING:
format_string += eval(text)
elif token_type == tokenize.NL:
pass
else:
break
if not format_string:
raise LocalizationError(
start, "H701: Empty localization string")
if token_type != tokenize.OP:
raise LocalizationError(
start, "H701: Invalid localization call")
if text != ")":
if text == "%":
raise LocalizationError(
start,
"H702: Formatting operation should be outside"
" of localization method call")
elif text == "+":
raise LocalizationError(
start,
"H702: Use bare string concatenation instead of +")
else:
raise LocalizationError(
start, "H702: Argument to _, _LI, _LW, _LC, or _LE "
"must be just a string")
format_specs = FORMAT_RE.findall(format_string)
positional_specs = [(key, spec) for key, spec in format_specs
if not key and spec]
# not spec means %%, key means %(smth)s
if len(positional_specs) > 1:
raise LocalizationError(
start, "H703: Multiple positional placeholders")
@core.flake8ext
def hacking_localization_strings(logical_line, tokens, noqa):
r"""Check localization in line.
Okay: _("This is fine")
Okay: _LI("This is fine")
Okay: _LW("This is fine")
Okay: _LE("This is fine")
Okay: _LC("This is fine")
Okay: _("This is also fine %s")
Okay: _("So is this %s, %(foo)s") % {foo: 'foo'}
H701: _('')
Okay: def _(msg):\n pass
Okay: def _LE(msg):\n pass
H701: _LI('')
H701: _LW('')
H701: _LE('')
H701: _LC('')
Okay: _('') # noqa
H702: _("Bob" + " foo")
H702: _LI("Bob" + " foo")
H702: _LW("Bob" + " foo")
H702: _LE("Bob" + " foo")
H702: _LC("Bob" + " foo")
Okay: _("Bob" + " foo") # noqa
H702: _("Bob %s" % foo)
H702: _LI("Bob %s" % foo)
H702: _LW("Bob %s" % foo)
H702: _LE("Bob %s" % foo)
H702: _LC("Bob %s" % foo)
H702: _("%s %s" % (foo, bar))
H703: _("%s %s") % (foo, bar)
"""
if noqa:
return
gen = check_i18n()
next(gen)
try:
list(map(gen.send, tokens))
gen.close()
except LocalizationError as e:
yield e.args
# TODO(jogo) Dict and list objects

View File

@@ -1,54 +0,0 @@
# 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.
import re
from hacking import core
log_string_interpolation = re.compile(r".*LOG\.(?:error|warn|warning|info"
r"|critical|exception|debug)"
r"\([^,]*%[^,]*[,)]")
@core.flake8ext
def hacking_no_cr(physical_line):
r"""Check that we only use newlines not carriage returns.
Okay: import os\nimport sys
# pep8 doesn't yet replace \r in strings, will work on an
# upstream fix
H903 import os\r\nimport sys
"""
pos = physical_line.find('\r')
if pos != -1 and pos == (len(physical_line) - 2):
return (pos, "H903: Windows style line endings not allowed in code")
@core.flake8ext
@core.off_by_default
def hacking_delayed_string_interpolation(logical_line, noqa):
r"""String interpolation should be delayed at logging calls.
H904: LOG.debug('Example: %s' % 'bad')
Okay: LOG.debug('Example: %s', 'good')
"""
msg = ("H904: String interpolation should be delayed to be "
"handled by the logging code, rather than being done "
"at the point of the logging call. "
"Use ',' instead of '%'.")
if noqa:
return
if log_string_interpolation.match(logical_line):
yield 0, msg

View File

@@ -1,228 +0,0 @@
# 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.
import re
import tokenize
from hacking import core
RE_OCTAL = re.compile(r"0+([1-9]\d*)")
RE_PRINT = re.compile(r"\bprint(?:$|\s+[^\(])")
@core.skip_on_py3
@core.flake8ext
def hacking_python3x_except_compatible(logical_line, noqa):
r"""Check for except statements to be Python 3.x compatible
As of Python 3.x, the construct 'except x,y:' has been removed.
Use 'except x as y:' instead.
Okay: try:\n pass\nexcept Exception:\n pass
Okay: try:\n pass\nexcept (Exception, AttributeError):\n pass
H231: try:\n pass\nexcept AttributeError, e:\n pass
Okay: try:\n pass\nexcept AttributeError, e: # noqa\n pass
"""
if noqa:
return
def is_old_style_except(logical_line):
return (',' in logical_line and
')' not in logical_line.rpartition(',')[2])
if (logical_line.startswith("except ") and
logical_line.endswith(':') and
is_old_style_except(logical_line)):
yield 0, "H231: Python 3.x incompatible 'except x,y:' construct"
@core.skip_on_py3
@core.flake8ext
def hacking_python3x_octal_literals(logical_line, tokens, noqa):
r"""Check for octal literals in Python 3.x compatible form.
As of Python 3.x, the construct "0755" has been removed.
Use "0o755" instead".
Okay: f(0o755)
Okay: 'f(0755)'
Okay: f(755)
Okay: f(0)
Okay: f(000)
Okay: MiB = 1.0415
H232: f(0755)
Okay: f(0755) # noqa
"""
if noqa:
return
for token_type, text, _, _, _ in tokens:
if token_type == tokenize.NUMBER:
match = RE_OCTAL.match(text)
if match:
yield 0, ("H232: Python 3.x incompatible octal %s should be "
"written as 0o%s " %
(match.group(0)[1:], match.group(1)))
@core.skip_on_py3
@core.flake8ext
def hacking_python3x_print_function(logical_line, noqa):
r"""Check that all print occurrences look like print functions.
Check that all occurrences of print look like functions, not
print operator. As of Python 3.x, the print operator has
been removed.
Okay: print(msg)
Okay: print (msg)
Okay: print msg # noqa
Okay: print()
H233: print msg
H233: print >>sys.stderr, "hello"
H233: print msg,
H233: print
"""
if noqa:
return
for match in RE_PRINT.finditer(logical_line):
yield match.start(0), (
"H233: Python 3.x incompatible use of print operator")
@core.flake8ext
def hacking_no_assert_equals(logical_line, tokens, noqa):
r"""assert(Not)Equals() is deprecated, use assert(Not)Equal instead.
Okay: self.assertEqual(0, 0)
Okay: self.assertNotEqual(0, 1)
H234: self.assertEquals(0, 0)
H234: self.assertNotEquals(0, 1)
Okay: self.assertEquals(0, 0) # noqa
Okay: self.assertNotEquals(0, 1) # noqa
"""
if noqa:
return
for token_type, text, start_index, _, _ in tokens:
if token_type == tokenize.NAME:
if text == "assertEquals" or text == "assertNotEquals":
yield (start_index[1],
"H234: %s is deprecated, use %s" % (text, text[:-1]))
@core.flake8ext
def hacking_no_assert_underscore(logical_line, tokens, noqa):
r"""assert_() is deprecated, use assertTrue instead.
Okay: self.assertTrue(foo)
H235: self.assert_(foo)
Okay: self.assert_(foo) # noqa
"""
if noqa:
return
for token_type, text, start_index, _, _ in tokens:
if token_type == tokenize.NAME and text == "assert_":
yield (
start_index[1],
"H235: assert_ is deprecated, use assertTrue")
@core.flake8ext
def hacking_python3x_metaclass(logical_line, noqa):
r"""Check for metaclass to be Python 3.x compatible.
Okay: @six.add_metaclass(Meta)\nclass Foo(object):\n pass
Okay: @six.with_metaclass(Meta)\nclass Foo(object):\n pass
Okay: class Foo(object):\n '''docstring\n\n __metaclass__ = Meta\n'''
H236: class Foo(object):\n __metaclass__ = Meta
H236: class Foo(object):\n foo=bar\n __metaclass__ = Meta
H236: class Foo(object):\n '''docstr.'''\n __metaclass__ = Meta
H236: class Foo(object):\n __metaclass__ = \\\n Meta
Okay: class Foo(object):\n __metaclass__ = Meta # noqa
"""
if noqa:
return
split_line = logical_line.split()
if(len(split_line) > 2 and split_line[0] == '__metaclass__' and
split_line[1] == '='):
yield (logical_line.find('__metaclass__'),
"H236: Python 3.x incompatible __metaclass__, "
"use six.add_metaclass()")
# NOTE(guochbo): This is removed module list:
# http://python3porting.com/stdlib.html#removed-modules
removed_modules = [
'audiodev', 'Bastion', 'bsddb185', 'bsddb3',
'Canvas', 'cfmfile', 'cl', 'commands', 'compiler'
'dircache', 'dl', 'exception', 'fpformat',
'htmllib', 'ihooks', 'imageop', 'imputil'
'linuxaudiodev', 'md5', 'mhlib', 'mimetools'
'MimeWriter', 'mimify', 'multifile', 'mutex',
'new', 'popen2', 'posixfile', 'pure', 'rexec'
'rfc822', 'sha', 'sgmllib', 'sre', 'stat'
'stringold', 'sunaudio' 'sv', 'test.testall',
'thread', 'timing', 'toaiff', 'user'
]
@core.flake8ext
def hacking_no_removed_module(logical_line, noqa):
r"""Check for removed modules in Python 3.
Examples:
Okay: from os import path
Okay: from os import path as p
Okay: from os import (path as p)
Okay: import os.path
H237: import thread
Okay: import thread # noqa
H237: import commands
H237: import md5 as std_md5
"""
if noqa:
return
line = core.import_normalize(logical_line.strip())
if line and line.split()[0] == 'import':
module_name = line.split()[1].split('.')[0]
if module_name in removed_modules:
yield 0, ("H237: module %s is "
"removed in Python 3" % module_name)
RE_NEW_STYLE_CLASS = re.compile(r"^class [^(]+\(.+\):")
@core.flake8ext
def hacking_no_old_style_class(logical_line, noqa):
r"""Check for old style classes.
Examples:
Okay: class Foo(object):\n pass
Okay: class Foo(Bar, Baz):\n pass
Okay: class Foo(object, Baz):\n pass
Okay: class Foo(somefunc()):\n pass
H238: class Bar:\n pass
H238: class Bar():\n pass
"""
if noqa:
return
line = core.import_normalize(logical_line.strip())
if line.startswith("class ") and not RE_NEW_STYLE_CLASS.match(line):
yield (0, "H238: old style class declaration, "
"use new style (inherit from `object`)")

View File

@@ -1,39 +0,0 @@
# 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.
import re
from hacking import core
vim_header_re = re.compile(r"^#\s+vim?:.+")
@core.flake8ext
@core.off_by_default
def no_vim_headers(physical_line, line_number, lines):
r"""Check for vim editor configuration in source files.
By default vim modelines can only appear in the first or
last 5 lines of a source file.
Examples:
H106: # vim: set tabstop=4 shiftwidth=4\n#\n#\n#\n#\n#
H106: # Lic\n# vim: set tabstop=4 shiftwidth=4\n#\n#\n#\n#\n#
H106: # Lic\n#\n#\n#\n#\n#\n#\n#\n#\n# vim: set tabstop=4 shiftwidth=4
Okay: # Lic\n#\n#\n#\n#\n#\n#\n#
Okay: # viminal hill is located in Rome
Okay: # vim, ze nemluvis cesky
"""
if ((line_number <= 5 or line_number > len(lines) - 5) and
vim_header_re.match(physical_line)):
return 0, "H106: Don't put vim configuration in source files"

View File

@@ -1,48 +0,0 @@
# 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.
from six import moves
class Config(object):
def __init__(self, default_section=None, tox_file='tox.ini'):
conf = moves.configparser.RawConfigParser()
conf.read(tox_file)
self.conf = conf
self.default_section = default_section
def get(self, option, section=None, default=None):
section = section or self.default_section
if not self.conf.has_section(section):
return default
if self.conf.has_option(section, option):
return self.conf.get(section, option).strip()
return default
def get_multiple(self, option, section=None, default=None):
section = section or self.default_section
values = self.get(option, section)
if not values:
return default
values = [v.strip() for v in values.split('\n') if v.strip()]
result = []
for vals in values:
result.extend([v.strip() for v in vals.split(',') if v.strip()])
return result

View File

@@ -1,164 +0,0 @@
# Copyright (c) 2012, Cloudscaling
# 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.
"""OpenStack HACKING file compliance testing
Built as a sets of pep8 checks using flake8.
"""
import gettext
import sys
import pbr.util
import pep8
from hacking import config
# Import tests need to inject _ properly into the builtins
kwargs = {}
if sys.version_info[0] < 3:
# In Python2, ensure that the _() that gets installed into built-ins
# always returns unicodes. This matches the default behavior under Python
# 3, although the keyword argument is not present in the Python 3 API.
kwargs['unicode'] = True
gettext.install('hacking', **kwargs)
def flake8ext(f):
f.name = __name__
f.version = '0.0.1'
f.skip_on_py3 = False
if not hasattr(f, 'off_by_default'):
f.off_by_default = False
return f
def off_by_default(f):
"""Decorator to turn check off by default.
To enable the check use the flake8 select setting in
tox.ini.
flake8 documentation:
http://flake8.readthedocs.org/en/latest/extensions.html.
"""
f.off_by_default = True
return f
def skip_on_py3(f):
f.skip_on_py3 = True
return f
# Error code block layout
# H1xx comments
# H20x except
# H23x Python 2.x -> 3.x portability issues
# H3xx imports
# H4xx docstrings
# H5xx dictionaries/lists
# H6xx calling methods
# H7xx localization
# H8xx git commit messages
# H9xx other
CONF = config.Config('hacking')
DEFAULT_IMPORT_EXCEPTIONS = [
'sqlalchemy',
'migrate',
]
IMPORT_EXCEPTIONS = CONF.get_multiple('import_exceptions', default=[])
IMPORT_EXCEPTIONS += DEFAULT_IMPORT_EXCEPTIONS
def is_import_exception(mod):
"""Check module name to see if import has been whitelisted.
Import based rules should not run on any whitelisted module
"""
return (mod in IMPORT_EXCEPTIONS or
any(mod.startswith(m + '.') for m in IMPORT_EXCEPTIONS))
def import_normalize(line):
# convert "from x import y" to "import x.y"
# handle "from x import y as z" to "import x.y as z"
split_line = line.split()
if ("import" in line and line.startswith("from ") and "," not in line and
split_line[2] == "import" and split_line[3] != "*" and
split_line[1] != "__future__" and
(len(split_line) == 4 or
(len(split_line) == 6 and split_line[4] == "as"))):
return "import %s.%s" % (split_line[1], split_line[3])
else:
return line
class GlobalCheck(object):
"""Base class for checks that should be run only once."""
name = None
version = '0.0.1'
skip_on_py3 = False
_has_run = set()
def __init__(self, tree, *args):
pass
def run(self):
"""Make run a no-op if run() has been called before.
Store in a global registry the list of checks we've run. If we have
run that one before, just skip doing anything the subsequent times.
This way, since pep8 is file/line based, we don't wind up re-running
a check on a git commit message over and over again.
"""
if self.name and self.name not in self.__class__._has_run:
self.__class__._has_run.add(self.name)
ret = self.run_once()
if ret is not None:
yield ret
def run_once(self):
pass
class ProxyChecks(GlobalCheck):
"""Provide a mechanism for locally defined checks."""
name = 'ProxyChecker'
@classmethod
def add_options(cls, parser):
# We're looking for local checks, so we need to include the local
# dir in the search path
sys.path.append('.')
local_check = CONF.get_multiple('local-check', default=[])
for check_path in set(local_check):
if check_path.strip():
checker = pbr.util.resolve_name(check_path)
pep8.register_check(checker)
local_check_fact = CONF.get('local-check-factory')
if local_check_fact:
factory = pbr.util.resolve_name(local_check_fact)
factory(pep8.register_check)
sys.path.pop()

View File

@@ -1,58 +0,0 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
import os
import fixtures
import testtools
_TRUE_VALUES = ('True', 'true', '1', 'yes')
class TestCase(testtools.TestCase):
"""Test case base class for all unit tests."""
def setUp(self):
"""Run before each test method to initialize test environment."""
super(TestCase, self).setUp()
test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
try:
test_timeout = int(test_timeout)
except ValueError:
# If timeout value is invalid do not set a timeout.
test_timeout = 0
if test_timeout > 0:
self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES:
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES:
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
def assertCheckFails(self, check_func, *args, **kwargs):
if not list(check_func(*args, **kwargs)):
raise AssertionError("Check %s did not fail." %
check_func.__name__)
def assertCheckPasses(self, check_func, *args, **kwargs):
try:
self.assertCheckFails(check_func, *args, **kwargs)
except AssertionError:
return
else:
raise AssertionError("Check %s failed." % check_func.__name__)

View File

@@ -1,84 +0,0 @@
# Copyright (c) 2013 eNovance
#
# 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.
from hacking.checks import comments
from hacking import tests
class CoreTestCase(tests.TestCase):
def test_H104_regex(self):
"""Verify that the H104 regex matches correct lines."""
self.assertTrue(comments.hacking_has_only_comments(
None,
None,
['# foo',
'# bar'],
1))
self.assertTrue(comments.hacking_has_only_comments(
None,
None,
['# foo',
'# bar',
''],
1))
self.assertTrue(comments.hacking_has_only_comments(
None,
None,
['# foo',
' ',
'# bar'],
1))
self.assertIsNone(comments.hacking_has_only_comments(
None,
None,
['# foo',
' ',
'"""foobar"""'],
1))
self.assertIsNone(comments.hacking_has_only_comments(
None,
None,
['# foo',
'',
'print(42)'],
1))
self.assertIsNone(comments.hacking_has_only_comments(
None,
None,
['# foo'],
100))
def test_H105(self):
self.assertTrue(comments.hacking_no_author_tags(
'# @author: Foo Bar'))
self.assertTrue(comments.hacking_no_author_tags(
'# @Author: Foo Bar'))
self.assertTrue(comments.hacking_no_author_tags(
'# author: Foo Bar'))
self.assertTrue(comments.hacking_no_author_tags(
'# authors: Foo Bar'))
self.assertTrue(comments.hacking_no_author_tags(
'# Author: Foo Bar'))
self.assertTrue(comments.hacking_no_author_tags(
'# Authors: Foo Bar'))
self.assertTrue(comments.hacking_no_author_tags(
'.. moduleauthor:: Foo Bar'))

View File

@@ -1,58 +0,0 @@
# 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.
import os
import fixtures
from hacking import config
from hacking import tests
TEST_TOX_INI = """[hacking]
option_1 = val_1
option_2 =
val_2
option_3 =
val_1,val_2, val_3,
val_4 , val_5 , val_6,
val_7
,
val_8
val_9
"""
class ConfigTest(tests.TestCase):
def setUp(self):
tox_ini_path = os.path.join(self.useFixture(fixtures.TempDir()).path,
'tox.ini')
with open(tox_ini_path, 'w') as tox_ini:
tox_ini.write(TEST_TOX_INI)
self.conf = config.Config('hacking', tox_ini_path)
super(ConfigTest, self).setUp()
def test_get(self):
self.assertEqual('val_1', self.conf.get('option_1'))
self.assertEqual('val_2', self.conf.get('option_2'))
self.assertEqual('val_3', self.conf.get('option_4', default='val_3'))
def test_get_multiple(self):
self.assertEqual(['val_1', 'val_2', 'val_3', 'val_4', 'val_5', 'val_6',
'val_7', 'val_8', 'val_9'],
self.conf.get_multiple('option_3'))
self.assertEqual(['val_1', 'val_2'],
self.conf.get_multiple('option_4',
default=['val_1', 'val_2']))

View File

@@ -1,97 +0,0 @@
#!/usr/bin/env python
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
import re
from flake8 import engine
import pep8
import pkg_resources
import six
import testscenarios
from testtools import content
from testtools import matchers
import hacking
import hacking.tests
SELFTEST_REGEX = re.compile(r'\b(Okay|[HEW]\d{3}):\s(.*)')
# Each scenario is (name, dict(lines=.., options=..., code=...))
file_cases = []
class HackingTestCase(hacking.tests.TestCase):
scenarios = file_cases
def test_pep8(self):
# NOTE(jecarey): Add tests marked as off_by_default to enable testing
turn_on = set(['H106', 'H203', 'H904', 'H204', 'H205'])
if self.options.select:
turn_on.update(self.options.select)
self.options.select = tuple(turn_on)
report = pep8.BaseReport(self.options)
checker = pep8.Checker(lines=self.lines, options=self.options,
report=report)
checker.check_all()
self.addDetail('doctest', content.text_content(self.raw))
if self.code == 'Okay':
self.assertThat(
len(report.counters),
matchers.Not(matchers.GreaterThan(
len(self.options.benchmark_keys))),
"incorrectly found %s" % ', '.join(
[key for key in report.counters
if key not in self.options.benchmark_keys]))
else:
self.addDetail('reason',
content.text_content("Failed to trigger rule %s" %
self.code))
self.assertIn(self.code, report.counters)
def _get_lines(check):
for line in check.__doc__.splitlines():
line = line.lstrip()
match = SELFTEST_REGEX.match(line)
if match is None:
continue
yield (line, match.groups())
def load_tests(loader, tests, pattern):
flake8_style = engine.get_style_guide(parse_argv=False,
# Ignore H104 otherwise it's
# raised on doctests.
ignore=('F', 'H104'))
options = flake8_style.options
for entry in pkg_resources.iter_entry_points('flake8.extension'):
if not entry.module_name.startswith('hacking.'):
continue
check = entry.load()
name = entry.attrs[0]
if check.skip_on_py3 and six.PY3:
continue
for (lineno, (raw, (code, source))) in enumerate(_get_lines(check)):
lines = [part.replace(r'\t', '\t') + '\n'
for part in source.split(r'\n')]
file_cases.append(("%s-%s-line-%s" % (entry.name, name, lineno),
dict(lines=lines, raw=raw, options=options,
code=code)))
return testscenarios.load_tests_apply_scenarios(loader, tests, pattern)

View File

@@ -1,53 +0,0 @@
# 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.
import os
import fixtures
from hacking import config
from hacking import core
from hacking import tests
TEST_TOX_INI = """[hacking]
import_exceptions =
a.b.c
z.x
"""
class ImportExceptionsTest(tests.TestCase):
def _setUpConfig(self, content):
tox_ini_path = os.path.join(self.useFixture(fixtures.TempDir()).path,
'tox.ini')
with open(tox_ini_path, 'w') as tox_ini:
tox_ini.write(content)
return config.Config('hacking', tox_ini_path)
def setUp(self):
super(ImportExceptionsTest, self).setUp()
def test_default_import_exceptions(self):
conf = self._setUpConfig("")
self.assertEqual(core.DEFAULT_IMPORT_EXCEPTIONS,
conf.get_multiple(
'import_exceptions',
default=core.DEFAULT_IMPORT_EXCEPTIONS))
def test_import_exceptions(self):
conf = self._setUpConfig(TEST_TOX_INI)
self.assertEqual(['a.b.c', 'z.x'],
conf.get_multiple('import_exceptions'))

View File

@@ -1,36 +0,0 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
from flake8 import engine
import pep8
import hacking.tests
def check(physical_line):
"""Test check to make sure local-checks are working."""
if physical_line.strip() == "#this-is-the-test-phrase":
return (0, "L100: Found local-check test case")
class HackingTestCase(hacking.tests.TestCase):
def test_local_check(self):
flake8_style = engine.get_style_guide(parse_argv=False, ignore='F')
report = pep8.BaseReport(flake8_style.options)
line = ["#this-is-the-test-phrase"]
checker = pep8.Checker(lines=line, options=flake8_style.options,
report=report)
checker.check_all()
self.assertIn("L100", report.counters)

View File

@@ -1,46 +0,0 @@
#!/bin/bash
# Usage: test.sh openstack keystone path-to-repo
# path-to-repo is an optional parameter, if it exists
# no cloning will happen and the local directory will be used,
# the first two parameter get ignored.
# Note: you can clone from a local file with REPO_ROOT=file:////~/path/to/repo
set -x
set -e
REPO_ROOT=${REPO_ROOT:-git://git.openstack.org}
if [[ $# -lt 2 ]] ; then
echo "Script needs at least two arguments:"
echo "$0 organization name [path-to-repo]"
exit 1
fi
org=$1
project=$2
if [[ $# -eq 3 ]] ; then
projectdir=$3
clone=0
else
projectdir=$project
clone=1
fi
if [ "$clone" = "1" ] ; then
tempdir="$(mktemp -d)"
trap "rm -rf $tempdir" EXIT
pushd $tempdir
git clone $REPO_ROOT/$org/$project --depth=1
fi
pushd $projectdir
set +e
flake8 --select H --statistics
popd
if [ "$clone" = "1" ] ; then
popd
fi

View File

@@ -1,283 +0,0 @@
# -*- coding: utf-8 -*-
# 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.
import pbr.version
# Hacking Release Notes documentation build configuration file, created by
# sphinx-quickstart on Tue Nov 3 17:40:50 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'openstackdocstheme',
'reno.sphinxext',
]
# openstackdocstheme options
repository_name = 'openstack-dev/hacking'
bug_project = 'hacking'
bug_tag = ''
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Hacking Release Notes'
copyright = u'2015, Hacking Developers'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
version_info = pbr.version.VersionInfo('hacking')
# The short X.Y version.
release = version_info.version_string_with_vcs()
# The short X.Y version.
version = version_info.canonical_version_string()
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all
# documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'openstackdocs'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
# html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'HackingReleaseNotesdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'HackingReleaseNotes.tex',
u'Hacking Release Notes Documentation',
u'Hacking Developers', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'hackingreleasenotes', u'Hacking Release Notes Documentation',
[u'Hacking Developers'], 1)
]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'HackingReleaseNotes', u'Hacking Release Notes Documentation',
u'Hacking Developers', 'HackingReleaseNotes',
'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
# texinfo_no_detailmenu = False
# -- Options for Internationalization output ------------------------------
locale_dirs = ['locale/']

View File

@@ -1,8 +0,0 @@
=======================
Hacking Release Notes
=======================
.. toctree::
:maxdepth: 1
unreleased

View File

@@ -1,5 +0,0 @@
==============================
Current Series Release Notes
==============================
.. release-notes::

View File

@@ -1,11 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=1.8 # Apache-2.0
pep8==1.5.7 # MIT
pyflakes==0.8.1 # MIT
flake8<2.6.0,>=2.5.4 # MIT
mccabe==0.2.1 # MIT License
six>=1.9.0 # MIT

View File

@@ -1,77 +0,0 @@
[metadata]
name = hacking
author = OpenStack
author-email = openstack-dev@lists.openstack.org
summary = OpenStack Hacking Guideline Enforcement
description-file =
README.rst
home-page = https://docs.openstack.org/hacking/latest/
classifier =
Development Status :: 4 - Beta
Environment :: Console
Environment :: OpenStack
Intended Audience :: Developers
Intended Audience :: Information Technology
License :: OSI Approved :: Apache Software License
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
[files]
packages =
hacking
[entry_points]
flake8.extension =
H000 = hacking.core:ProxyChecks
H101 = hacking.checks.comments:hacking_todo_format
H102 = hacking.checks.comments:hacking_has_license
H103 = hacking.checks.comments:hacking_has_correct_license
H104 = hacking.checks.comments:hacking_has_only_comments
H105 = hacking.checks.comments:hacking_no_author_tags
H106 = hacking.checks.vim_check:no_vim_headers
H201 = hacking.checks.except_checks:hacking_except_format
H202 = hacking.checks.except_checks:hacking_except_format_assert
H203 = hacking.checks.except_checks:hacking_assert_is_none
H204 = hacking.checks.except_checks:hacking_assert_equal
H205 = hacking.checks.except_checks:hacking_assert_greater_less
H231 = hacking.checks.python23:hacking_python3x_except_compatible
H232 = hacking.checks.python23:hacking_python3x_octal_literals
H233 = hacking.checks.python23:hacking_python3x_print_function
H234 = hacking.checks.python23:hacking_no_assert_equals
H235 = hacking.checks.python23:hacking_no_assert_underscore
H236 = hacking.checks.python23:hacking_python3x_metaclass
H237 = hacking.checks.python23:hacking_no_removed_module
H238 = hacking.checks.python23:hacking_no_old_style_class
H301 = hacking.checks.imports:hacking_import_rules
H306 = hacking.checks.imports:hacking_import_alphabetical
H401 = hacking.checks.docstrings:hacking_docstring_start_space
H403 = hacking.checks.docstrings:hacking_docstring_multiline_end
H404 = hacking.checks.docstrings:hacking_docstring_multiline_start
H405 = hacking.checks.docstrings:hacking_docstring_summary
H501 = hacking.checks.dictlist:hacking_no_locals
H700 = hacking.checks.localization:hacking_localization_strings
H903 = hacking.checks.other:hacking_no_cr
H904 = hacking.checks.other:hacking_delayed_string_interpolation
[extras]
pep257 =
flake8-docstrings==0.2.1.post1 # MIT
[build_sphinx]
all_files = 1
build-dir = doc/build
source-dir = doc/source
warning-is-error = 1
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
[wheel]
universal = 1

View File

@@ -1,29 +0,0 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr>=1.8'],
pbr=True)

View File

@@ -1,21 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
coverage>=4.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD
mock>=2.0 # BSD
python-subunit>=0.0.18 # Apache-2.0/BSD
sphinx>=1.6.2 # BSD
openstackdocstheme>=1.11.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
# hacking doesn't use this anywhere, but nova imports this in nova/__init__.py
# since eventlet is such a common universal import, add it to the hacking test
# virtualenv, so importing things like 'nova.hacking.checks.factory' will just
# work.
# See https://bugs.launchpad.net/hacking/+bug/1403270
eventlet!=0.18.3,>=0.18.2 # MIT
reno>=1.8.0 # Apache-2.0

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env bash
# Client constraint file contains this client version pin that is in conflict
# with installing the client from source. We should remove the version pin in
# the constraints file before applying it for from-source installation.
CONSTRAINTS_FILE="$1"
shift 1
set -e
# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
# published to logs.openstack.org for easy debugging.
localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
if [[ "$CONSTRAINTS_FILE" != http* ]]; then
CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE"
fi
# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep
curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile"
pip install -c"$localfile" openstack-requirements
# This is the main purpose of the script: Allow local installation of
# the current repo. It is listed in constraints file and thus any
# install will be constrained and we need to unconstrain it.
edit-constraints "$localfile" -- "$CLIENT_NAME"
pip install -c"$localfile" -U "$@"
exit $?

48
tox.ini
View File

@@ -1,48 +0,0 @@
[tox]
minversion = 2.0
skipsdist = True
envlist = py{35,34,27},pep8,pypy
[testenv]
usedevelop = True
install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
BRANCH_NAME=master
CLIENT_NAME=hacking
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
python setup.py testr --slowest --testr-args='{posargs}'
[tox:jenkins]
sitepackages = True
[testenv:pep8]
commands = flake8 {posargs}
[testenv:integration]
whitelist_externals = bash
commands =
bash integration-test/test.sh {posargs}
[testenv:cover]
commands =
python setup.py testr --coverage
[testenv:venv]
commands = {posargs}
[testenv:docs]
commands = python setup.py build_sphinx
[testenv:releasenotes]
commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
[flake8]
exclude = .venv,.tox,dist,doc,*.egg,build
show-source = true
enable-extensions = H106
[hacking]
local-check = hacking.tests.test_local.check