2013-11-12 14:11:52 -08:00
|
|
|
from spec import Spec, skip, eq_
|
|
|
|
from mock import Mock
|
|
|
|
|
|
|
|
from releases import (
|
|
|
|
issue,
|
2013-11-20 15:51:57 -08:00
|
|
|
issues_role,
|
2013-11-12 14:11:52 -08:00
|
|
|
release,
|
2013-11-20 18:01:07 -08:00
|
|
|
release_role,
|
2013-11-20 15:51:57 -08:00
|
|
|
construct_releases,
|
|
|
|
construct_nodes
|
2013-11-12 14:11:52 -08:00
|
|
|
)
|
2013-11-20 18:01:07 -08:00
|
|
|
from docutils.nodes import reference
|
2013-11-12 14:11:52 -08:00
|
|
|
|
|
|
|
|
2013-11-15 11:26:11 -08:00
|
|
|
def _app():
|
|
|
|
# Fake app obj
|
|
|
|
app = Mock('app')
|
|
|
|
config = Mock('config')
|
|
|
|
config.releases_release_uri = 'foo_%s'
|
|
|
|
config.releases_issue_uri = 'bar_%s'
|
|
|
|
config.releases_debug = False
|
|
|
|
app.config = config
|
|
|
|
return app
|
|
|
|
|
2013-11-20 18:01:07 -08:00
|
|
|
def _inliner():
|
|
|
|
return Mock(document=Mock(settings=Mock(env=Mock(app=_app()))))
|
|
|
|
|
2013-11-20 15:51:57 -08:00
|
|
|
# Obtain issue() object w/o wrapping all parse steps
|
|
|
|
def _issue(type_, number, **kwargs):
|
|
|
|
text = str(number)
|
|
|
|
if kwargs.get('backported', False):
|
|
|
|
text += " backported"
|
|
|
|
if kwargs.get('major', False):
|
|
|
|
text += " major"
|
|
|
|
return issues_role(
|
|
|
|
name=type_,
|
|
|
|
rawtext='',
|
|
|
|
text=text,
|
|
|
|
lineno=None,
|
2013-11-20 18:01:07 -08:00
|
|
|
inliner=_inliner(),
|
2013-11-20 15:51:57 -08:00
|
|
|
)[0][0]
|
|
|
|
|
|
|
|
def _entry(i):
|
|
|
|
return [[i]]
|
|
|
|
|
|
|
|
def _release(number, **kwargs):
|
2013-11-20 18:01:07 -08:00
|
|
|
return release_role(
|
|
|
|
name=None,
|
|
|
|
rawtext='',
|
|
|
|
text='%s <2013-11-20>' % number,
|
|
|
|
lineno=None,
|
|
|
|
inliner=_inliner(),
|
|
|
|
)
|
2013-11-20 15:51:57 -08:00
|
|
|
|
2013-11-20 14:48:18 -08:00
|
|
|
def _releases(*entries):
|
|
|
|
entries = list(entries) # lol tuples
|
|
|
|
# Translate simple objs into changelog-friendly ones
|
|
|
|
for index, item in enumerate(entries):
|
|
|
|
if isinstance(item, basestring):
|
|
|
|
entries[index] = _release(item)
|
|
|
|
else:
|
|
|
|
entries[index] = _entry(item)
|
|
|
|
# Insert initial/empty 1st release to start timeline
|
|
|
|
entries.append(_release('1.0.0'))
|
|
|
|
return construct_releases(entries, _app())
|
|
|
|
|
2013-11-20 15:51:57 -08:00
|
|
|
def _setup_issues(self):
|
|
|
|
self.f = _issue('feature', '12')
|
|
|
|
self.s = _issue('support', '5')
|
|
|
|
self.b = _issue('bug', '15')
|
|
|
|
self.mb = _issue('bug', '200', major=True)
|
|
|
|
self.bf = _issue('feature', '27', backported=True)
|
|
|
|
self.bs = _issue('support', '29', backported=True)
|
|
|
|
|
2013-11-06 17:24:47 -08:00
|
|
|
|
2013-11-11 10:09:48 -08:00
|
|
|
class releases(Spec):
|
|
|
|
"""
|
|
|
|
Organization of issues into releases
|
|
|
|
"""
|
|
|
|
def setup(self):
|
2013-11-20 15:51:57 -08:00
|
|
|
_setup_issues(self)
|
2013-11-13 15:55:54 -08:00
|
|
|
|
2013-11-14 14:20:38 -08:00
|
|
|
def _expect_entries(self, all_entries, in_, not_in):
|
|
|
|
# Grab 2nd release as 1st is the empty 'beginning of time' one
|
2013-11-20 14:48:18 -08:00
|
|
|
entries = _releases(*all_entries)[1]['entries']
|
2013-11-14 13:40:01 -08:00
|
|
|
eq_(len(entries), len(in_))
|
2013-11-14 13:25:32 -08:00
|
|
|
for x in in_:
|
|
|
|
assert x in entries
|
|
|
|
for x in not_in:
|
|
|
|
assert x not in entries
|
2013-11-06 17:24:47 -08:00
|
|
|
|
2013-11-13 15:48:07 -08:00
|
|
|
def feature_releases_include_features_and_support_not_bugs(self):
|
2013-11-14 13:25:32 -08:00
|
|
|
self._expect_entries(
|
|
|
|
['1.1.0', self.f, self.b, self.s],
|
|
|
|
[self.f, self.s],
|
|
|
|
[self.b]
|
2013-11-13 15:55:54 -08:00
|
|
|
)
|
2013-11-06 17:24:47 -08:00
|
|
|
|
2013-11-11 10:09:48 -08:00
|
|
|
def feature_releases_include_major_bugs(self):
|
2013-11-14 13:25:32 -08:00
|
|
|
self._expect_entries(
|
|
|
|
['1.1.0', self.f, self.b, self.mb],
|
|
|
|
[self.f, self.mb],
|
|
|
|
[self.b]
|
2013-11-14 10:14:56 -08:00
|
|
|
)
|
2013-11-11 10:09:48 -08:00
|
|
|
|
|
|
|
def bugfix_releases_include_bugs(self):
|
2013-11-14 13:25:32 -08:00
|
|
|
self._expect_entries(
|
|
|
|
['1.0.2', self.f, self.b, self.mb],
|
|
|
|
[self.b],
|
|
|
|
[self.mb, self.f],
|
2013-11-14 10:46:12 -08:00
|
|
|
)
|
2013-11-11 10:09:48 -08:00
|
|
|
|
|
|
|
def bugfix_releases_include_backported_features(self):
|
2013-11-14 13:41:16 -08:00
|
|
|
self._expect_entries(
|
|
|
|
['1.0.2', self.bf, self.b, self.s],
|
|
|
|
[self.b, self.bf],
|
|
|
|
[self.s]
|
|
|
|
)
|
2013-11-11 10:09:48 -08:00
|
|
|
|
|
|
|
def bugfix_releases_include_backported_support(self):
|
2013-11-14 13:41:16 -08:00
|
|
|
self._expect_entries(
|
|
|
|
['1.0.2', self.f, self.b, self.s, self.bs],
|
|
|
|
[self.b, self.bs],
|
|
|
|
[self.s, self.f]
|
|
|
|
)
|
2013-11-11 10:09:48 -08:00
|
|
|
|
|
|
|
def unmarked_bullet_list_items_treated_as_bugs(self):
|
2013-11-14 14:20:38 -08:00
|
|
|
# Empty list item here stands in for just-a-list-of-nodes,
|
|
|
|
# which is what a non-issue/release changelog list item looks like
|
2013-11-20 14:48:18 -08:00
|
|
|
entries = _releases('1.0.2', self.f, [])[1]['entries']
|
2013-11-14 13:41:16 -08:00
|
|
|
eq_(len(entries), 1)
|
|
|
|
assert self.f not in entries
|
|
|
|
assert isinstance(entries[0], issue)
|
|
|
|
eq_(entries[0].number, None)
|
2013-11-11 10:09:48 -08:00
|
|
|
|
2013-11-14 14:16:40 -08:00
|
|
|
def unreleased_items_go_in_unreleased_release(self):
|
2013-11-20 14:48:18 -08:00
|
|
|
releases = _releases('1.0.2', self.f, self.b)
|
2013-11-14 14:20:38 -08:00
|
|
|
r = releases[-1]
|
|
|
|
eq_(len(r['entries']), 1)
|
|
|
|
assert self.f in r['entries']
|
|
|
|
eq_(r['obj'].number, 'unreleased')
|
2013-11-14 14:16:40 -08:00
|
|
|
|
2013-11-15 10:10:24 -08:00
|
|
|
def issues_consumed_by_releases_are_not_in_unreleased(self):
|
2013-11-20 14:48:18 -08:00
|
|
|
releases = _releases('1.0.2', self.f, self.b, self.s, self.bs)
|
2013-11-15 10:10:24 -08:00
|
|
|
release = releases[1]['entries']
|
|
|
|
unreleased = releases[-1]['entries']
|
|
|
|
assert self.b in release
|
|
|
|
assert self.b not in unreleased
|
|
|
|
|
|
|
|
def unreleased_catches_bugs_and_features(self):
|
|
|
|
entries = [self.f, self.b, self.mb, self.s, self.bs, self.bf]
|
2013-11-20 14:48:18 -08:00
|
|
|
releases = _releases(*entries)
|
2013-11-15 10:10:24 -08:00
|
|
|
unreleased = releases[-1]['entries']
|
|
|
|
for x in entries:
|
|
|
|
assert x in unreleased
|
|
|
|
|
2013-11-11 10:09:48 -08:00
|
|
|
|
|
|
|
class nodes(Spec):
|
|
|
|
"""
|
|
|
|
Expansion/extension of docutils nodes
|
|
|
|
"""
|
2013-11-15 11:26:11 -08:00
|
|
|
def setup(self):
|
2013-11-20 18:01:07 -08:00
|
|
|
_setup_issues(self)
|
|
|
|
|
|
|
|
def _generate(self, *entries):
|
2013-11-20 18:24:51 -08:00
|
|
|
return construct_nodes(_releases(*entries))[0][1]
|
2013-11-15 11:26:11 -08:00
|
|
|
|
2013-11-11 10:09:48 -08:00
|
|
|
def issues_with_numbers_appear_as_number_links(self):
|
2013-11-20 18:01:07 -08:00
|
|
|
nodes = self._generate('1.0.2', self.b)
|
2013-11-20 18:24:51 -08:00
|
|
|
link = nodes[0][2]
|
2013-11-20 18:01:07 -08:00
|
|
|
assert isinstance(link, reference)
|
|
|
|
assert link['refuri'] == 'bar_15'
|
2013-11-11 10:09:48 -08:00
|
|
|
|
|
|
|
def bugs_marked_as_bugs(self):
|
2013-11-20 18:09:36 -08:00
|
|
|
nodes = self._generate('1.0.2', self.b)
|
2013-11-20 18:24:51 -08:00
|
|
|
assert 'Bug' in nodes[0][0][0]
|
2013-11-11 10:09:48 -08:00
|
|
|
|
|
|
|
def features_marked_as_features(self):
|
2013-11-20 18:20:06 -08:00
|
|
|
nodes = self._generate('1.1.0', self.f)
|
2013-11-20 18:24:51 -08:00
|
|
|
assert 'Feature' in nodes[0][0][0]
|
2013-11-11 10:09:48 -08:00
|
|
|
|
|
|
|
def support_marked_as_suppot(self):
|
2013-11-20 18:20:06 -08:00
|
|
|
nodes = self._generate('1.1.0', self.s)
|
2013-11-20 18:24:51 -08:00
|
|
|
assert 'Support' in nodes[0][0][0]
|
2013-11-11 10:09:48 -08:00
|
|
|
|
|
|
|
def zeroed_issues_appear_as_unlinked_issues(self):
|
|
|
|
skip()
|
2013-11-15 11:26:11 -08:00
|
|
|
|
|
|
|
def issues_wrapped_in_unordered_list_nodes(self):
|
|
|
|
skip()
|