Build Common Framework for Feature Classification Matrix
The feature classification matrix will provide information about plugins and the features they support. Acts as a launching point for users to ready to deploy their cloud. Users can use the matrix to find features and plugins that meet their needs. Pulling out the framework from Nova's implementation here: http://docs.openstack.org/developer/nova/support-matrix.html Neutron and Nova are currently working on implementation. Putting the extension in its own module will give all projects a common framework to use. Co-Authored-By: <ankur.gupta@intel.com> Change-Id: Icf4975b1dafefc9ba9f063bd8f9c6c54a36c1e13
This commit is contained in:
parent
35e2190d1c
commit
1263efe416
@ -52,6 +52,8 @@ pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
html_theme = 'openstackdocs'
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
# html_theme_path = ["."]
|
||||
|
@ -13,6 +13,7 @@ Contents:
|
||||
|
||||
readme
|
||||
installation
|
||||
usage
|
||||
contributing
|
||||
|
||||
Indices and tables
|
||||
|
81
doc/source/usage.rst
Normal file
81
doc/source/usage.rst
Normal file
@ -0,0 +1,81 @@
|
||||
========
|
||||
Usage
|
||||
========
|
||||
|
||||
Sphinx Configuration
|
||||
====================
|
||||
|
||||
To use the extension, add ``'sphinx-feature-classification'`` to the
|
||||
``extensions`` list in the ``conf.py`` file in your Sphinx project.
|
||||
|
||||
Documenting Your Drivers
|
||||
========================
|
||||
|
||||
1. This extension uses an ini file to render your driver matrix in Sphinx. You
|
||||
can begin by creating the file support-matrix.ini file in your sphinx's
|
||||
source directory.
|
||||
|
||||
2. In the INI file, create driver sections that are prefixed with driver-. The
|
||||
section has various options that can be specified.
|
||||
|
||||
+------------+-----------+---------------------------------------+
|
||||
| Field Name | Mandatory | Description |
|
||||
+============+===========+=======================================+
|
||||
| title | **Yes** | Friendly name of the driver. |
|
||||
+------------+-----------+---------------------------------------+
|
||||
| link | No | A link to documentation of the driver.|
|
||||
+------------+-----------+---------------------------------------+
|
||||
|
||||
.. code-block:: INI
|
||||
|
||||
[driver.slow-driver]
|
||||
title=Slow Driver
|
||||
link=https://docs.openstack.org/foo/latest/some-slow-driver-doc
|
||||
|
||||
[driver.fast-driver]
|
||||
title=Fast Driver
|
||||
link=https://docs.openstack.org/foo/latest/some-fast-driver-doc
|
||||
|
||||
|
||||
3. Next we'll create a feature section to show which drivers support it.
|
||||
|
||||
.. code-block:: INI
|
||||
|
||||
[operation.attach-volume]
|
||||
title=Attach block volume to instance
|
||||
status=optional
|
||||
notes=The attach volume operation provides a means to hotplug
|
||||
additional block storage to a running instance.
|
||||
cli=nova volume-attach <server> <volume>
|
||||
driver-slow-driver=complete
|
||||
driver-fast-driver=complete
|
||||
|
||||
The 'status' field takes possible values
|
||||
|
||||
+---------------+------------------------------------------------------+
|
||||
| Status | Description |
|
||||
+===============+======================================================+
|
||||
| mandatory | Unconditionally required to be implemented. |
|
||||
+---------------+------------------------------------------------------+
|
||||
| optional | Optional to support, nice to have. |
|
||||
+---------------+------------------------------------------------------+
|
||||
| choice(group) | At least one of the options within the named group |
|
||||
| | must be implemented. |
|
||||
+---------------+------------------------------------------------------+
|
||||
| condition | Required, if the referenced condition is met. |
|
||||
+---------------+------------------------------------------------------+
|
||||
|
||||
The value against each 'driver-XXXX' entry refers to the level
|
||||
of the implementation of the feature in that driver
|
||||
|
||||
+---------------+------------------------------------------------------+
|
||||
| Status | Description |
|
||||
+===============+======================================================+
|
||||
| complete | Fully implemented, expected to work at all times. |
|
||||
+---------------+------------------------------------------------------+
|
||||
| partial | Implemented, but with caveats about when it will |
|
||||
| | work eg some configurations or hardware or guest OS |
|
||||
| | may not support it. |
|
||||
+---------------+------------------------------------------------------+
|
||||
| missing | Not implemented at all. |
|
||||
+---------------+------------------------------------------------------+
|
@ -2,4 +2,5 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
docutils>=0.11 # OSI-Approved Open Source, Public Domain
|
||||
pbr>=2.0 # Apache-2.0
|
||||
|
33
sphinx_feature_classification/support-matrix.css
Normal file
33
sphinx_feature_classification/support-matrix.css
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
.sp_feature_required {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sp_impl_complete {
|
||||
color: rgb(0, 120, 0);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.sp_impl_missing {
|
||||
color: rgb(120, 0, 0);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.sp_impl_partial {
|
||||
color: rgb(170, 170, 0);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.sp_impl_unknown {
|
||||
color: rgb(170, 170, 170);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.sp_impl_summary {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.sp_cli {
|
||||
font-family: monospace;
|
||||
background-color: #F5F5F5;
|
||||
}
|
452
sphinx_feature_classification/support_matrix.py
Normal file
452
sphinx_feature_classification/support_matrix.py
Normal file
@ -0,0 +1,452 @@
|
||||
# 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 provides a sphinx extension able to render the support-matrix.ini
|
||||
file into the developer documentation.
|
||||
|
||||
It is used via a single directive in the .rst file
|
||||
|
||||
.. support_matrix::
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers import rst
|
||||
from six.moves import configparser
|
||||
|
||||
KEY_PATTERN = re.compile("[^a-zA-Z0-9_]")
|
||||
DRIVER_PREFIX = "driver."
|
||||
FEATURE_PREFIX = 'operation.'
|
||||
DRIVER_NOTES_PREFIX = "driver-notes."
|
||||
|
||||
|
||||
class Matrix(object):
|
||||
"""Represents the entire support matrix for project drivers"""
|
||||
|
||||
def __init__(self, cfg):
|
||||
self.drivers = self._set_drivers(cfg)
|
||||
self.features = self._set_features(cfg)
|
||||
|
||||
@staticmethod
|
||||
def _set_drivers(cfg):
|
||||
drivers = {}
|
||||
|
||||
for section in cfg.sections():
|
||||
if not section.startswith(DRIVER_PREFIX):
|
||||
continue
|
||||
|
||||
title = cfg.get(section, "title")
|
||||
|
||||
link = None
|
||||
if cfg.has_option(section, 'link'):
|
||||
link = cfg.get(section, "link")
|
||||
|
||||
driver = Driver(title, link)
|
||||
drivers[section] = driver
|
||||
|
||||
return drivers
|
||||
|
||||
def _set_features(self, cfg):
|
||||
features = []
|
||||
|
||||
def _process_feature(section):
|
||||
if not cfg.has_option(section, "title"):
|
||||
raise Exception(
|
||||
"'title' option missing in '[%s]' section" % section)
|
||||
|
||||
title = cfg.get(section, "title")
|
||||
status = Feature.STATUS_OPTIONAL
|
||||
group = None
|
||||
|
||||
if cfg.has_option(section, "status"):
|
||||
# The value is a string "status(group)" where
|
||||
# the 'group' part is optional
|
||||
status, group = re.match('^([^(]+)(?:\(([^)]+)\))?$',
|
||||
cfg.get(section, "status")).groups()
|
||||
|
||||
if status not in Feature.STATUS_ALL:
|
||||
raise ValueError(
|
||||
"'status' option value '%s' in ['%s']"
|
||||
"section must be one of (%s)" %
|
||||
(status, section,
|
||||
", ".join(Feature.STATUS_ALL)))
|
||||
|
||||
cli = []
|
||||
if cfg.has_option(section, "cli"):
|
||||
cli = cfg.get(section, "cli")
|
||||
|
||||
notes = None
|
||||
if cfg.has_option(section, "notes"):
|
||||
notes = cfg.get(section, "notes")
|
||||
return Feature(section, title, status, group, notes, cli)
|
||||
|
||||
def _process_implementation(section, option, feature):
|
||||
if option not in self.drivers:
|
||||
raise Exception(
|
||||
"'%s' section is not declared in the "
|
||||
"INI file." % (option))
|
||||
|
||||
status = cfg.get(section, option)
|
||||
if status not in Implementation.STATUS_ALL:
|
||||
raise ValueError(
|
||||
"%s is set to %s in '[%s]' section but must be "
|
||||
"one of (%s)" % (option, status, section, ", ".join(
|
||||
Implementation.STATUS_ALL)))
|
||||
|
||||
impl = Implementation(status)
|
||||
feature.implementations[option] = impl
|
||||
|
||||
return feature
|
||||
|
||||
for section in cfg.sections():
|
||||
if not section.startswith(FEATURE_PREFIX):
|
||||
continue
|
||||
|
||||
feature = _process_feature(section)
|
||||
|
||||
# Now we've got the basic feature details, we must process
|
||||
# the backend driver implementation for each feature
|
||||
for option in cfg.options(section):
|
||||
if not option.startswith(DRIVER_PREFIX):
|
||||
continue
|
||||
|
||||
implementation = _process_implementation(section, option,
|
||||
feature)
|
||||
features.append(implementation)
|
||||
|
||||
return features
|
||||
|
||||
|
||||
class Feature(object):
|
||||
STATUS_CHOICE = "choice"
|
||||
STATUS_CONDITION = "condition"
|
||||
STATUS_REQUIRED = "required"
|
||||
STATUS_OPTIONAL = "optional"
|
||||
STATUS_MATURE = "mature"
|
||||
STATUS_IMMATURE = "immature"
|
||||
|
||||
STATUS_ALL = [STATUS_REQUIRED, STATUS_OPTIONAL, STATUS_CHOICE,
|
||||
STATUS_CONDITION, STATUS_MATURE, STATUS_IMMATURE]
|
||||
|
||||
def __init__(self, key, title, status=STATUS_OPTIONAL,
|
||||
group=None, notes=None, cli=(), api=None):
|
||||
self.key = key
|
||||
self.title = title
|
||||
self.status = status
|
||||
self.group = group
|
||||
self.notes = notes
|
||||
self.cli = cli
|
||||
self.api = api
|
||||
|
||||
self.implementations = {}
|
||||
|
||||
|
||||
class Implementation(object):
|
||||
STATUS_COMPLETE = "complete"
|
||||
STATUS_PARTIAL = "partial"
|
||||
STATUS_INCOMPLETE = "incomplete"
|
||||
STATUS_UNKNOWN = "unknown"
|
||||
|
||||
STATUS_ALL = [STATUS_COMPLETE, STATUS_INCOMPLETE,
|
||||
STATUS_PARTIAL, STATUS_UNKNOWN]
|
||||
|
||||
def __init__(self, status=STATUS_INCOMPLETE):
|
||||
self.status = status
|
||||
|
||||
|
||||
STATUS_SYMBOLS = {
|
||||
Implementation.STATUS_COMPLETE: u"\u2714",
|
||||
Implementation.STATUS_INCOMPLETE: u"\u2716",
|
||||
Implementation.STATUS_PARTIAL: u"\u2714",
|
||||
Implementation.STATUS_UNKNOWN: u"?"
|
||||
}
|
||||
|
||||
|
||||
class Driver(object):
|
||||
def __init__(self, title, link=None):
|
||||
"""Driver object.
|
||||
|
||||
:param title: Human readable name for plugin
|
||||
:param link: A URL to documentation about the driver.
|
||||
"""
|
||||
self.title = title
|
||||
self.link = link
|
||||
|
||||
|
||||
class Directive(rst.Directive):
|
||||
|
||||
# support-matrix.ini is the arg
|
||||
required_arguments = 1
|
||||
|
||||
def run(self):
|
||||
matrix = self._load_support_matrix()
|
||||
return self._build_markup(matrix)
|
||||
|
||||
def _load_support_matrix(self):
|
||||
"""Parse support-matrix.ini file.
|
||||
|
||||
Reads the support-matrix.ini file and populates an instance of the
|
||||
Matrix class with all the data.
|
||||
|
||||
:returns: Matrix instance
|
||||
"""
|
||||
|
||||
cfg = configparser.ConfigParser()
|
||||
env = self.state.document.settings.env
|
||||
fname = self.arguments[0]
|
||||
rel_fpath, fpath = env.relfn2path(fname)
|
||||
with open(fpath) as fp:
|
||||
cfg.readfp(fp)
|
||||
|
||||
# This ensures that the docs are rebuilt whenever the
|
||||
# .ini file changes
|
||||
env.note_dependency(rel_fpath)
|
||||
|
||||
matrix = Matrix(cfg)
|
||||
return matrix
|
||||
|
||||
def _build_markup(self, matrix):
|
||||
"""Constructs the docutils content for the support matrix."""
|
||||
content = []
|
||||
self._build_summary(matrix, content)
|
||||
self._build_details(matrix, content)
|
||||
self._build_notes(content)
|
||||
return content
|
||||
|
||||
@staticmethod
|
||||
def _build_summary(matrix, content):
|
||||
"""Constructs the content for the summary of the support matrix.
|
||||
|
||||
The summary consists of a giant table, with one row
|
||||
for each feature, and a column for each backend
|
||||
driver. It provides an 'at a glance' summary of the
|
||||
status of each driver.
|
||||
"""
|
||||
|
||||
summary_title = nodes.subtitle(text="Summary")
|
||||
summary = nodes.table()
|
||||
cols = len(matrix.drivers.keys())
|
||||
|
||||
# Add two columns for the Feature and Status columns.
|
||||
cols += 2
|
||||
|
||||
summary_group = nodes.tgroup(cols=cols)
|
||||
summary_body = nodes.tbody()
|
||||
summary_head = nodes.thead()
|
||||
|
||||
for i in range(cols):
|
||||
summary_group.append(nodes.colspec(colwidth=1))
|
||||
summary_group.append(summary_head)
|
||||
summary_group.append(summary_body)
|
||||
summary.append(summary_group)
|
||||
content.append(summary_title)
|
||||
content.append(summary)
|
||||
|
||||
# This sets up all the column headers - two fixed
|
||||
# columns for feature name & status
|
||||
header = nodes.row()
|
||||
blank = nodes.entry()
|
||||
blank.append(nodes.emphasis(text="Feature"))
|
||||
header.append(blank)
|
||||
blank = nodes.entry()
|
||||
blank.append(nodes.emphasis(text="Status"))
|
||||
header.append(blank)
|
||||
summary_head.append(header)
|
||||
|
||||
# then one column for each backend driver
|
||||
impls = matrix.drivers.keys()
|
||||
impls.sort()
|
||||
for key in impls:
|
||||
driver = matrix.drivers[key]
|
||||
implcol = nodes.entry()
|
||||
header.append(implcol)
|
||||
implcol.append(nodes.strong(text=driver.title))
|
||||
|
||||
# We now produce the body of the table, one row for
|
||||
# each feature to report on
|
||||
for feature in matrix.features:
|
||||
item = nodes.row()
|
||||
|
||||
# the hyperlink driver name linking to details
|
||||
feature_id = re.sub(KEY_PATTERN, "_", feature.key)
|
||||
|
||||
# first the fixed columns for title/status
|
||||
key_col = nodes.entry()
|
||||
item.append(key_col)
|
||||
key_ref = nodes.reference(refid=feature_id)
|
||||
key_txt = nodes.inline()
|
||||
key_col.append(key_txt)
|
||||
key_txt.append(key_ref)
|
||||
key_ref.append(nodes.strong(text=feature.title))
|
||||
|
||||
status_col = nodes.entry()
|
||||
item.append(status_col)
|
||||
status_col.append(nodes.inline(
|
||||
text=feature.status,
|
||||
classes=["sp_feature_" + feature.status]))
|
||||
|
||||
# and then one column for each backend driver
|
||||
impls = matrix.drivers.keys()
|
||||
impls.sort()
|
||||
for key in impls:
|
||||
impl = feature.implementations[key]
|
||||
impl_col = nodes.entry()
|
||||
item.append(impl_col)
|
||||
|
||||
key_id = re.sub(KEY_PATTERN, "_",
|
||||
"{}_{}".format(feature.key, key))
|
||||
|
||||
impl_ref = nodes.reference(refid=key_id)
|
||||
impl_txt = nodes.inline()
|
||||
impl_col.append(impl_txt)
|
||||
impl_txt.append(impl_ref)
|
||||
|
||||
status = STATUS_SYMBOLS.get(impl.status, "")
|
||||
|
||||
impl_ref.append(nodes.literal(
|
||||
text=status,
|
||||
classes=["sp_impl_summary", "sp_impl_" + impl.status]))
|
||||
|
||||
summary_body.append(item)
|
||||
|
||||
def _build_details(self, matrix, content):
|
||||
"""Constructs the content for the details of the support matrix."""
|
||||
|
||||
details_title = nodes.subtitle(text="Details")
|
||||
details = nodes.bullet_list()
|
||||
|
||||
content.append(details_title)
|
||||
content.append(details)
|
||||
|
||||
# One list entry for each feature we're reporting on
|
||||
for feature in matrix.features:
|
||||
item = nodes.list_item()
|
||||
|
||||
status = feature.status
|
||||
if feature.group is not None:
|
||||
status += "({})".format(feature.group)
|
||||
|
||||
feature_id = re.sub(KEY_PATTERN, "_", feature.key)
|
||||
|
||||
# Highlight the feature title name
|
||||
item.append(nodes.strong(text=feature.title, ids=[feature_id]))
|
||||
|
||||
# Add maturity status
|
||||
para = nodes.paragraph()
|
||||
para.append(nodes.strong(text="Status: {}. ".format(status)))
|
||||
item.append(para)
|
||||
|
||||
if feature.api is not None:
|
||||
para = nodes.paragraph()
|
||||
para.append(
|
||||
nodes.strong(text="API Alias: {} ".format(feature.api)))
|
||||
item.append(para)
|
||||
|
||||
if feature.cli:
|
||||
item.append(self._create_cli_paragraph(feature))
|
||||
|
||||
if feature.notes is not None:
|
||||
item.append(self._create_notes_paragraph(feature.notes))
|
||||
|
||||
para_divers = nodes.paragraph()
|
||||
para_divers.append(nodes.strong(text="Driver Support:"))
|
||||
# A sub-list giving details of each backend driver
|
||||
impls = nodes.bullet_list()
|
||||
for key in feature.implementations:
|
||||
driver = matrix.drivers[key]
|
||||
impl = feature.implementations[key]
|
||||
subitem = nodes.list_item()
|
||||
|
||||
key_id = re.sub(KEY_PATTERN, "_",
|
||||
"{}_{}".format(feature.key, key))
|
||||
|
||||
subitem += [
|
||||
nodes.strong(text="{}: ".format(driver.title)),
|
||||
nodes.literal(text=impl.status,
|
||||
classes=["sp_impl_{}".format(impl.status)],
|
||||
ids=[key_id]),
|
||||
]
|
||||
|
||||
impls.append(subitem)
|
||||
|
||||
para_divers.append(impls)
|
||||
item.append(para_divers)
|
||||
details.append(item)
|
||||
|
||||
@staticmethod
|
||||
def _build_notes(content):
|
||||
"""Constructs a list of notes content for the support matrix.
|
||||
|
||||
This is generated as a bullet list.
|
||||
"""
|
||||
notes_title = nodes.subtitle(text="Notes:")
|
||||
notes = nodes.bullet_list()
|
||||
|
||||
content.append(notes_title)
|
||||
content.append(notes)
|
||||
|
||||
for note in ["This document is a continuous work in progress"]:
|
||||
item = nodes.list_item()
|
||||
item.append(nodes.strong(text=note))
|
||||
notes.append(item)
|
||||
|
||||
@staticmethod
|
||||
def _create_cli_paragraph(feature):
|
||||
"""Create a paragraph which represents the CLI commands of the feature
|
||||
|
||||
The paragraph will have a bullet list of CLI commands.
|
||||
"""
|
||||
para = nodes.paragraph()
|
||||
para.append(nodes.strong(text="CLI commands:"))
|
||||
commands = nodes.bullet_list()
|
||||
for c in feature.cli.split(";"):
|
||||
cli_command = nodes.list_item()
|
||||
cli_command += nodes.literal(text=c, classes=["sp_cli"])
|
||||
commands.append(cli_command)
|
||||
para.append(commands)
|
||||
return para
|
||||
|
||||
@staticmethod
|
||||
def _create_notes_paragraph(notes):
|
||||
"""Constructs a paragraph which represents the implementation notes
|
||||
|
||||
The paragraph consists of text and clickable URL nodes if links were
|
||||
given in the notes.
|
||||
"""
|
||||
para = nodes.paragraph()
|
||||
para.append(nodes.strong(text="Notes: "))
|
||||
# links could start with http:// or https://
|
||||
link_idxs = [m.start() for m in re.finditer('https?://', notes)]
|
||||
start_idx = 0
|
||||
for link_idx in link_idxs:
|
||||
# assume the notes start with text (could be empty)
|
||||
para.append(nodes.inline(text=notes[start_idx:link_idx]))
|
||||
# create a URL node until the next text or the end of the notes
|
||||
link_end_idx = notes.find(" ", link_idx)
|
||||
if link_end_idx == -1:
|
||||
# In case the notes end with a link without a blank
|
||||
link_end_idx = len(notes)
|
||||
uri = notes[link_idx:link_end_idx + 1]
|
||||
para.append(nodes.reference("", uri, refuri=uri))
|
||||
start_idx = link_end_idx + 1
|
||||
|
||||
# get all text after the last link (could be empty) or all of the
|
||||
# text if no link was given
|
||||
para.append(nodes.inline(text=notes[start_idx:]))
|
||||
return para
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_directive('support_matrix', Directive)
|
||||
app.add_stylesheet('support-matrix.css')
|
14
sphinx_feature_classification/tests/fakes/support-matrix.ini
Normal file
14
sphinx_feature_classification/tests/fakes/support-matrix.ini
Normal file
@ -0,0 +1,14 @@
|
||||
[driver.foo]
|
||||
title=Foo Driver
|
||||
link=https://docs.openstack.org
|
||||
|
||||
[driver.bar]
|
||||
title=Bar Driver
|
||||
link=https://docs.openstack.org
|
||||
|
||||
[operation.Cool_Feature]
|
||||
title=Cool Feature
|
||||
status=optional
|
||||
notes=A pretty darn cool feature.
|
||||
driver.foo=complete
|
||||
driver.bar=partial
|
@ -18,11 +18,43 @@ test_sphinx_feature_classification
|
||||
|
||||
Tests for `sphinx_feature_classification` module.
|
||||
"""
|
||||
import os
|
||||
|
||||
from sphinx_feature_classification import support_matrix
|
||||
from sphinx_feature_classification.tests import base
|
||||
|
||||
import ddt
|
||||
from six.moves import configparser
|
||||
|
||||
class TestSphinx_feature_classification(base.TestCase):
|
||||
|
||||
def test_something(self):
|
||||
pass
|
||||
@ddt.ddt
|
||||
class MatrixTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MatrixTestCase, self).setUp()
|
||||
|
||||
cfg = configparser.ConfigParser()
|
||||
directory = os.path.dirname(os.path.abspath(__file__))
|
||||
config_file = os.path.join(directory, 'fakes', 'support-matrix.ini')
|
||||
|
||||
with open(config_file) as fp:
|
||||
cfg.readfp(fp)
|
||||
|
||||
self.matrix = support_matrix.Matrix(cfg)
|
||||
|
||||
def test_features_set(self):
|
||||
fake_feature = self.matrix.features[0]
|
||||
self.assertEqual('Cool Feature', fake_feature.title)
|
||||
self.assertEqual('optional', fake_feature.status)
|
||||
self.assertEqual('A pretty darn cool feature.',
|
||||
fake_feature.notes)
|
||||
|
||||
@ddt.unpack
|
||||
@ddt.data({'key': 'driver.foo', 'title': 'Foo Driver',
|
||||
'link': 'https://docs.openstack.org'},
|
||||
{'key': 'driver.bar', 'title': 'Bar Driver',
|
||||
'link': 'https://docs.openstack.org'})
|
||||
def test_drivers_set(self, key, title, link):
|
||||
fake_driver = self.matrix.drivers[key]
|
||||
self.assertEqual(title, fake_driver.title)
|
||||
self.assertEqual(link, fake_driver.link)
|
||||
|
@ -3,10 +3,10 @@
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
hacking>=0.12.0,<0.13 # Apache-2.0
|
||||
|
||||
coverage>=4.0,!=4.4 # Apache-2.0
|
||||
openstackdocstheme>=1.17.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
ddt>=1.0.1 # MIT
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
sphinx!=1.6.1,>=1.5.1 # BSD
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
|
Loading…
Reference in New Issue
Block a user