Introduce in-tree Rally specs
Most of OpenStack projects have specs. Specs are documents in .rst format with fixed structure. They allow developers to use standard gerrit code review approach to discuss and align future changes in project. This patch includes: - doc/specs directory - doc/specs readme files - spec template.rst - unit tests that checks that specs are regarding template Change-Id: I31e0dbb8bcbc68ca40c67c643d44b27359906b9c
This commit is contained in:
parent
f80f35ac6c
commit
0538b6f845
14
doc/specs/README.rst
Normal file
14
doc/specs/README.rst
Normal file
@ -0,0 +1,14 @@
|
||||
Rally Specs
|
||||
===========
|
||||
|
||||
Specs are detailed description of proposed changes in project. Usually they
|
||||
answer on what, why, how to change in project and who is going to work on
|
||||
change.
|
||||
|
||||
This directory contains 2 subdirectories:
|
||||
|
||||
- in-progress - These specs are approved, but they are not implemented yet
|
||||
- implemented - Implemented specs archive
|
||||
|
||||
If you are looking for full rally road map overview go `here <https://docs.google.com/a/mirantis.com/spreadsheets/d/16DXpfbqvlzMFaqaXAcJsBzzpowb_XpymaK2aFY2gA2g/edit#gid=0>`_.
|
||||
|
10
doc/specs/implemented/README.rst
Normal file
10
doc/specs/implemented/README.rst
Normal file
@ -0,0 +1,10 @@
|
||||
Rally Specs
|
||||
===========
|
||||
|
||||
Specs are detailed description of proposed changes in project. Usually they
|
||||
answer on what, why, how to change in project and who is going to work on
|
||||
change.
|
||||
|
||||
This directory contains files with implemented specs, 1 file is 1 spec.
|
||||
|
||||
If you are looking for full rally road map overview go `here <https://docs.google.com/a/mirantis.com/spreadsheets/d/16DXpfbqvlzMFaqaXAcJsBzzpowb_XpymaK2aFY2gA2g/edit#gid=0>`_.
|
12
doc/specs/in-progress/README.rst
Normal file
12
doc/specs/in-progress/README.rst
Normal file
@ -0,0 +1,12 @@
|
||||
Rally Specs
|
||||
===========
|
||||
|
||||
Specs are detailed description of proposed changes in project. Usually they
|
||||
answer on what, why, how to change in project and who is going to work on
|
||||
change.
|
||||
|
||||
|
||||
This directory contains files with accepted by not implemented specs,
|
||||
1 file is 1 spec.
|
||||
|
||||
If you are looking for full rally road map overview go `here <https://docs.google.com/a/mirantis.com/spreadsheets/d/16DXpfbqvlzMFaqaXAcJsBzzpowb_XpymaK2aFY2gA2g/edit#gid=0>`_.
|
89
doc/specs/template.rst
Normal file
89
doc/specs/template.rst
Normal file
@ -0,0 +1,89 @@
|
||||
..
|
||||
This work is licensed under a Creative Commons Attribution 3.0 Unported
|
||||
License.
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/legalcode
|
||||
|
||||
..
|
||||
This template should be in ReSTructured text. The filename in the git
|
||||
repository should match the launchpad URL, for example a URL of
|
||||
https://blueprints.launchpad.net/heat/+spec/awesome-thing should be named
|
||||
awesome-thing.rst . Please do not delete any of the sections in this
|
||||
template. If you have nothing to say for a whole section, just write: None
|
||||
For help with syntax, see http://sphinx-doc.org/rest.html
|
||||
To test out your formatting, see http://www.tele3.cz/jbar/rest/rest.html
|
||||
|
||||
=======================
|
||||
The title of your Spec
|
||||
=======================
|
||||
|
||||
Rally Road map:
|
||||
|
||||
https://docs.google.com/a/mirantis.com/spreadsheets/d/16DXpfbqvlzMFaqaXAcJsBzzpowb_XpymaK2aFY2gA2g/edit#gid=0
|
||||
|
||||
|
||||
Introduction paragraph -- why are we doing anything?
|
||||
|
||||
Problem description
|
||||
===================
|
||||
|
||||
A detailed description of the problem.
|
||||
|
||||
Proposed change
|
||||
===============
|
||||
|
||||
Here is where you cover the change you propose to make in detail. How do you
|
||||
propose to solve this problem?
|
||||
|
||||
If this is one part of a larger effort make it clear where this piece ends. In
|
||||
other words, what's the scope of this effort?
|
||||
|
||||
Include where in the heat tree hierarchy this will reside.
|
||||
|
||||
Alternatives
|
||||
------------
|
||||
|
||||
This is an optional section, where it does apply we'd just like a demonstration
|
||||
that some thought has been put into why the proposed approach is the best one.
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
Assignee(s)
|
||||
-----------
|
||||
|
||||
Who is leading the writing of the code? Or is this a blueprint where you're
|
||||
throwing it out there to see who picks it up?
|
||||
|
||||
If more than one person is working on the implementation, please designate the
|
||||
primary author and contact.
|
||||
|
||||
Primary assignee:
|
||||
<launchpad-id or None>
|
||||
|
||||
Can optionally can list additional ids if they intend on doing
|
||||
substantial implementation work on this blueprint.
|
||||
|
||||
Milestones
|
||||
----------
|
||||
|
||||
Target Milestone for completion:
|
||||
Juno-1
|
||||
|
||||
Work Items
|
||||
----------
|
||||
|
||||
Work items or tasks -- break the feature up into the things that need to be
|
||||
done to implement it. Those parts might end up being done by different people,
|
||||
but we're mostly trying to understand the timeline for implementation.
|
||||
|
||||
|
||||
Dependencies
|
||||
============
|
||||
|
||||
- Include specific references to specs and/or blueprints in heat, or in other
|
||||
projects, that this one either depends on or is related to.
|
||||
|
||||
- Does this feature require any new library dependencies or code otherwise not
|
||||
included in OpenStack? Or does it depend on a specific version of library?
|
||||
|
119
tests/unit/doc/test_specs.py
Normal file
119
tests/unit/doc/test_specs.py
Normal file
@ -0,0 +1,119 @@
|
||||
# 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 glob
|
||||
import os
|
||||
import re
|
||||
|
||||
import docutils.core
|
||||
|
||||
from tests.unit import test
|
||||
|
||||
|
||||
class TitlesTestCase(test.TestCase):
|
||||
|
||||
specs_path = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
os.pardir, os.pardir, os.pardir,
|
||||
"doc", "specs")
|
||||
|
||||
def _get_title(self, section_tree):
|
||||
section = {"subtitles": []}
|
||||
for node in section_tree:
|
||||
if node.tagname == "title":
|
||||
section["name"] = node.rawsource
|
||||
elif node.tagname == "section":
|
||||
subsection = self._get_title(node)
|
||||
section["subtitles"].append(subsection["name"])
|
||||
return section
|
||||
|
||||
def _get_titles(self, spec):
|
||||
titles = {}
|
||||
for node in spec:
|
||||
if node.tagname == "section":
|
||||
# Note subsection subtitles are thrown away
|
||||
section = self._get_title(node)
|
||||
titles[section["name"]] = section["subtitles"]
|
||||
return titles
|
||||
|
||||
def _check_titles(self, filename, expect, actual):
|
||||
missing_sections = [x for x in expect.keys() if x not in actual.keys()]
|
||||
extra_sections = [x for x in actual.keys() if x not in expect.keys()]
|
||||
|
||||
msgs = []
|
||||
if len(missing_sections) > 0:
|
||||
msgs.append("Missing sections: %s" % missing_sections)
|
||||
if len(extra_sections) > 0:
|
||||
msgs.append("Extra sections: %s" % extra_sections)
|
||||
|
||||
for section in expect.keys():
|
||||
missing_subsections = [x for x in expect[section]
|
||||
if x not in actual.get(section, {})]
|
||||
# extra subsections are allowed
|
||||
if len(missing_subsections) > 0:
|
||||
msgs.append("Section '%s' is missing subsections: %s"
|
||||
% (section, missing_subsections))
|
||||
|
||||
if len(msgs) > 0:
|
||||
self.fail("While checking '%s':\n %s"
|
||||
% (filename, "\n ".join(msgs)))
|
||||
|
||||
def _check_lines_wrapping(self, tpl, raw):
|
||||
for i, line in enumerate(raw.split("\n")):
|
||||
if "http://" in line or "https://" in line:
|
||||
continue
|
||||
self.assertTrue(
|
||||
len(line) < 80,
|
||||
msg="%s:%d: Line limited to a maximum of 79 characters." %
|
||||
(tpl, i+1))
|
||||
|
||||
def _check_no_cr(self, tpl, raw):
|
||||
matches = re.findall("\r", raw)
|
||||
self.assertEqual(
|
||||
len(matches), 0,
|
||||
"Found %s literal carriage returns in file %s" %
|
||||
(len(matches), tpl))
|
||||
|
||||
def _check_trailing_spaces(self, tpl, raw):
|
||||
for i, line in enumerate(raw.split("\n")):
|
||||
trailing_spaces = re.findall(" +$", line)
|
||||
self.assertEqual(
|
||||
len(trailing_spaces), 0,
|
||||
"Found trailing spaces on line %s of %s" % (i+1, tpl))
|
||||
|
||||
def test_template(self):
|
||||
with open(os.path.join(self.specs_path, "template.rst")) as f:
|
||||
template = f.read()
|
||||
|
||||
spec = docutils.core.publish_doctree(template)
|
||||
template_titles = self._get_titles(spec)
|
||||
|
||||
for d in ["implemented", "in-progress"]:
|
||||
spec_dir = "%s/%s" % (self.specs_path, d)
|
||||
|
||||
self.assertTrue(os.path.isdir(spec_dir),
|
||||
"%s is not a directory" % spec_dir)
|
||||
for filename in glob.glob(spec_dir + "/*"):
|
||||
if filename.endswith("README.rst"):
|
||||
continue
|
||||
|
||||
self.assertTrue(
|
||||
filename.endswith(".rst"),
|
||||
"spec's file must have .rst ext. Found: %s" % filename)
|
||||
with open(filename) as f:
|
||||
data = f.read()
|
||||
|
||||
titles = self._get_titles(docutils.core.publish_doctree(data))
|
||||
self._check_titles(filename, template_titles, titles)
|
||||
self._check_lines_wrapping(filename, data)
|
||||
self._check_no_cr(filename, data)
|
||||
self._check_trailing_spaces(filename, data)
|
@ -97,7 +97,8 @@ class TaskSampleTestCase(test.TestCase):
|
||||
inexistent_paths.append(json_path)
|
||||
|
||||
if inexistent_paths:
|
||||
self.fail("Sample task configs are missing:\n%r" % inexistent_paths)
|
||||
self.fail("Sample task configs are missing:\n%r"
|
||||
% inexistent_paths)
|
||||
|
||||
def test_task_config_pairs_equality(self):
|
||||
for dirname, dirnames, filenames in os.walk(self.samples_path):
|
||||
|
Loading…
Reference in New Issue
Block a user