diff options
-rw-r--r-- | openstack/common/service.py | 10 | ||||
-rw-r--r-- | tests/unit/test_service.py | 26 | ||||
-rwxr-xr-x | tools/list_unreleased_changes.sh | 6 | ||||
-rwxr-xr-x | tools/release_notes.py | 264 |
4 files changed, 37 insertions, 269 deletions
diff --git a/openstack/common/service.py b/openstack/common/service.py index 66b37d85..369abdab 100644 --- a/openstack/common/service.py +++ b/openstack/common/service.py @@ -199,6 +199,13 @@ class ServiceWrapper(object): class ProcessLauncher(object): + _signal_handlers_set = set() + + @classmethod + def _handle_class_signals(cls, *args, **kwargs): + for handler in cls._signal_handlers_set: + handler(*args, **kwargs) + def __init__(self): """Constructor.""" @@ -210,7 +217,8 @@ class ProcessLauncher(object): self.handle_signal() def handle_signal(self): - _set_signals_handler(self._handle_signal) + self._signal_handlers_set.add(self._handle_signal) + _set_signals_handler(self._handle_class_signals) def _handle_signal(self, signo, frame): self.sigcaught = signo diff --git a/tests/unit/test_service.py b/tests/unit/test_service.py index 8ba1f74d..3d758bbc 100644 --- a/tests/unit/test_service.py +++ b/tests/unit/test_service.py @@ -371,7 +371,9 @@ class LauncherTest(test_base.BaseTestCase): class ProcessLauncherTest(test_base.BaseTestCase): - def test_stop(self): + @mock.patch("signal.signal") + def test_stop(self, signal_mock): + signal_mock.SIGTERM = 15 launcher = service.ProcessLauncher() self.assertTrue(launcher.running) @@ -383,10 +385,28 @@ class ProcessLauncherTest(test_base.BaseTestCase): self.assertFalse(launcher.running) self.assertFalse(launcher.children) - self.assertEqual([mock.call(22, signal.SIGTERM), - mock.call(222, signal.SIGTERM)], + self.assertEqual([mock.call(22, signal_mock.SIGTERM), + mock.call(222, signal_mock.SIGTERM)], mock_kill.mock_calls) + @mock.patch( + "openstack.common.service.ProcessLauncher._signal_handlers_set", + new_callable=lambda: set()) + def test__signal_handlers_set(self, signal_handlers_set_mock): + service.ProcessLauncher() + self.assertEqual(1, len(service.ProcessLauncher._signal_handlers_set)) + service.ProcessLauncher() + self.assertEqual(2, len(service.ProcessLauncher._signal_handlers_set)) + + @mock.patch( + "openstack.common.service.ProcessLauncher._signal_handlers_set", + new_callable=lambda: set()) + def test__handle_class_signals(self, signal_handlers_set_mock): + signal_handlers_set_mock.update([mock.Mock(), mock.Mock()]) + service.ProcessLauncher._handle_class_signals() + for m in service.ProcessLauncher._signal_handlers_set: + m.assert_called_once_with() + class GracefulShutdownTestService(service.Service): def __init__(self): diff --git a/tools/list_unreleased_changes.sh b/tools/list_unreleased_changes.sh index 4b441475..2bf33128 100755 --- a/tools/list_unreleased_changes.sh +++ b/tools/list_unreleased_changes.sh @@ -16,6 +16,7 @@ bindir=$(cd $(dirname $0) && pwd) repodir=$(cd $bindir/../../.. && pwd) +release_tools=$repodir/openstack-infra/release-tools # Make sure no pager is configured so the output is not blocked export PAGER= @@ -43,6 +44,9 @@ do then echo "$lib has not yet been released" else - $bindir/release_notes.py --show-dates --changes-only $repodir/$lib $prev_tag origin/master + $release_tools/release_notes.py \ + --show-dates \ + --changes-only \ + $repodir/$lib $prev_tag origin/master fi done diff --git a/tools/release_notes.py b/tools/release_notes.py deleted file mode 100755 index 633a3076..00000000 --- a/tools/release_notes.py +++ /dev/null @@ -1,264 +0,0 @@ -#!/usr/bin/env python -# -# 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. - -"""Generates a standard set of release notes for a repository.""" - -import argparse -import glob -import os -import random -import subprocess -import sys - -import jinja2 -from oslo_concurrency import processutils -import parawrap - - -EMOTIONS = [ - 'chuffed', - 'content', - 'delighted', - 'eager', - 'excited', - 'glad', - 'gleeful', - 'happy', - 'overjoyed', - 'pleased', - 'pumped', - 'satisfied', - 'thrilled', -] - -# This will be replaced with template values and then wrapped using parawrap -# to correctly wrap at paragraph boundaries... -HEADER_RELEASE_TPL = """ -The Oslo team is {{ emotion }} to announce the release of: - -{{ project }} {{ end_rev }}: {{ description }} - -For more details, please see the git log history below and: - - {{ milestone_url }} - -Please report issues through launchpad: - - {{ bug_url }} -""" - -# This will just be replaced with template values (no wrapping applied). -CHANGE_RELEASE_TPL = """{% if notables %} -Notable changes ----------------- - -{{ notables }} -{% endif %} -{{ change_header }}{% if skip_requirement_merges %} - -NOTE: Skipping requirement commits... -{%- endif %} - -{% for change in changes -%} -{{ change }} -{% endfor %} -Diffstat (except docs and test files) -------------------------------------- - -{% for change in diff_stats -%} -{{ change }} -{% endfor %} -{% if requirement_changes %} -Requirements updates --------------------- - -{% for change in requirement_changes -%} -{{ change }} -{% endfor %} -{% endif %} -""" - -CHANGES_ONLY_TPL = """{{ change_header }} -{% for change in changes -%} -{{ change }} -{% endfor %} -""" - - -def expand_template(contents, params): - if not params: - params = {} - tpl = jinja2.Template(source=contents, undefined=jinja2.StrictUndefined) - return tpl.render(**params) - - -def run_cmd(cmd, cwd=None): - # Created since currently the 'processutils' function doesn't take a - # working directory, which we need in this example due to the different - # working directories we run programs in... - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - cwd=cwd) - stdout, stderr = p.communicate() - if p.returncode != 0: - raise processutils.ProcessExecutionError(stdout=stdout, - stderr=stderr, - exit_code=p.returncode, - cmd=cmd) - return stdout, stderr - - -def is_skippable_commit(args, line): - return (args.skip_requirement_merges and - line.lower().endswith('updated from global requirements')) - - -def main(): - parser = argparse.ArgumentParser( - prog='release_notes', - description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument("library", metavar='path', action="store", - help="library directory, for example" - " 'openstack/cliff'", - ) - parser.add_argument("start_revision", metavar='revision', - action="store", - help="start revision, for example '1.8.0'", - ) - parser.add_argument("end_revision", metavar='revision', - action="store", - nargs='?', - help="end revision, for example '1.9.0'" - " (default: HEAD)", - default="HEAD") - parser.add_argument('--changes-only', - action='store_true', - default=False, - help='List only the change summary, without details', - ) - parser.add_argument("--notable-changes", metavar='path', - action="store", - help="a file containing any notable changes") - parser.add_argument("--skip-requirement-merges", - action='store_true', default=False, - help="skip requirement update commit messages" - " (default: False)") - parser.add_argument("--show-dates", - action='store_true', default=False, - help="show dates in the change log") - args = parser.parse_args() - - library_path = os.path.abspath(args.library) - if not os.path.isfile(os.path.join(library_path, "setup.py")): - sys.stderr.write("No 'setup.py' file found in %s\n" % library_path) - sys.stderr.write("This will not end well...\n") - return 1 - - # Get the python library/program description... - cmd = [sys.executable, 'setup.py', '--description'] - stdout, stderr = run_cmd(cmd, cwd=library_path) - description = stdout.strip() - - # Get the python library/program name - cmd = [sys.executable, 'setup.py', '--name'] - stdout, stderr = run_cmd(cmd, cwd=library_path) - library_name = stdout.strip() - - # Get the commits that are in the desired range... - git_range = "%s..%s" % (args.start_revision, args.end_revision) - if args.show_dates: - format = "--format=%h %ci %s" - else: - format = "--oneline" - cmd = ["git", "log", "--no-color", format, "--no-merges", git_range] - stdout, stderr = run_cmd(cmd, cwd=library_path) - changes = [] - for commit_line in stdout.splitlines(): - commit_line = commit_line.strip() - if not commit_line or is_skippable_commit(args, commit_line): - continue - else: - changes.append(commit_line) - - # Filter out any requirement file changes... - requirement_changes = [] - requirement_files = list(glob.glob(os.path.join(library_path, - '*requirements*.txt'))) - if requirement_files: - cmd = ['git', 'diff', '-U0', '--no-color', git_range] - cmd.extend(requirement_files) - stdout, stderr = run_cmd(cmd, cwd=library_path) - requirement_changes = [line.strip() - for line in stdout.splitlines() if line.strip()] - - # Get statistics about the range given... - cmd = ['git', 'diff', '--stat', '--no-color', git_range] - stdout, stderr = run_cmd(cmd, cwd=library_path) - diff_stats = [] - for line in stdout.splitlines(): - line = line.strip() - if not line or line.find("tests") != -1 or line.startswith("doc"): - continue - diff_stats.append(line) - - # Find what the bug url is... - bug_url = '' - with open(os.path.join(library_path, 'README.rst'), 'r') as fh: - for line in fh: - pieces = line.split("Bugs:", 1) - if len(pieces) == 2: - bug_url = pieces[1].strip() - break - if not bug_url: - raise IOError("No bug url found in '%s'" - % os.path.join(library_path, 'README.rst')) - - notables = '' - if args.notable_changes: - with open(args.notable_changes, 'r') as fh: - notables = fh.read().rstrip() - - lp_url = bug_url.replace("bugs.", "").rstrip("/") - milestone_url = lp_url + "/+milestone/%s" % args.end_revision - change_header = ["Changes in %s %s" % (library_name, git_range)] - change_header.append("-" * len(change_header[0])) - - params = { - 'project': os.path.basename(library_path), - 'description': description, - 'end_rev': args.end_revision, - 'range': git_range, - 'lib': library_path, - 'milestone_url': milestone_url, - 'skip_requirement_merges': args.skip_requirement_merges, - 'bug_url': bug_url, - 'changes': changes, - 'requirement_changes': requirement_changes, - 'diff_stats': diff_stats, - 'notables': notables, - 'change_header': "\n".join(change_header), - 'emotion': random.choice(EMOTIONS), - } - if args.changes_only: - print(expand_template(CHANGES_ONLY_TPL, params)) - else: - header = expand_template(HEADER_RELEASE_TPL.strip(), params) - for line in parawrap.wrap(header): - print(line) - print(expand_template(CHANGE_RELEASE_TPL, params)) - return 0 - - -if __name__ == '__main__': - sys.exit(main()) |