In particular, 'scheduled' will run if not explicitly skipped, 'skipped' means 'is never going to be run' and 'requested' means 'will run and can not be skipped'. --- pyanaconda/dispatch.py | 103 +++++++++++++++++------- pyanaconda/errors.py | 3 + pyanaconda/installclass.py | 2 +- pyanaconda/upgrade.py | 2 +- tests/pyanaconda_test/dispatch_test.py | 133 ++++++++++++++++++++++++++++++++ 5 files changed, 211 insertions(+), 32 deletions(-) create mode 100644 tests/pyanaconda_test/dispatch_test.py diff --git a/pyanaconda/dispatch.py b/pyanaconda/dispatch.py index ee15ce2..0f4499a 100644 --- a/pyanaconda/dispatch.py +++ b/pyanaconda/dispatch.py @@ -24,6 +24,7 @@ import string from types import * import indexed_dict +import errors from constants import * from packages import writeKSConfiguration, turnOnFilesystems from packages import doPostAction @@ -55,11 +56,21 @@ from packages import doReIPL import logging log = logging.getLogger("anaconda") -SCHED_SKIPPED = -2 -SCHED_SKIPPED_PERMANENTLY = -1 -SCHED_SCHEDULED = 1 - class Step(object): + SCHED_UNSCHEDULED = 0 + SCHED_SCHEDULED = 1 # will execute if not explicitly skipped + SCHED_SKIPPED = 2 # is never going to execute + SCHED_REQUESTED = 3 # will execute and can not be skipped + SCHED_DONE = 4 # done is a final state + + sched_state_machine = [ + # unscheduled # scheduled # skipped # requested # done + [SCHED_UNSCHEDULED, SCHED_SCHEDULED, SCHED_SKIPPED, SCHED_REQUESTED, SCHED_DONE], # unscheduled + [None , SCHED_SCHEDULED, SCHED_SKIPPED, SCHED_REQUESTED, SCHED_DONE], # scheduled + [None , None , SCHED_SKIPPED, None , None], # skipped + [None , None , None , SCHED_REQUESTED, SCHED_DONE], # requested + [None , None , None , None , SCHED_DONE]] # done + def __init__(self, name, target = None): """ Dispatcher step object. @@ -70,22 +81,48 @@ class Step(object): """ self.name = name self.target = target # None for dynamic target (e.g. gui view) - self._sched = SCHED_SKIPPED + self._sched = self.SCHED_UNSCHEDULED - def reschedule(self, new_sched): - if self._sched == SCHED_SKIPPED_PERMANENTLY: - # do not allow "upgrading" of permanently skipped steps - return + def _reschedule(self, to_sched): + new_sched = self.sched_state_machine[self._sched][to_sched] + if new_sched is None: + raise errors.DispatchError( + "Can not reschedule step '%s' from '%s' to '%s'" % + (self.name, + self.namesched(self._sched), + self.namesched(to_sched))) self._sched = new_sched @property def direct(self): return self.target is not None + def done(self): + self._reschedule(self.SCHED_DONE) + + def request(self): + self._reschedule(self.SCHED_REQUESTED) + + def namesched(self, sched): + return { + self.SCHED_UNSCHEDULED : "unscheduled", + self.SCHED_SCHEDULED : "scheduled", + self.SCHED_SKIPPED : "skipped", + self.SCHED_REQUESTED : "requested", + self.SCHED_DONE : "done" + }[sched] + @property def sched(self): return self._sched + def schedule(self): + self._reschedule(self.SCHED_SCHEDULED) + + def skip(self): + self._reschedule(self.SCHED_SKIPPED) + + class Dispatcher(object): def __init__(self, anaconda): @@ -158,6 +195,9 @@ class Dispatcher(object): def _step_index(self): return self.steps.index(self.step) + def done_steps(self, *steps): + map(lambda s: self.steps[s].done(), steps) + def go_back(self): """ The caller should make sure canGoBack() is True before calling this @@ -170,42 +210,43 @@ class Dispatcher(object): self._setDir(DISPATCH_FORWARD) self.dispatch() - def canGoBack(self): + def can_go_back(self): # Begin with the step before this one. If all steps are skipped, # we can not go backwards from this one. i = self._step_index() - 1 while i >= 0: sname = self.steps[i].name - if not (self.stepIsDirect(sname) or self.stepInSkipList(sname)): + if not self.step_is_direct(sname) and self.step_enabled(sname): return True i -= 1 return False + def request_step(self, *steps): + map(lambda s: self.steps[s].request(), steps) + def run(self): self.anaconda.intf.run(self.anaconda) log.info("dispatch: finished.") - def setStepList(self, *new_steps): - for (name, step) in self.steps.items(): - if name in new_steps: - step.reschedule(SCHED_SCHEDULED) - else: - step.reschedule(SCHED_SKIPPED) + def schedule_steps(self, *steps): + map(lambda s: self.steps[s].schedule(), steps) - def stepInSkipList(self, step): - return self.steps[step].sched in [SCHED_SKIPPED_PERMANENTLY, - SCHED_SKIPPED] + def step_disabled(self, step): + """ True if step is not yet scheduled to be run or will never be run + (i.e. is skipped). + """ + return not self.step_enabled(step) - def skipStep(self, step, skip = 1, permanent = 0): - new_sched = SCHED_SCHEDULED - if skip: - if permanent: - new_sched = SCHED_SKIPPED_PERMANENTLY - else: - new_sched = SCHED_SKIPPED - self.steps[step].reschedule(new_sched) + def step_enabled(self, step): + """ True if step is scheduled to be run or have been run already. """ + return self.steps[step].sched in [Step.SCHED_SCHEDULED, + Step.SCHED_REQUESTED, + Step.SCHED_DONE] + + def skipStep(self, *steps): + map(lambda s: self.steps[s].skip(), steps) - def stepIsDirect(self, step): + def step_is_direct(self, step): return self.steps[step].direct def dispatch(self): @@ -216,6 +257,7 @@ class Dispatcher(object): else: log.info("dispatch: leaving (%d) step %s" % (self.dir, self.step)) + self.done_steps(self.step) self._advance_step() while True: @@ -223,7 +265,7 @@ class Dispatcher(object): # installation has proceeded beyond the last step: finished self.anaconda.intf.shutdown() return - if self.stepInSkipList(self.step): + if self.step_disabled(self.step): self._advance_step() continue log.info("dispatch: moving (%d) to step %s" % @@ -248,6 +290,7 @@ class Dispatcher(object): self.dir = rc log.info("dispatch: leaving (%d) step %s" % (self.dir, self.step)) + self.done_steps(self.step) self._advance_step() def _getDir(self): diff --git a/pyanaconda/errors.py b/pyanaconda/errors.py index 9733733..0bb6237 100644 --- a/pyanaconda/errors.py +++ b/pyanaconda/errors.py @@ -157,3 +157,6 @@ class NoSuchGroup(Exception): def __str__ (self): return self.value + +class DispatchError(RuntimeError): + pass diff --git a/pyanaconda/installclass.py b/pyanaconda/installclass.py index 8092e94..7815051 100644 --- a/pyanaconda/installclass.py +++ b/pyanaconda/installclass.py @@ -83,7 +83,7 @@ class BaseInstallClass(object): def setSteps(self, anaconda): dispatch = anaconda.dispatch - dispatch.setStepList( + dispatch.schedule_steps( "sshd", "language", "keyboard", diff --git a/pyanaconda/upgrade.py b/pyanaconda/upgrade.py index 6a16169..2b96eb5 100644 --- a/pyanaconda/upgrade.py +++ b/pyanaconda/upgrade.py @@ -288,7 +288,7 @@ def upgradeMountFilesystems(anaconda): def setSteps(anaconda): dispatch = anaconda.dispatch - dispatch.setStepList( + dispatch.schedule_steps( "language", "keyboard", "filtertype", diff --git a/tests/pyanaconda_test/dispatch_test.py b/tests/pyanaconda_test/dispatch_test.py new file mode 100644 index 0000000..485ddde --- /dev/null +++ b/tests/pyanaconda_test/dispatch_test.py @@ -0,0 +1,133 @@ +import mock + +class StepTest(mock.TestCase): + def setUp(self): + self.setupModules( + ['logging', 'pyanaconda.anaconda_log', 'block']) + + import pyanaconda + pyanaconda.anaconda_log = mock.Mock() + + def tearDown(self): + self.tearDownModules() + + def done_test(self): + from pyanaconda.dispatch import Step + from pyanaconda.errors import DispatchError + s = Step("a_step") + s.schedule() + s.request() + s.done() + self.assertEquals(s.sched, Step.SCHED_DONE) + self.assertRaises(DispatchError, s.skip) + + def instantiation_test(self): + from pyanaconda.dispatch import Step + s = Step("yeah") + self.assertIsInstance(s, Step) + # name + self.assertEqual(s.name, "yeah") + # default scheduling + self.assertEqual(s.sched, Step.SCHED_UNSCHEDULED) + + def namesched_test(self): + from pyanaconda.dispatch import Step + s = Step("a_step") + self.assertEqual(s.namesched(Step.SCHED_UNSCHEDULED), "unscheduled") + + def reschedule_test(self): + from pyanaconda.dispatch import Step + from pyanaconda.errors import DispatchError + s = Step("a_step") + s._reschedule(Step.SCHED_UNSCHEDULED) + self.assertEqual(s.sched, Step.SCHED_UNSCHEDULED) + s._reschedule(Step.SCHED_SCHEDULED) + self.assertEqual(s.sched, Step.SCHED_SCHEDULED) + s._reschedule(Step.SCHED_REQUESTED) + self.assertEqual(s.sched, Step.SCHED_REQUESTED) + self.assertRaises(DispatchError, s._reschedule, Step.SCHED_SKIPPED) + self.assertRaises(DispatchError, s._reschedule, Step.SCHED_UNSCHEDULED) + s._reschedule(Step.SCHED_DONE) + self.assertEqual(s.sched, Step.SCHED_DONE) + + s = Step("another_step") + s._reschedule(Step.SCHED_SKIPPED) + self.assertEqual(s.sched, Step.SCHED_SKIPPED) + self.assertRaises(DispatchError, s._reschedule, Step.SCHED_SCHEDULED) + self.assertRaises(DispatchError, s._reschedule, Step.SCHED_REQUESTED) + self.assertRaises(DispatchError, s._reschedule, Step.SCHED_DONE) + + def request_test(self): + from pyanaconda.dispatch import Step + from pyanaconda.errors import DispatchError + s = Step("a_step") + s.request() + self.assertRaises(DispatchError, s.skip) + + def schedule_test(self): + from pyanaconda.dispatch import Step + s = Step("a_step") + s.schedule() + self.assertEquals(s.sched, Step.SCHED_SCHEDULED) + + def skip_test(self): + from pyanaconda.dispatch import Step + from pyanaconda.errors import DispatchError + s = Step("a_step") + s.skip() + self.assertEquals(s.sched, Step.SCHED_SKIPPED) + self.assertRaises(DispatchError, s.done) + self.assertRaises(DispatchError, s.request) + +class DispatchTest(mock.TestCase): + def setUp(self): + self.setupModules( + ['logging', 'pyanaconda.anaconda_log', 'block']) + + import pyanaconda + pyanaconda.anaconda_log = mock.Mock() + + def tearDown(self): + self.tearDownModules() + + def _getDispatcher(self): + from pyanaconda.dispatch import Dispatcher + self.anaconda_obj = mock.Mock() + return Dispatcher(self.anaconda_obj) + + def done_test(self): + from pyanaconda.dispatch import Dispatcher + from pyanaconda.dispatch import Step + from pyanaconda.errors import DispatchError + + d = self._getDispatcher() + self.assertFalse(d.step_enabled("betanag")) + d.schedule_steps("betanag") + d.done_steps("betanag") + self.assertTrue(d.step_enabled("betanag")) + self.assertTrue(d.steps["betanag"], Step.SCHED_DONE) + self.assertRaises(DispatchError, d.skipStep, "betanag") + + def instantiation_test(self): + from pyanaconda.dispatch import Dispatcher + d = self._getDispatcher() + self.assertIsInstance(d, Dispatcher) + + def schedule_test(self): + from pyanaconda.dispatch import Step + d = self._getDispatcher() + d.schedule_steps("betanag", "complete") + self.assertEqual(d.steps["betanag"].sched, Step.SCHED_SCHEDULED) + self.assertEqual(d.steps["complete"].sched, Step.SCHED_SCHEDULED) + self.assertEqual(d.steps["bootloader"].sched, Step.SCHED_UNSCHEDULED) + # impossible to reach nonexistent steps: + self.assertRaises(KeyError, d.steps.__getitem__, "nonexistent") + + def skip_test(self): + d = self._getDispatcher() + d.schedule_steps("betanag", "filtertype", "complete") + self.assertTrue(d.step_enabled("betanag")) + d.skipStep("betanag", "complete") + self.assertFalse(d.step_enabled("betanag")) + self.assertFalse(d.step_enabled("complete")) + self.assertTrue(d.step_enabled("filtertype")) -- 1.7.3.3 _______________________________________________ Anaconda-devel-list mailing list Anaconda-devel-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/anaconda-devel-list