From 0f18ac8da4f984e8af632532e098e023427bc548 Mon Sep 17 00:00:00 2001 From: Fabio Menegazzo Date: Fri, 1 Aug 2014 17:00:02 -0300 Subject: [PATCH 1/5] Starting implementation of stateful machine. --- travispy/entities/_restartable.py | 8 ++- travispy/entities/_stateful.py | 99 +++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 travispy/entities/_stateful.py diff --git a/travispy/entities/_restartable.py b/travispy/entities/_restartable.py index f858057..408025b 100644 --- a/travispy/entities/_restartable.py +++ b/travispy/entities/_restartable.py @@ -1,15 +1,19 @@ -from ._entity import Entity +from ._stateful import Stateful #=================================================================================================== # Restartable #=================================================================================================== -class Restartable(Entity): +class Restartable(Stateful): ''' Base class for restartable entities such as :class:`.Build` and :class:`.Job`. ''' + __slots__ = [ + 'state', + ] + def cancel(self): ''' Method responsible for canceling current action of this object. diff --git a/travispy/entities/_stateful.py b/travispy/entities/_stateful.py new file mode 100644 index 0000000..3dde6f2 --- /dev/null +++ b/travispy/entities/_stateful.py @@ -0,0 +1,99 @@ +from ._entity import Entity + + + +#=================================================================================================== +# Stateful +#=================================================================================================== +class Stateful(Entity): + ''' + Base class for stateful entities such as :class:`.Repo`, :class:`.Build` and :class:`.Job`. + ''' + + CANCELED = 'canceled' + CREATED = 'created' + ERRORED = 'errored' + FAILED = 'failed' + PASSED = 'passed' + QUEUED = 'queued' + READY = 'ready' + STARTED = 'started' + + STATES = [ + 'canceled', + 'created', + 'errored', + 'failed', + 'passed', + 'queued', + 'ready', + 'started', + ] + + __slots__ = [ + 'created?', + 'errored?', + 'failed?', + 'finished?', + 'green?', + 'passed?', + 'pending?', + 'queued?', + 'red?', + 'running?', + 'started?', + 'successful?', + 'unsuccessful?', + 'yellow?', + 'color', + 'state', + ] + + @property + def ready(self): + return self.state == self.READY + + + @property + def pending(self): + return self.state in [self.CREATED, self.STARTED, self.QUEUED] + + + @property + def started(self): + return self.state not in [self.CREATED, self.QUEUED] + + + @property + def queued(self): + return self.state != self.CREATED + + + @property + def finished(self): + return not self.pending + + + @property + def passed(self): + return self.state == self.PASSED + + + @property + def errored(self): + return self.state == self.ERRORED + + + @property + def failed(self): + return self.state == self.FAILED + + + @property + def canceled(self): + return self.state == self.CANCELED + + + @property + def unsuccessful(self): + return self.errored or self.failed or self.canceled From 49750601c36d3ba853fb5555790110ef577b71e6 Mon Sep 17 00:00:00 2001 From: Fabio Menegazzo Date: Sun, 3 Aug 2014 22:03:54 -0300 Subject: [PATCH 2/5] Adding missing methods/properties to complete stateful entities. Missing documentation yet. --- travispy/_tests/test_stateful.py | 49 ++++++++++ travispy/entities/_restartable.py | 4 - travispy/entities/_stateful.py | 148 ++++++++++++++++++++---------- travispy/entities/build.py | 4 - travispy/entities/job.py | 3 - travispy/entities/repo.py | 11 ++- 6 files changed, 159 insertions(+), 60 deletions(-) create mode 100644 travispy/_tests/test_stateful.py diff --git a/travispy/_tests/test_stateful.py b/travispy/_tests/test_stateful.py new file mode 100644 index 0000000..c454bc3 --- /dev/null +++ b/travispy/_tests/test_stateful.py @@ -0,0 +1,49 @@ +from travispy.entities._stateful import Stateful +import pytest + + +#=================================================================================================== +# Test +#=================================================================================================== +class Test: + + @pytest.mark.parametrize( + 'state, created, queued, started, passed, failed, errored, canceled, ready, pending, running, finished, successful, unsuccessful, color, green, yellow, red', [ + (Stateful.CREATED, True, False, False, False, False, False, False, False, True, False, False, False, False, Stateful.YELLOW, False, True, False), + (Stateful.QUEUED, True, True, False, False, False, False, False, False, True, False, False, False, False, Stateful.YELLOW, False, True, False), + (Stateful.STARTED, True, True, True, False, False, False, False, False, True, True, False, False, False, Stateful.YELLOW, False, True, False), + (Stateful.PASSED, True, True, True, True, False, False, False, False, False, False, True, True, False, Stateful.GREEN, True, False, False), + (Stateful.FAILED, True, True, True, False, True, False, False, False, False, False, True, False, True, Stateful.RED, False, False, True), + (Stateful.ERRORED, True, True, True, False, False, True, False, False, False, False, True, False, True, Stateful.RED, False, False, True), + (Stateful.CANCELED, True, True, True, False, False, False, True, False, False, False, True, False, True, Stateful.RED, False, False, True), + (Stateful.READY, True, True, True, False, False, False, False, True, False, False, True, False, False, Stateful.GREEN, True, False, False), + ]) + def testStateful(self, state, created, queued, started, passed, failed, errored, canceled, ready, pending, running, finished, successful, unsuccessful, color, green, yellow, red): + stateful = Stateful(None) + + assert hasattr(stateful, 'state') == False + with pytest.raises(AttributeError): + stateful.created + + stateful.state = None + with pytest.raises(ValueError): + stateful.created + + stateful.state = state + assert stateful.created == created + assert stateful.queued == queued + assert stateful.started == started + assert stateful.passed == passed + assert stateful.failed == failed + assert stateful.errored == errored + assert stateful.canceled == canceled + assert stateful.ready == ready + assert stateful.pending == pending + assert stateful.running == running + assert stateful.finished == finished + assert stateful.successful == successful + assert stateful.unsuccessful == unsuccessful + assert stateful.color == color + assert stateful.green == green + assert stateful.yellow == yellow + assert stateful.red == red diff --git a/travispy/entities/_restartable.py b/travispy/entities/_restartable.py index 408025b..0633d74 100644 --- a/travispy/entities/_restartable.py +++ b/travispy/entities/_restartable.py @@ -10,10 +10,6 @@ class Restartable(Stateful): Base class for restartable entities such as :class:`.Build` and :class:`.Job`. ''' - __slots__ = [ - 'state', - ] - def cancel(self): ''' Method responsible for canceling current action of this object. diff --git a/travispy/entities/_stateful.py b/travispy/entities/_stateful.py index 3dde6f2..6a08ae1 100644 --- a/travispy/entities/_stateful.py +++ b/travispy/entities/_stateful.py @@ -8,8 +8,23 @@ class Stateful(Entity): ''' Base class for stateful entities such as :class:`.Repo`, :class:`.Build` and :class:`.Job`. + + :ivar str state: + Current state. Possible values are: + + - created + - queued + - started + - passed + - failed + - errored + - canceled + - ready ''' - + + __slots__ = ['state'] + + # States --------------------------------------------------------------------------------------- CANCELED = 'canceled' CREATED = 'created' ERRORED = 'errored' @@ -18,82 +33,121 @@ class Stateful(Entity): QUEUED = 'queued' READY = 'ready' STARTED = 'started' - - STATES = [ - 'canceled', - 'created', - 'errored', - 'failed', - 'passed', - 'queued', - 'ready', - 'started', - ] - - __slots__ = [ - 'created?', - 'errored?', - 'failed?', - 'finished?', - 'green?', - 'passed?', - 'pending?', - 'queued?', - 'red?', - 'running?', - 'started?', - 'successful?', - 'unsuccessful?', - 'yellow?', - 'color', - 'state', - ] + + # Colors --------------------------------------------------------------------------------------- + GREEN = 'green' + YELLOW = 'yellow' + RED = 'red' @property - def ready(self): - return self.state == self.READY + def created(self): + self._check_state() + return hasattr(self, 'state') @property - def pending(self): - return self.state in [self.CREATED, self.STARTED, self.QUEUED] + def queued(self): + self._check_state() + return self.state != self.CREATED @property def started(self): + self._check_state() return self.state not in [self.CREATED, self.QUEUED] @property - def queued(self): - return self.state != self.CREATED + def passed(self): + self._check_state() + return self.state == self.PASSED @property - def finished(self): - return not self.pending + def failed(self): + self._check_state() + return self.state == self.FAILED @property - def passed(self): - return self.state == self.PASSED + def errored(self): + self._check_state() + return self.state == self.ERRORED @property - def errored(self): - return self.state == self.ERRORED + def canceled(self): + self._check_state() + return self.state == self.CANCELED @property - def failed(self): - return self.state == self.FAILED + def ready(self): + return self.state == self.READY @property - def canceled(self): - return self.state == self.CANCELED + def pending(self): + self._check_state() + return self.state in [self.CREATED, self.STARTED, self.QUEUED] + + + @property + def running(self): + return self.state == self.STARTED + + + @property + def finished(self): + return not self.pending + + + @property + def successful(self): + return self.passed @property def unsuccessful(self): return self.errored or self.failed or self.canceled + + + @property + def color(self): + self._check_state() + if self.passed or self.ready: + return self.GREEN + + elif self.pending: + return self.YELLOW + + elif self.unsuccessful: + return self.RED + + + @property + def green(self): + return self.color == self.GREEN + + + @property + def yellow(self): + return self.color == self.YELLOW + + + @property + def red(self): + return self.color == self.RED + + def _check_state(self): + if self.state not in [ + self.CANCELED, + self.CREATED, + self.ERRORED, + self.FAILED, + self.PASSED, + self.QUEUED, + self.READY, + self.STARTED, + ]: + raise ValueError('unknown state %s for %s' % (self.state, self.__class__.__name__)) diff --git a/travispy/entities/build.py b/travispy/entities/build.py index 110fd1b..2dad8c9 100644 --- a/travispy/entities/build.py +++ b/travispy/entities/build.py @@ -28,9 +28,6 @@ class Build(Restartable): :ivar dict config: Build config (secure values and ssh key removed). It comes from ``.travis.yml`` file. - :ivar str state: - Build state. - :ivar str started_at: Time the build was started. @@ -53,7 +50,6 @@ class Build(Restartable): 'pull_request_title', 'pull_request_number', 'config', - 'state', 'started_at', 'finished_at', 'duration', diff --git a/travispy/entities/job.py b/travispy/entities/job.py index a7ecb26..d8307a6 100644 --- a/travispy/entities/job.py +++ b/travispy/entities/job.py @@ -25,9 +25,6 @@ class Job(Restartable): :ivar dict config: Job config (secure values and ssh key removed). It comes from ``.travis.yml`` file. - :ivar str state: - Job state. - :ivar str started_at: Time the job was started. diff --git a/travispy/entities/repo.py b/travispy/entities/repo.py index 81dcf7d..691812e 100644 --- a/travispy/entities/repo.py +++ b/travispy/entities/repo.py @@ -1,11 +1,11 @@ -from ._entity import Entity +from ._stateful import Stateful #=================================================================================================== # Repo #=================================================================================================== -class Repo(Entity): +class Repo(Stateful): ''' :ivar str slug: Repository slug. @@ -51,3 +51,10 @@ class Repo(Entity): 'github_language', 'active', ] + + @property + def state(self): + ''' + This property must be overiden as soon as support to load "lazy" information is implemented. + ''' + return self.CREATED From db04b92ec449f25899584d02e62a360ba66455b9 Mon Sep 17 00:00:00 2001 From: Fabio Menegazzo Date: Mon, 4 Aug 2014 12:19:15 -0300 Subject: [PATCH 3/5] Applying changes on test_stateful as suggested by @nicoddemus Tks for the tip! ;) --- travispy/_tests/test_stateful.py | 56 +++++++++++++++++--------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/travispy/_tests/test_stateful.py b/travispy/_tests/test_stateful.py index c454bc3..841df73 100644 --- a/travispy/_tests/test_stateful.py +++ b/travispy/_tests/test_stateful.py @@ -8,17 +8,17 @@ class Test: @pytest.mark.parametrize( - 'state, created, queued, started, passed, failed, errored, canceled, ready, pending, running, finished, successful, unsuccessful, color, green, yellow, red', [ - (Stateful.CREATED, True, False, False, False, False, False, False, False, True, False, False, False, False, Stateful.YELLOW, False, True, False), - (Stateful.QUEUED, True, True, False, False, False, False, False, False, True, False, False, False, False, Stateful.YELLOW, False, True, False), - (Stateful.STARTED, True, True, True, False, False, False, False, False, True, True, False, False, False, Stateful.YELLOW, False, True, False), - (Stateful.PASSED, True, True, True, True, False, False, False, False, False, False, True, True, False, Stateful.GREEN, True, False, False), - (Stateful.FAILED, True, True, True, False, True, False, False, False, False, False, True, False, True, Stateful.RED, False, False, True), - (Stateful.ERRORED, True, True, True, False, False, True, False, False, False, False, True, False, True, Stateful.RED, False, False, True), - (Stateful.CANCELED, True, True, True, False, False, False, True, False, False, False, True, False, True, Stateful.RED, False, False, True), - (Stateful.READY, True, True, True, False, False, False, False, True, False, False, True, False, False, Stateful.GREEN, True, False, False), + 'state, color, expected_true_properties', [ + (Stateful.CREATED, Stateful.YELLOW, (Stateful.CREATED, Stateful.YELLOW, 'pending')), + (Stateful.QUEUED, Stateful.YELLOW, (Stateful.CREATED, Stateful.QUEUED, Stateful.YELLOW, 'pending')), + (Stateful.STARTED, Stateful.YELLOW, (Stateful.CREATED, Stateful.QUEUED, Stateful.STARTED, Stateful.YELLOW, 'pending', 'running')), + (Stateful.PASSED, Stateful.GREEN, (Stateful.CREATED, Stateful.QUEUED, Stateful.STARTED, Stateful.PASSED, Stateful.GREEN, 'finished', 'successful')), + (Stateful.FAILED, Stateful.RED, (Stateful.CREATED, Stateful.QUEUED, Stateful.STARTED, Stateful.FAILED, Stateful.RED, 'finished', 'unsuccessful')), + (Stateful.ERRORED, Stateful.RED, (Stateful.CREATED, Stateful.QUEUED, Stateful.STARTED, Stateful.ERRORED, Stateful.RED, 'finished', 'unsuccessful')), + (Stateful.CANCELED, Stateful.RED, (Stateful.CREATED, Stateful.QUEUED, Stateful.STARTED, Stateful.CANCELED, Stateful.RED, 'finished', 'unsuccessful')), + (Stateful.READY, Stateful.GREEN, (Stateful.CREATED, Stateful.QUEUED, Stateful.STARTED, Stateful.READY, Stateful.GREEN, 'finished')), ]) - def testStateful(self, state, created, queued, started, passed, failed, errored, canceled, ready, pending, running, finished, successful, unsuccessful, color, green, yellow, red): + def test_stateful(self, state, color, expected_true_properties): stateful = Stateful(None) assert hasattr(stateful, 'state') == False @@ -30,20 +30,24 @@ def testStateful(self, state, created, queued, started, passed, failed, errored, stateful.created stateful.state = state - assert stateful.created == created - assert stateful.queued == queued - assert stateful.started == started - assert stateful.passed == passed - assert stateful.failed == failed - assert stateful.errored == errored - assert stateful.canceled == canceled - assert stateful.ready == ready - assert stateful.pending == pending - assert stateful.running == running - assert stateful.finished == finished - assert stateful.successful == successful - assert stateful.unsuccessful == unsuccessful assert stateful.color == color - assert stateful.green == green - assert stateful.yellow == yellow - assert stateful.red == red + + for prop in [ + Stateful.CREATED, + Stateful.QUEUED, + Stateful.STARTED, + Stateful.PASSED, + Stateful.FAILED, + Stateful.ERRORED, + Stateful.CANCELED, + Stateful.READY, + Stateful.GREEN, + Stateful.YELLOW, + Stateful.RED, + 'pending', + 'running', + 'finished', + 'successful', + 'unsuccessful', + ]: + assert getattr(stateful, prop) == (prop in expected_true_properties) From 8590861e0dda81c35bfc4587bbb47361fdde94f5 Mon Sep 17 00:00:00 2001 From: Fabio Menegazzo Date: Mon, 4 Aug 2014 12:19:42 -0300 Subject: [PATCH 4/5] Updating documentation about stateful entities. --- docs/entities.rst | 3 + travispy/entities/_stateful.py | 196 ++++++++++++++++++++++++++++++--- travispy/entities/job.py | 1 - 3 files changed, 181 insertions(+), 19 deletions(-) diff --git a/docs/entities.rst b/docs/entities.rst index 5e9d17e..b55c02c 100644 --- a/docs/entities.rst +++ b/docs/entities.rst @@ -22,6 +22,9 @@ This document brings information about all entities that are used by |travispy| .. autoclass:: Entity :no-show-inheritance: +.. module:: travispy.entities._stateful +.. autoclass:: Stateful + .. module:: travispy.entities._restartable .. autoclass:: Restartable diff --git a/travispy/entities/_stateful.py b/travispy/entities/_stateful.py index 6a08ae1..d04eaf2 100644 --- a/travispy/entities/_stateful.py +++ b/travispy/entities/_stateful.py @@ -9,17 +9,62 @@ class Stateful(Entity): ''' Base class for stateful entities such as :class:`.Repo`, :class:`.Build` and :class:`.Job`. + .. attribute:: CANCELED + + Constant representing state ``canceled``. Should not be changed. + + .. attribute:: CREATED + + Constant representing state ``created``. Should not be changed. + + .. attribute:: QUEUED + + Constant representing state ``queued``. Should not be changed. + + .. attribute:: STARTED + + Constant representing state ``started``. Should not be changed. + + .. attribute:: PASSED + + Constant representing state ``passed``. Should not be changed. + + .. attribute:: FAILED + + Constant representing state ``failed``. Should not be changed. + + .. attribute:: ERRORED + + Constant representing state ``errored``. Should not be changed. + + .. attribute:: READY + + Constant representing state ``ready``. Should not be changed. + + .. attribute:: GREEN + + Constant representing state color ``green``. Should not be changed. + + .. attribute:: YELLOW + + Constant representing state color ``yellow``. Should not be changed. + + .. attribute:: RED + + Constant representing state color ``red``. Should not be changed. + :ivar str state: + Current state. Possible values are: - - created - - queued - - started - - passed - - failed - - errored - - canceled - - ready + - :attr:`.CANCELED` + - :attr:`.CREATED` + - :attr:`.QUEUED` + - :attr:`.STARTED` + - :attr:`.PASSED` + - :attr:`.FAILED` + - :attr:`.ERRORED` + - :attr:`.READY` ''' __slots__ = ['state'] @@ -41,80 +86,173 @@ class Stateful(Entity): @property def created(self): - self._check_state() + ''' + :rtype: bool + :returns: + ``True`` if entity build process was created successfully. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() return hasattr(self, 'state') @property def queued(self): - self._check_state() + ''' + :rtype: bool + :returns: + ``True`` if entity was already queued sometime in the build process. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() return self.state != self.CREATED @property def started(self): - self._check_state() + ''' + :rtype: bool + :returns: + ``True`` if entity was already started sometime in the build process. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() return self.state not in [self.CREATED, self.QUEUED] @property def passed(self): - self._check_state() + ''' + :rtype: bool + :returns: + ``True`` if build process was finished successfully. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() return self.state == self.PASSED @property def failed(self): - self._check_state() + ''' + :rtype: bool + :returns: + ``True`` if build process failed. This is usually related to failures on tests. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() return self.state == self.FAILED @property def errored(self): - self._check_state() + ''' + :rtype: bool + :returns: + ``True`` if build process got errors. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() return self.state == self.ERRORED @property def canceled(self): - self._check_state() + ''' + :rtype: bool + :returns: + ``True`` if build process was canceled. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() return self.state == self.CANCELED @property def ready(self): + ''' + :rtype: bool + :returns: + ``True`` if build process is ready. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() return self.state == self.READY @property def pending(self): - self._check_state() + ''' + :rtype: bool + :returns: + ``True`` if build was scheduled but was not finished. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() return self.state in [self.CREATED, self.STARTED, self.QUEUED] @property def running(self): + ''' + :rtype: bool + :returns: + ``True`` if build process is running. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() return self.state == self.STARTED @property def finished(self): + ''' + :rtype: bool + :returns: + ``True`` if build process is finished. + ''' return not self.pending @property def successful(self): + ''' + .. seealso:: :attr:`.passed` + ''' return self.passed @property def unsuccessful(self): + ''' + :rtype: bool + :returns: + ``True`` if build process was finished unsuccessfully. + ''' return self.errored or self.failed or self.canceled @property def color(self): - self._check_state() + ''' + :rtype: bool + :returns: + The color related to current build state. Possible values are: + + - :attr:`.GREEN`: when build has passed or it is ready. + - :attr:`.YELLOW`: when build process is running. + - :attr:`.RED`: when build has failed somehow. + ''' if self.passed or self.ready: return self.GREEN @@ -127,19 +265,41 @@ def color(self): @property def green(self): + ''' + :rtype: bool + :returns: + ``True`` if build :attr:`.color` is :attr:`.GREEN`. + ''' return self.color == self.GREEN @property def yellow(self): + ''' + :rtype: bool + :returns: + ``True`` if build :attr:`.color` is :attr:`.YELLOW`. + ''' return self.color == self.YELLOW @property def red(self): + ''' + :rtype: bool + :returns: + ``True`` if build :attr:`.color` is :attr:`.RED`. + ''' return self.color == self.RED - def _check_state(self): + + def check_state(self): + ''' + Method responsible for checking and validating current :attr:`.state`. + + :raises AttributeError: when :attr:`.state` does not exist. + :raises ValueError: when :attr:`.state` value is not supported. + ''' if self.state not in [ self.CANCELED, self.CREATED, diff --git a/travispy/entities/job.py b/travispy/entities/job.py index d8307a6..2698934 100644 --- a/travispy/entities/job.py +++ b/travispy/entities/job.py @@ -52,7 +52,6 @@ class Job(Restartable): 'log_id', 'number', 'config', - 'state', 'started_at', 'finished_at', 'duration', From 13e4681ae6aed2c4ee6c44b449d303f20d37f1ac Mon Sep 17 00:00:00 2001 From: Fabio Menegazzo Date: Mon, 4 Aug 2014 14:34:43 -0300 Subject: [PATCH 5/5] Fixing Repo.state to return last_build_state reference. Also added a line to test state against last_build_state so coverage may be improved. --- travispy/_tests/test_travispy.py | 1 + travispy/entities/repo.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/travispy/_tests/test_travispy.py b/travispy/_tests/test_travispy.py index c6ef1d5..ea0d40c 100644 --- a/travispy/_tests/test_travispy.py +++ b/travispy/_tests/test_travispy.py @@ -166,6 +166,7 @@ def test_repos(self, repo_slug): assert hasattr(repo, 'last_build_duration') assert hasattr(repo, 'last_build_started_at') assert hasattr(repo, 'last_build_finished_at') + assert repo.state == repo.last_build_state def test_user(self): diff --git a/travispy/entities/repo.py b/travispy/entities/repo.py index 691812e..249259e 100644 --- a/travispy/entities/repo.py +++ b/travispy/entities/repo.py @@ -55,6 +55,8 @@ class Repo(Stateful): @property def state(self): ''' - This property must be overiden as soon as support to load "lazy" information is implemented. + :class:`.Repo` state is given through ``last_build_state``. + + .. seealso:: :class:`.Stateful` for ``state`` full documentation. ''' - return self.CREATED + return self.last_build_state