bin/gen_calendar_entries: Add support for extending a release
Acked-by: Eric Engestrom <eric@engestrom.ch> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/8341>
This commit is contained in:
@@ -113,6 +113,69 @@ def release_candidate(args: RCArguments) -> None:
|
|||||||
commit(f'docs: Add calendar entries for {major}.{minor} release candidates.')
|
commit(f'docs: Add calendar entries for {major}.{minor} release candidates.')
|
||||||
|
|
||||||
|
|
||||||
|
def extend(args: ExtendArguments) -> None:
|
||||||
|
"""Extend a release."""
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def write_existing(writer: _csv._writer, current: typing.List[CalendarRowType]) -> typing.Iterator[CalendarRowType]:
|
||||||
|
"""Write the orinal file, yield to insert new entries.
|
||||||
|
|
||||||
|
This is a bit clever, basically what happens it writes out the
|
||||||
|
original csv file until it reaches the start of the release after the
|
||||||
|
one we're appending, then it yields the last row. When control is
|
||||||
|
returned it writes out the rest of the original calendar data.
|
||||||
|
"""
|
||||||
|
last_row: typing.Optional[CalendarRowType] = None
|
||||||
|
in_wanted = False
|
||||||
|
for row in current:
|
||||||
|
if in_wanted and row[0]:
|
||||||
|
in_wanted = False
|
||||||
|
assert last_row is not None
|
||||||
|
yield last_row
|
||||||
|
if row[0] == args.series:
|
||||||
|
in_wanted = True
|
||||||
|
if in_wanted and len(row) >= 5 and row[4] in {LAST_RELEASE.format(args.series), OR_FINAL.format(args.series)}:
|
||||||
|
# If this was the last planned release and we're adding more,
|
||||||
|
# then we need to remove that message and add it elsewhere
|
||||||
|
r = list(row)
|
||||||
|
r[4] = None
|
||||||
|
# Mypy can't figure this out…
|
||||||
|
row = typing.cast('CalendarRowType', tuple(r))
|
||||||
|
last_row = row
|
||||||
|
writer.writerow(row)
|
||||||
|
# If this is the only entry we can hit a case where the contextmanager
|
||||||
|
# hasn't yielded
|
||||||
|
if in_wanted:
|
||||||
|
yield row
|
||||||
|
|
||||||
|
current = read_calendar()
|
||||||
|
|
||||||
|
with CALENDAR_CSV.open('w') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
with write_existing(writer, current) as row:
|
||||||
|
# Get rid of -rcX as well
|
||||||
|
if '-rc' in row[2]:
|
||||||
|
first_point = int(row[2].split('rc')[-1]) + 1
|
||||||
|
template = '{}.0-rc{}'
|
||||||
|
days = 7
|
||||||
|
else:
|
||||||
|
first_point = int(row[2].split('-')[0].split('.')[-1]) + 1
|
||||||
|
template = '{}.{}'
|
||||||
|
days = 14
|
||||||
|
|
||||||
|
date = datetime.date.fromisoformat(row[1])
|
||||||
|
for i in range(first_point, first_point + args.count):
|
||||||
|
date = date + datetime.timedelta(days=days)
|
||||||
|
r = [None, date.isoformat(), template.format(args.series, i), row[3], None]
|
||||||
|
if i == first_point + args.count - 1:
|
||||||
|
if days == 14:
|
||||||
|
r[4] = LAST_RELEASE.format(args.series)
|
||||||
|
else:
|
||||||
|
r[4] = OR_FINAL.format(args.series)
|
||||||
|
writer.writerow(r)
|
||||||
|
|
||||||
|
commit(f'docs: Extend calendar entries for {args.series} by {args.count} releases.')
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
sub = parser.add_subparsers()
|
sub = parser.add_subparsers()
|
||||||
@@ -121,6 +184,11 @@ def main() -> None:
|
|||||||
rc.add_argument('manager', help="the name of the person managing the release.")
|
rc.add_argument('manager', help="the name of the person managing the release.")
|
||||||
rc.set_defaults(func=release_candidate)
|
rc.set_defaults(func=release_candidate)
|
||||||
|
|
||||||
|
ex = sub.add_parser('extend', help='Generate additional entries for a release.')
|
||||||
|
ex.add_argument('series', help='The series to extend, such as "29.3" or "30.0".')
|
||||||
|
ex.add_argument('count', type=int, help='The number of new entries to add.')
|
||||||
|
ex.set_defaults(func=extend)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
args.func(args)
|
args.func(args)
|
||||||
|
|
||||||
|
@@ -25,15 +25,30 @@ from __future__ import annotations
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
import argparse
|
import argparse
|
||||||
import csv
|
import csv
|
||||||
|
import contextlib
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import typing
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from . import gen_calendar_entries
|
from . import gen_calendar_entries
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def mock_csv(data: typing.List[gen_calendar_entries.CalendarRowType]) -> typing.Iterator[None]:
|
||||||
|
"""Replace the actual CSV data with our test data."""
|
||||||
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
c = os.path.join(d, 'calendar.csv')
|
||||||
|
with open(c, 'w') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerows(data)
|
||||||
|
|
||||||
|
with mock.patch('bin.gen_calendar_entries.CALENDAR_CSV', pathlib.Path(c)):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True, scope='module')
|
@pytest.fixture(autouse=True, scope='module')
|
||||||
def disable_git_commits() -> None:
|
def disable_git_commits() -> None:
|
||||||
"""Mock out the commit function so no git commits are made durring testing."""
|
"""Mock out the commit function so no git commits are made durring testing."""
|
||||||
@@ -81,19 +96,13 @@ class TestRC:
|
|||||||
yield
|
yield
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_data(self) -> None:
|
def csv(self) -> None:
|
||||||
"""inject our test data.."""
|
"""inject our test data.."""
|
||||||
with tempfile.TemporaryDirectory() as d:
|
with mock_csv(self.ORIGINAL_DATA):
|
||||||
c = os.path.join(d, 'calendar.csv')
|
yield
|
||||||
with open(c, 'w') as f:
|
|
||||||
writer = csv.writer(f)
|
|
||||||
writer.writerows(self.ORIGINAL_DATA)
|
|
||||||
|
|
||||||
with mock.patch('bin.gen_calendar_entries.CALENDAR_CSV', pathlib.Path(c)):
|
|
||||||
yield
|
|
||||||
|
|
||||||
def test_basic(self) -> None:
|
def test_basic(self) -> None:
|
||||||
args = argparse.Namespace()
|
args: gen_calendar_entries.RCArguments = argparse.Namespace()
|
||||||
args.manager = "Dylan Baker"
|
args.manager = "Dylan Baker"
|
||||||
gen_calendar_entries.release_candidate(args)
|
gen_calendar_entries.release_candidate(args)
|
||||||
|
|
||||||
@@ -106,3 +115,83 @@ class TestRC:
|
|||||||
actual = gen_calendar_entries.read_calendar()
|
actual = gen_calendar_entries.read_calendar()
|
||||||
|
|
||||||
assert actual == expected
|
assert actual == expected
|
||||||
|
|
||||||
|
|
||||||
|
class TestExtend:
|
||||||
|
|
||||||
|
def test_one_release(self) -> None:
|
||||||
|
data = [
|
||||||
|
('20.3', '2021-01-13', '20.3.3', 'Dylan Baker', ''),
|
||||||
|
('', '2021-01-27', '20.3.4', 'Dylan Baker', 'This is the last planned release of the 20.3.x series.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
args: gen_calendar_entries.ExtendArguments = argparse.Namespace()
|
||||||
|
args.series = '20.3'
|
||||||
|
args.count = 2
|
||||||
|
|
||||||
|
with mock_csv(data):
|
||||||
|
gen_calendar_entries.extend(args)
|
||||||
|
actual = gen_calendar_entries.read_calendar()
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
data[0],
|
||||||
|
('', '2021-01-27', '20.3.4', 'Dylan Baker', ''),
|
||||||
|
('', '2021-02-10', '20.3.5', 'Dylan Baker', ''),
|
||||||
|
('', '2021-02-24', '20.3.6', 'Dylan Baker', 'This is the last planned release of the 20.3.x series.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
assert actual == expected
|
||||||
|
def test_one_release(self) -> None:
|
||||||
|
data = [
|
||||||
|
('20.3', '2021-01-13', '20.3.3', 'Dylan Baker', ''),
|
||||||
|
('', '2021-01-27', '20.3.4', 'Dylan Baker', 'This is the last planned release of the 20.3.x series.'),
|
||||||
|
('21.0', '2021-01-13', '21.0.1', 'Dylan Baker', ''),
|
||||||
|
('', '2021-01-27', '21.0.2', 'Dylan Baker', ''),
|
||||||
|
('', '2021-02-10', '21.0.3', 'Dylan Baker', ''),
|
||||||
|
('', '2021-02-24', '21.0.4', 'Dylan Baker', 'This is the last planned release of the 21.0.x series.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
args: gen_calendar_entries.ExtendArguments = argparse.Namespace()
|
||||||
|
args.series = '21.0'
|
||||||
|
args.count = 1
|
||||||
|
|
||||||
|
with mock_csv(data):
|
||||||
|
gen_calendar_entries.extend(args)
|
||||||
|
actual = gen_calendar_entries.read_calendar()
|
||||||
|
|
||||||
|
expected = data.copy()
|
||||||
|
d = list(data[-1])
|
||||||
|
d[-1] = ''
|
||||||
|
expected[-1] = tuple(d)
|
||||||
|
expected.extend([
|
||||||
|
('', '2021-03-10', '21.0.5', 'Dylan Baker', 'This is the last planned release of the 21.0.x series.'),
|
||||||
|
])
|
||||||
|
|
||||||
|
assert actual == expected
|
||||||
|
|
||||||
|
def test_rc(self) -> None:
|
||||||
|
data = [
|
||||||
|
('20.3', '2021-01-13', '20.3.3', 'Dylan Baker', ''),
|
||||||
|
('', '2021-01-27', '20.3.4', 'Dylan Baker', 'This is the last planned release of the 20.3.x series.'),
|
||||||
|
('21.0', '2021-01-13', '21.0.0-rc1', 'Dylan Baker', ''),
|
||||||
|
('', '2021-01-20', '21.0.0-rc2', 'Dylan Baker', gen_calendar_entries.OR_FINAL.format('21.0')),
|
||||||
|
]
|
||||||
|
|
||||||
|
args: gen_calendar_entries.ExtendArguments = argparse.Namespace()
|
||||||
|
args.series = '21.0'
|
||||||
|
args.count = 2
|
||||||
|
|
||||||
|
with mock_csv(data):
|
||||||
|
gen_calendar_entries.extend(args)
|
||||||
|
actual = gen_calendar_entries.read_calendar()
|
||||||
|
|
||||||
|
expected = data.copy()
|
||||||
|
d = list(expected[-1])
|
||||||
|
d[-1] = ''
|
||||||
|
expected[-1] = tuple(d)
|
||||||
|
expected.extend([
|
||||||
|
('', '2021-01-27', '21.0.0-rc3', 'Dylan Baker', ''),
|
||||||
|
('', '2021-02-03', '21.0.0-rc4', 'Dylan Baker', gen_calendar_entries.OR_FINAL.format('21.0')),
|
||||||
|
])
|
||||||
|
|
||||||
|
assert actual == expected
|
||||||
|
Reference in New Issue
Block a user