diff options
Diffstat (limited to 'linaropy/rn/linaroseries.py')
-rw-r--r-- | linaropy/rn/linaroseries.py | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/linaropy/rn/linaroseries.py b/linaropy/rn/linaroseries.py new file mode 100644 index 0000000..f978655 --- /dev/null +++ b/linaropy/rn/linaroseries.py @@ -0,0 +1,361 @@ +import unittest +import copy + +from datetime import datetime +from dateutil.relativedelta import relativedelta +from ..vers import Spin +from ..vers import Rc +from ..series import Series +from ..series import seriesFromBranchname + +# Progression: +# The Linaro release progression follows. +# +# Snapshot 2016.01 +# spin 1 2016.01-1 +# spin 2 2016.01-2 +# Candidate 2016.01-rc1 +# rc 2 2016.01-rc2 +# Release 2016.01 +# +# Note: Remember, if a snapshot is respun to fix a +# bug then the fix should be cherry-picked into the +# release branch and a release-candidate spun from +# from the release branch. +# +# Candidate 2016.01-1-rc1 +# rc 2 2016.01-1-rc2 +# Release 2016.01-1 + +# Inherit Series in order to provide Linaro specific rules on transitions +# between series types. The baseclass Series doesn't have the toNext* +# functions defined. +class LinaroSeries(Series): + def __init__(self, seriestype, vendor=None, package=None, date=datetime.today(), spin=None, rc=None, strict=True): + + # This is a dispatch table so that we can use a 'toNext' function based + # on an input type and it will call the correct toNextFoo function. + # This will work if the key is an integer or a string. Ideally this + # would be added to Series and we wouldn't need to derive, but I + # couldn't figure out how to get the pointer tables correct. + self.dispatchnext = { + Series.series.index("candidate"): self.toNextCandidate, + 'candidate': self.toNextCandidate, + Series.series.index("snapshot"): self.toNextSnapshot, + 'snapshot': self.toNextSnapshot, + Series.series.index("release"): self.toNextRelease, + 'release': self.toNextRelease, + } + + super(LinaroSeries,self).__init__(seriestype, vendor, package, date,spin,rc, strict) + + def toNextCandidate(self, date=None, strict=False): + if self.seriestype < 0 or self.seriestype >= len(Series.series): + raise TypeError('toNextCandidate on an unknown series type.') + + candidate=copy.deepcopy(self) + candidate.seriestype = Series.series.index("candidate") + + if date: + if not isinstance(date,datetime): + raise TypeError('date is not of type datetime.') + candidate.date=date + + if self.seriestype == Series.series.index("candidate"): + candidate.rc.increment() + elif self.seriestype == Series.series.index("release"): + rc=Rc(1) + candidate.rc=rc + candidate.spin.increment() + elif self.seriestype == Series.series.index("snapshot"): + spin=Spin(None) + rc=Rc(1) + candidate.rc=rc + candidate.spin=spin + + # If the user hasn't specified a date, the implicit behavior is to + # increment the date by 1 month when moving from a snapshot to a + # candidate. + if not date: + candidate.incrementMonth() + # If the user did specify a date and we're in strict mode we need to + # verify that the date they chose is exactly one month difference. + elif strict: + if candidate.date != self.date + relativedelta(months=1): + raise ValueError('toNextCandidate invoked with strict=True. Snapshot date to candidate date can only be +1 month difference.') + return candidate + + if strict and candidate.date != self.date: + raise ValueError('Candidate date can only change if current series is a Snapshot when toNextCandidate is invoked with strict=True.') + + return candidate + + def toNextRelease(self, date=None, strict=False): + if self.seriestype < 0 or self.seriestype >= len(Series.series): + raise TypeError('toNextRelease called on an unknown series type.') + elif self.seriestype == Series.series.index("release"): + raise TypeError('A release series can not be the basis for another release. Move to a release candidate first.') + elif self.seriestype == Series.series.index("snapshot"): + raise TypeError('A snapshot series can not be the basis for a release. Move to a release candidate first.') + + # Only a candidate can turn into a release. + release=copy.deepcopy(self) + # if we have a candidate and ask for a release rc needs to be None. + rc=Rc() + release.rc=rc + release.seriestype = Series.series.index("release") + + if date: + if not isinstance(date,datetime): + raise TypeError('date is not of type datetime.') + release.date=date + + if strict and release.date != self.date: + raise ValueError('Release date can not change as toNextRelease was invoked with strict=True') + + return release + + def toNextSnapshot(self, date=None, strict=False): + if self.seriestype < 0 or self.seriestype >= len(Series.series): + raise TypeError('toNextRelease called on an unknown series type.') + elif self.seriestype == Series.series.index("candidate"): + raise TypeError('Series of type candidate can not be the basis for a snapshot.') + if self.seriestype == Series.series.index("release"): + raise TypeError('Series of type release can not be the basis for a snapshot.') + + # Only snapshots can be snapshots next. + snapshot=copy.deepcopy(self) + snapshot.seriestype = Series.series.index("snapshot") + snapshot.spin.increment() + + if date: + if not isinstance(date,datetime): + raise TypeError('date is not of type datetime.') + snapshot.date=date + + if strict and snapshot.date != self.date: + raise ValueError('Snapshot date can not change as toNextSnapshot was invoked with strict=True') + + return snapshot + + # toNext will take a 'key' as either a string representing the + # Series.series type or the index of the Series.series type, e.g., + # 'candidate' or Series.series.index("candidate") and it will forward + # to the correct toNext* function as mapped in the dispatchnext + # dictionary. + def toNext(self, seriestype, date=None, strict=False): + return self.dispatchnext[seriestype](date, strict) + +# Helper function which creates a LinaroSeries from a properly formed branch name +# input string. +def linaroSeriesFromBranchname(branch=None): + + # Create a Series using the helper function and then populate a + # LinaroSeries. TODO: There's probably a better way to do this such + # as a SeriesFactory that is overridden. + series=seriesFromBranchname(branch) + linaroseries=LinaroSeries(Series.series[series.seriestype], vendor="linaro", package=series.package ,date=series.date , spin=series.spin, rc=series.rc, strict=True ) + return linaroseries + +class TestLinaroSeries(unittest.TestCase): + + def test_candidate_to_candidate(self): + candidate=LinaroSeries("candidate", package="GCC-5.3.1", date=datetime(2016,05,15), spin="1", rc="1") + candidate2=candidate.toNextCandidate() + self.assertEqual(candidate2.getBranchname(), "releases/linaro-5.3-2016.05-1-rc2") + self.assertEqual(candidate2.seriestype, Series.series.index("candidate")) + self.assertEqual(candidate.date, candidate2.date) + + #TODO test date and strict=True + + def test_release_to_candidate(self): + # Test where spin is None and needs to be incremented. + release=LinaroSeries("release", package="GCC-5.3.1", date=datetime(2016,05,15)) + candidate=release.toNextCandidate() + self.assertEqual(candidate.getBranchname(), "releases/linaro-5.3-2016.05-1-rc1") + self.assertEqual(candidate.seriestype, Series.series.index("candidate")) + self.assertEqual(candidate.date, release.date) + + # Now test where spin is already non-zero. + release2=LinaroSeries("release", package="GCC-5.3.1", date=datetime(2016,05,15), spin="1") + candidate2=release2.toNextCandidate() + self.assertEqual(candidate2.getBranchname(), "releases/linaro-5.3-2016.05-2-rc1") + self.assertEqual(candidate2.seriestype, Series.series.index("candidate")) + + #TODO test date and strict=True + + def test_snapshot_to_candidate(self): + snapshot=LinaroSeries("snapshot", package="GCC-5.3.1", date=datetime(2016,05,15), spin="6") + candidate=snapshot.toNextCandidate() + self.assertEqual(candidate.getBranchname(), "releases/linaro-5.3-2016.06-rc1") + self.assertEqual(candidate.seriestype, Series.series.index("candidate")) + self.assertNotEqual(candidate.date, snapshot.date) + # Verify that it's one month apart. + self.assertEqual(candidate.date, snapshot.date + relativedelta(months=1)) + + # Make sure that the default date increment is just one month. + candidate2=snapshot.toNextCandidate(strict=True) + self.assertEqual(candidate2.date, snapshot.date + relativedelta(months=1)) + + # whether strict=True or not. + candidate3=snapshot.toNextCandidate(strict=False) + self.assertEqual(candidate3.date, snapshot.date + relativedelta(months=1)) + + # If the user passed a date and strict=yes, make sure the increment is only one month. + candidate4=snapshot.toNextCandidate(date=datetime.strptime("2016.06.15", "%Y.%m.%d"), strict=True) + plusonem=snapshot.date + relativedelta(months=1) + self.assertEqual(candidate4.date,plusonem) + + # Make sure the date can be set without error when strict is False + candidate5=snapshot.toNextCandidate(date=datetime.strptime("2016.07.15", "%Y.%m.%d"), strict=False) + self.assertEqual(candidate5.date, snapshot.date + relativedelta(months=2)) + + with self.assertRaises(ValueError): + # Attempting to increment the date when strict=True should raise an exception. + candidate6=snapshot.toNextCandidate(date=datetime.strptime("2016.07.15", "%Y.%m.%d"), strict=True) + + def test_unknown_to_candidate(self): + unknown=LinaroSeries("candidate", package="GCC-5.3.1", date=datetime(2016,05,15), spin="1", rc="1") + # Purposely override with an incorrect seriestype. + unknown.seriestype=99 + with self.assertRaises(TypeError): + candidate=unknown.toNextCandidate() + + def test_candidate_to_release(self): + candidate=LinaroSeries("candidate", package="GCC-5.3.1", date=datetime(2016,05,15), spin="1", rc="1") + release=candidate.toNextRelease() + self.assertEqual(release.getBranchname(), "releases/linaro-5.3-2016.05-1") + self.assertEqual(release.seriestype, Series.series.index("release")) + self.assertEqual(candidate.date, release.date) + + release2=candidate.toNextRelease(strict=True) + self.assertEqual(release2.getBranchname(), "releases/linaro-5.3-2016.05-1") + self.assertEqual(release2.date, candidate.date) + + with self.assertRaises(ValueError): + # Attempting to increment the date when strict=True should raise an exception. + release3=candidate.toNextRelease(date=datetime.strptime("2016.06.15", "%Y.%m.%d"), strict=True) + + # If strict=False and the date is changed, go ahead and change it. + release4=candidate.toNextRelease(date=datetime.strptime("2016.06.15", "%Y.%m.%d"), strict=False) + self.assertEqual(release4.getBranchname(), "releases/linaro-5.3-2016.06-1") + self.assertNotEqual(release4.date, release.date) + + with self.assertRaises(TypeError): + release5=candidate.toNextRelease("2016.06", strict=False) + + def test_snapshot_to_release(self): + snapshot=LinaroSeries("snapshot", package="GCC-5.3.1", date=datetime(2016,05,15), spin="6") + with self.assertRaises(TypeError): + release=snapshot.toNextRelease() + + def test_release_to_release(self): + release=LinaroSeries("release", package="GCC-5.3.1", date=datetime(2016,05,15), spin="6") + with self.assertRaises(TypeError): + release2=release.toNextRelease() + + def test_unknown_to_release(self): + unknown=LinaroSeries("release", package="GCC-5.3.1", date=datetime(2016,05,15), spin="1") + # Purposely override with an incorrect seriestype. + unknown.seriestype=99 + with self.assertRaises(TypeError): + release=unknown.toNextRelease() + + def test_candidate_to_snapshot(self): + candidate=LinaroSeries("candidate", package="GCC-5.3.1", date=datetime(2016,05,15), spin="1", rc="1") + with self.assertRaises(TypeError): + snapshot=candidate.toNextSnapshot() + # This would/should be the case if this wasn't and invalid option. + # self.assertEqual(snapshot.seriestype, "snapshot") + + def test_release_to_snapshot(self): + release=LinaroSeries("release", package="GCC-5.3.1", date=datetime(2016,05,15), spin="1") + with self.assertRaises(TypeError): + snapshot=release.toNextSnapshot() + + def test_snapshot_to_snapshot(self): + snapshot=LinaroSeries("snapshot", package="GCC-5.3.1", date=datetime(2016,05,15), spin="6") + snapshot2=snapshot.toNextSnapshot() + + self.assertEqual(snapshot2.getBranchname(), "snapshots/linaro-5.3-2016.05-7") + self.assertEqual(snapshot2.seriestype, Series.series.index("snapshot")) + self.assertEqual(snapshot.date, snapshot2.date) + + # Verify that the original hasn't changed. This verifies that a + # deepcopy is used on the toNext* functions. + self.assertEqual(snapshot.getBranchname(), "snapshots/linaro-5.3-2016.05-6") + + snapshot3=snapshot.toNextSnapshot(strict=True) + self.assertEqual(snapshot3.getBranchname(), "snapshots/linaro-5.3-2016.05-7") + self.assertEqual(snapshot3.seriestype, Series.series.index("snapshot")) + self.assertEqual(snapshot.date, snapshot3.date) + + with self.assertRaises(ValueError): + # Attempting to increment the date when strict=True should raise an exception. + snapshot4=snapshot.toNextSnapshot(date=datetime.strptime("2016.06.15", "%Y.%m.%d"), strict=True) + + # If strict=False and the date is changed, go ahead and change it. + snapshot5=snapshot.toNextSnapshot(date=datetime.strptime("2016.06.15", "%Y.%m.%d"), strict=False) + self.assertEqual(snapshot5.getBranchname(), "snapshots/linaro-5.3-2016.06-7") + self.assertNotEqual(snapshot5.date, snapshot.date) + + # Verify that a date as a string generates an exception. + with self.assertRaises(TypeError): + snapshot6=snapshot.toNextSnapshot("2016.06.15", strict=False) + + def test_unknown_to_snapshot(self): + unknown=LinaroSeries("snapshot", package="GCC-5.3.1", date=datetime(2016,05,15), spin="1") + # Purposely override with an incorrect seriestype. + unknown.seriestype=99 + with self.assertRaises(TypeError): + snapshot=unknown.toNextSnapshot() + + def test_snapshot_toNext(self): + snapshot=LinaroSeries("snapshot", package="GCC-5.3.1", date=datetime(2016,05,15), spin="1") + self.assertEqual(snapshot.getBranchname(), "snapshots/linaro-5.3-2016.05-1") + + candidate=snapshot.toNext("candidate") + # A Snapshot -> Candidate traversal always results in spin being set to zero + # and the first release candidate being created. + self.assertEqual(candidate.getBranchname(), "releases/linaro-5.3-2016.06-rc1") + self.assertEqual(candidate.seriestype, Series.series.index("candidate")) + + # Make sure toNext works with 'index' as well as the type string. + candidate2=snapshot.toNext(Series.series.index("candidate")) + # A Snapshot -> Candidate traversal always results in spin being set to zero + # and the first release candidate being created. + self.assertEqual(candidate2.getBranchname(), "releases/linaro-5.3-2016.06-rc1") + self.assertEqual(candidate2.seriestype, Series.series.index("candidate")) + + with self.assertRaises(TypeError): + candidate=snapshot.toNext("release") + candidate=snapshot.toNext(Series.series.index("release")) + + def test_toNext_incorrect_keys(self): + snapshot=LinaroSeries("snapshot", package="GCC-5.3.1", date=datetime(2016,05,15), spin="1") + self.assertEqual(snapshot.getBranchname(), "snapshots/linaro-5.3-2016.05-1") + with self.assertRaises(KeyError): + candidate=snapshot.toNext("candidat") + candidate=snapshot.toNext("snapsho") + candidate=snapshot.toNext("releas") + candidate=snapshot.toNext(-1) + candidate=snapshot.toNext(len(Series.series)+1) + # Index out of range of the Series.series array. + + # Test to see if the objects returned from the toNext* functions are + # new instances and not references to the input Series. + def test_toNextFOO_return_new_objects(self): + candidate=LinaroSeries("candidate", package="GCC-5.3.1", date=datetime(2016,05,15), spin="1", rc="1") + candidate2=candidate.toNext("candidate") + release=candidate.toNext("release") + + self.assertIsNot(candidate,candidate2) + self.assertIsNot(candidate,release) + + snapshot=LinaroSeries("snapshot", package="GCC-5.3.1", date=datetime(2016,05,15), spin="1") + snapshot2=snapshot.toNext("snapshot") + self.assertIsNot(snapshot,snapshot2) + +if __name__ == '__main__': + #logging.basicConfig(level="INFO") + unittest.main() |