Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State machine entities #3

Merged
merged 5 commits into from Aug 4, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/entities.rst
Expand Up @@ -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

Expand Down
53 changes: 53 additions & 0 deletions travispy/_tests/test_stateful.py
@@ -0,0 +1,53 @@
from travispy.entities._stateful import Stateful
import pytest


#===================================================================================================
# Test
#===================================================================================================
class Test:

@pytest.mark.parametrize(
'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 test_stateful(self, state, color, expected_true_properties):
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.color == color

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)
1 change: 1 addition & 0 deletions travispy/_tests/test_travispy.py
Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions travispy/entities/_restartable.py
@@ -1,11 +1,11 @@
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`.
'''
Expand Down
313 changes: 313 additions & 0 deletions travispy/entities/_stateful.py
@@ -0,0 +1,313 @@
from ._entity import Entity



#===================================================================================================
# Stateful
#===================================================================================================
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:

- :attr:`.CANCELED`
- :attr:`.CREATED`
- :attr:`.QUEUED`
- :attr:`.STARTED`
- :attr:`.PASSED`
- :attr:`.FAILED`
- :attr:`.ERRORED`
- :attr:`.READY`
'''

__slots__ = ['state']

# States ---------------------------------------------------------------------------------------
CANCELED = 'canceled'
CREATED = 'created'
ERRORED = 'errored'
FAILED = 'failed'
PASSED = 'passed'
QUEUED = 'queued'
READY = 'ready'
STARTED = 'started'

# Colors ---------------------------------------------------------------------------------------
GREEN = 'green'
YELLOW = 'yellow'
RED = 'red'

@property
def created(self):
'''
: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):
'''
: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):
'''
: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):
'''
: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):
'''
: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):
'''
: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):
'''
: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):
'''
: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):
'''
: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

elif self.pending:
return self.YELLOW

elif self.unsuccessful:
return self.RED


@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):
'''
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,
self.ERRORED,
self.FAILED,
self.PASSED,
self.QUEUED,
self.READY,
self.STARTED,
]:
raise ValueError('unknown state %s for %s' % (self.state, self.__class__.__name__))